seperate subjects/transaction on a user basis
This commit is contained in:
@@ -1,21 +1,43 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
|
||||||
from core.models import Subject, Transaction
|
from core.models import Subject, Transaction
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class CustomAdminSite(admin.AdminSite):
|
class CustomAdminSite(admin.AdminSite):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
admin_site = CustomAdminSite()
|
admin_site = CustomAdminSite()
|
||||||
|
admin_site.register(User, UserAdmin)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Subject, site=admin_site)
|
@admin.register(Subject, site=admin_site)
|
||||||
class SubjectAdmin(admin.ModelAdmin):
|
class SubjectAdmin(admin.ModelAdmin):
|
||||||
pass
|
list_display = ("name",)
|
||||||
|
search_fields = ("name",)
|
||||||
|
exclude = ("user",)
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if not change:
|
||||||
|
obj.user = request.user
|
||||||
|
return super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
def get_search_results(self, request, queryset, search_term):
|
||||||
|
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
|
||||||
|
return queryset.filter(user=request.user), use_distinct
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Transaction, site=admin_site)
|
@admin.register(Transaction, site=admin_site)
|
||||||
class TransactionAdmin(admin.ModelAdmin):
|
class TransactionAdmin(admin.ModelAdmin):
|
||||||
list_display = ("subject", "amount", "booking_date",)
|
list_display = ("subject", "amount", "booking_date",)
|
||||||
ordering = ("-booking_date",)
|
ordering = ("-booking_date",)
|
||||||
|
search_fields = ("subject__name",)
|
||||||
|
autocomplete_fields = ("subject",)
|
||||||
|
|
||||||
|
def get_search_results(self, request, queryset, search_term):
|
||||||
|
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
|
||||||
|
return queryset.filter(subject__user=request.user), use_distinct
|
||||||
|
|||||||
33
core/migrations/0002_auto_20210118_2334.py
Normal file
33
core/migrations/0002_auto_20210118_2334.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 3.1.5 on 2021-01-18 22:34
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('core', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Balance',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='subject',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='subjects', related_query_name='subject', to='auth.user'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='subject',
|
||||||
|
unique_together={('user', 'name')},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='subject',
|
||||||
|
name='created_time',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class PriceField(models.DecimalField):
|
class PriceField(models.DecimalField):
|
||||||
@@ -22,8 +24,14 @@ class PriceField(models.DecimalField):
|
|||||||
|
|
||||||
|
|
||||||
class Subject(models.Model):
|
class Subject(models.Model):
|
||||||
|
class Meta:
|
||||||
|
unique_together = ("user", "name")
|
||||||
|
|
||||||
|
user = models.ForeignKey(
|
||||||
|
to=User, on_delete=models.CASCADE, editable=False,
|
||||||
|
related_name="subjects", related_query_name="subject",
|
||||||
|
)
|
||||||
name = models.CharField(max_length=64)
|
name = models.CharField(max_length=64)
|
||||||
created_time = models.DateTimeField(default=timezone.now)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@@ -39,11 +47,3 @@ class Transaction(models.Model):
|
|||||||
to=Subject, on_delete=models.CASCADE,
|
to=Subject, on_delete=models.CASCADE,
|
||||||
related_name="transactions", related_query_name="transaction",
|
related_name="transactions", related_query_name="transaction",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Balance(models.Model):
|
|
||||||
class Meta:
|
|
||||||
get_latest_by = "date"
|
|
||||||
|
|
||||||
amount = PriceField()
|
|
||||||
date = models.DateField()
|
|
||||||
|
|||||||
@@ -150,8 +150,8 @@ def get_color_for_amount(amount: Decimal, minimum: Decimal, maximum: Decimal):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def predict_balance(start_balance=Decimal("0")):
|
def predict_balance(subjects: Iterable[Subject], start_balance=Decimal("0")):
|
||||||
prediction_list = predict_all(Subject.objects.order_by("name"))
|
prediction_list = predict_all(subjects)
|
||||||
today = timezone.now().date()
|
today = timezone.now().date()
|
||||||
future_transactions = []
|
future_transactions = []
|
||||||
minimum, maximum = Decimal(), Decimal()
|
minimum, maximum = Decimal(), Decimal()
|
||||||
|
|||||||
@@ -1,32 +1,37 @@
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from core.models import Subject, Transaction
|
from core.models import Subject, Transaction
|
||||||
from core.prediction import predict_transactions
|
from core.prediction import predict_transactions
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class PredictionTestCase(TestCase):
|
class PredictionTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create(username="dummy")
|
||||||
|
self.subject = Subject.objects.create(user=self.user, name="rent")
|
||||||
|
|
||||||
def test_not_enough_data(self):
|
def test_not_enough_data(self):
|
||||||
subject = Subject.objects.create(name="rent")
|
predicted_transactions, prediction_info = predict_transactions(self.subject)
|
||||||
predicted_transactions, prediction_info = predict_transactions(subject)
|
|
||||||
self.assertEqual(predicted_transactions, [])
|
self.assertEqual(predicted_transactions, [])
|
||||||
self.assertIsNone(prediction_info)
|
self.assertIsNone(prediction_info)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=self.subject)
|
||||||
predicted_transactions, prediction_info = predict_transactions(subject)
|
predicted_transactions, prediction_info = predict_transactions(self.subject)
|
||||||
self.assertEqual(predicted_transactions, [])
|
self.assertEqual(predicted_transactions, [])
|
||||||
self.assertIsNone(prediction_info)
|
self.assertIsNone(prediction_info)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 7, 2), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 7, 2), subject=self.subject)
|
||||||
predicted_transactions, prediction_info = predict_transactions(subject)
|
predicted_transactions, prediction_info = predict_transactions(self.subject)
|
||||||
self.assertNotEqual(predicted_transactions, [])
|
self.assertNotEqual(predicted_transactions, [])
|
||||||
self.assertIsNotNone(prediction_info)
|
self.assertIsNotNone(prediction_info)
|
||||||
|
|
||||||
def test_every_month(self):
|
def test_every_month(self):
|
||||||
subject = Subject.objects.create(name="rent")
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 7, 2), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 7, 2), subject=subject)
|
predicted_transactions, prediction_info = predict_transactions(self.subject)
|
||||||
predicted_transactions, prediction_info = predict_transactions(subject)
|
|
||||||
first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1]
|
first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1]
|
||||||
self.assertEqual(first.booking_date, date(2020, 8, 2))
|
self.assertEqual(first.booking_date, date(2020, 8, 2))
|
||||||
self.assertEqual(second.booking_date, date(2020, 9, 2))
|
self.assertEqual(second.booking_date, date(2020, 9, 2))
|
||||||
@@ -39,10 +44,9 @@ class PredictionTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_every_3_months(self):
|
def test_every_3_months(self):
|
||||||
subject = Subject.objects.create(name="rent")
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 9, 2), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 9, 2), subject=subject)
|
predicted_transactions, prediction_info = predict_transactions(self.subject)
|
||||||
predicted_transactions, prediction_info = predict_transactions(subject)
|
|
||||||
first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1]
|
first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1]
|
||||||
self.assertEqual(first.booking_date, date(2020, 12, 2))
|
self.assertEqual(first.booking_date, date(2020, 12, 2))
|
||||||
self.assertEqual(second.booking_date, date(2021, 3, 2))
|
self.assertEqual(second.booking_date, date(2021, 3, 2))
|
||||||
@@ -55,10 +59,9 @@ class PredictionTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_every_year(self):
|
def test_every_year(self):
|
||||||
subject = Subject.objects.create(name="rent")
|
Transaction.objects.create(amount=-333, booking_date=date(2019, 6, 2), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2019, 6, 2), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=subject)
|
predicted_transactions, prediction_info = predict_transactions(self.subject)
|
||||||
predicted_transactions, prediction_info = predict_transactions(subject)
|
|
||||||
self.assertEqual(len(predicted_transactions), 1)
|
self.assertEqual(len(predicted_transactions), 1)
|
||||||
trans = predicted_transactions[0]
|
trans = predicted_transactions[0]
|
||||||
self.assertEqual(trans.booking_date, date(2021, 6, 2))
|
self.assertEqual(trans.booking_date, date(2021, 6, 2))
|
||||||
@@ -69,12 +72,11 @@ class PredictionTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_monthly_varying_begin_end(self):
|
def test_monthly_varying_begin_end(self):
|
||||||
subject = Subject.objects.create(name="rent")
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 1, 31), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 1, 31), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 3, 1), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 3, 1), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 4, 1), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 4, 1), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 4, 30), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 4, 30), subject=subject)
|
predicted_transactions, prediction_info = predict_transactions(self.subject)
|
||||||
predicted_transactions, prediction_info = predict_transactions(subject)
|
|
||||||
first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1]
|
first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1]
|
||||||
self.assertEqual(first.booking_date, date(2020, 5, 30))
|
self.assertEqual(first.booking_date, date(2020, 5, 30))
|
||||||
self.assertEqual(second.booking_date, date(2020, 6, 30))
|
self.assertEqual(second.booking_date, date(2020, 6, 30))
|
||||||
@@ -87,12 +89,11 @@ class PredictionTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_no_monthly_pattern(self):
|
def test_no_monthly_pattern(self):
|
||||||
subject = Subject.objects.create(name="rent")
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 1, 15), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 1, 15), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 2, 10), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 2, 10), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 4, 25), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 4, 25), subject=subject)
|
Transaction.objects.create(amount=-333, booking_date=date(2020, 5, 5), subject=self.subject)
|
||||||
Transaction.objects.create(amount=-333, booking_date=date(2020, 5, 5), subject=subject)
|
predicted_transactions, prediction_info = predict_transactions(self.subject)
|
||||||
predicted_transactions, prediction_info = predict_transactions(subject)
|
|
||||||
first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1]
|
first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1]
|
||||||
self.assertEqual(first.booking_date, date(2020, 6, 11))
|
self.assertEqual(first.booking_date, date(2020, 6, 11))
|
||||||
self.assertEqual(second.booking_date, date(2020, 7, 18))
|
self.assertEqual(second.booking_date, date(2020, 7, 18))
|
||||||
@@ -105,10 +106,9 @@ class PredictionTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_amount_change(self):
|
def test_amount_change(self):
|
||||||
subject = Subject.objects.create(name="rent")
|
Transaction.objects.create(amount=1337, booking_date=date(2020, 6, 2), subject=self.subject)
|
||||||
Transaction.objects.create(amount=1337, booking_date=date(2020, 6, 2), subject=subject)
|
Transaction.objects.create(amount=1312, booking_date=date(2020, 7, 2), subject=self.subject)
|
||||||
Transaction.objects.create(amount=1312, booking_date=date(2020, 7, 2), subject=subject)
|
Transaction.objects.create(amount=404, booking_date=date(2020, 8, 2), subject=self.subject)
|
||||||
Transaction.objects.create(amount=404, booking_date=date(2020, 8, 2), subject=subject)
|
predicted_transactions, prediction_info = predict_transactions(self.subject)
|
||||||
predicted_transactions, prediction_info = predict_transactions(subject)
|
|
||||||
for tr in predicted_transactions:
|
for tr in predicted_transactions:
|
||||||
self.assertEqual(tr.amount, 404)
|
self.assertEqual(tr.amount, 404)
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
from decimal import Decimal, InvalidOperation
|
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from core.models import Balance
|
|
||||||
|
|
||||||
|
|
||||||
def _create_and_reload_balance(amount):
|
|
||||||
bal = Balance.objects.create(amount=amount, date=timezone.now().date())
|
|
||||||
bal.refresh_from_db()
|
|
||||||
return bal
|
|
||||||
|
|
||||||
|
|
||||||
class PriceFieldTestCase(TestCase):
|
|
||||||
|
|
||||||
def test_normal_decimal(self):
|
|
||||||
bal = _create_and_reload_balance(Decimal("13.37"))
|
|
||||||
self.assertEqual(bal.amount, Decimal("13.37"))
|
|
||||||
|
|
||||||
def test_too_big_decimal(self):
|
|
||||||
with self.assertRaises(InvalidOperation):
|
|
||||||
_create_and_reload_balance(Decimal("13371337.00"))
|
|
||||||
|
|
||||||
def test_rounding_decimal(self):
|
|
||||||
bal = _create_and_reload_balance(Decimal("1.337"))
|
|
||||||
self.assertEqual(bal.amount, Decimal("1.34"))
|
|
||||||
|
|
||||||
def test_normal_int(self):
|
|
||||||
bal = _create_and_reload_balance(1337)
|
|
||||||
self.assertEqual(bal.amount, Decimal("1337.00"))
|
|
||||||
|
|
||||||
def test_too_big_int(self):
|
|
||||||
with self.assertRaises(InvalidOperation):
|
|
||||||
_create_and_reload_balance(13371337)
|
|
||||||
|
|
||||||
def test_normal_float(self):
|
|
||||||
bal = _create_and_reload_balance(13.37)
|
|
||||||
self.assertEqual(bal.amount, Decimal("13.37"))
|
|
||||||
|
|
||||||
def test_too_big_float(self):
|
|
||||||
with self.assertRaises(InvalidOperation):
|
|
||||||
_create_and_reload_balance(13371337.00)
|
|
||||||
|
|
||||||
def test_rounding_float(self):
|
|
||||||
bal = _create_and_reload_balance(1.337)
|
|
||||||
self.assertEqual(bal.amount, Decimal("1.34"))
|
|
||||||
@@ -11,6 +11,7 @@ class BalanceView(TemplateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
subjects = Subject.objects.filter(user=self.request.user).order_by("name")
|
||||||
amount, amount_error = Decimal(), False
|
amount, amount_error = Decimal(), False
|
||||||
if amount_query := self.request.GET.get("amount"):
|
if amount_query := self.request.GET.get("amount"):
|
||||||
try:
|
try:
|
||||||
@@ -19,7 +20,7 @@ class BalanceView(TemplateView):
|
|||||||
amount_error = True
|
amount_error = True
|
||||||
context["amount"] = round(amount, 2)
|
context["amount"] = round(amount, 2)
|
||||||
context["amount_error"] = amount_error
|
context["amount_error"] = amount_error
|
||||||
context["future_transactions"] = predict_balance(amount)
|
context["future_transactions"] = predict_balance(subjects, amount)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -28,5 +29,6 @@ class SubjectsView(TemplateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["prediction_list"] = predict_all(Subject.objects.order_by("name"))
|
subjects = Subject.objects.filter(user=self.request.user).order_by("name")
|
||||||
|
context["prediction_list"] = predict_all(subjects)
|
||||||
return context
|
return context
|
||||||
|
|||||||
Reference in New Issue
Block a user