From 0447f9f6efc7f080b057fdfc70e4ecb9195ad13d Mon Sep 17 00:00:00 2001 From: Florian Hartmann Date: Mon, 18 Jan 2021 23:35:10 +0100 Subject: [PATCH] seperate subjects/transaction on a user basis --- core/admin.py | 24 +++++++- core/migrations/0002_auto_20210118_2334.py | 33 ++++++++++ core/models.py | 20 +++---- core/prediction.py | 4 +- core/tests/test_prediction.py | 70 +++++++++++----------- core/tests/test_price_field.py | 47 --------------- core/views.py | 6 +- 7 files changed, 107 insertions(+), 97 deletions(-) create mode 100644 core/migrations/0002_auto_20210118_2334.py delete mode 100644 core/tests/test_price_field.py diff --git a/core/admin.py b/core/admin.py index 8f8a336..75c1130 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,21 +1,43 @@ 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 +User = get_user_model() + class CustomAdminSite(admin.AdminSite): pass admin_site = CustomAdminSite() +admin_site.register(User, UserAdmin) @admin.register(Subject, site=admin_site) 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) class TransactionAdmin(admin.ModelAdmin): list_display = ("subject", "amount", "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 diff --git a/core/migrations/0002_auto_20210118_2334.py b/core/migrations/0002_auto_20210118_2334.py new file mode 100644 index 0000000..49d19c7 --- /dev/null +++ b/core/migrations/0002_auto_20210118_2334.py @@ -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', + ), + ] diff --git a/core/models.py b/core/models.py index f33e775..c3bcbf4 100644 --- a/core/models.py +++ b/core/models.py @@ -1,7 +1,9 @@ from decimal import Decimal +from django.contrib.auth import get_user_model from django.db import models -from django.utils import timezone + +User = get_user_model() class PriceField(models.DecimalField): @@ -22,8 +24,14 @@ class PriceField(models.DecimalField): 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) - created_time = models.DateTimeField(default=timezone.now) def __str__(self): return self.name @@ -39,11 +47,3 @@ class Transaction(models.Model): to=Subject, on_delete=models.CASCADE, related_name="transactions", related_query_name="transaction", ) - - -class Balance(models.Model): - class Meta: - get_latest_by = "date" - - amount = PriceField() - date = models.DateField() diff --git a/core/prediction.py b/core/prediction.py index 635519d..1fb47ca 100644 --- a/core/prediction.py +++ b/core/prediction.py @@ -150,8 +150,8 @@ def get_color_for_amount(amount: Decimal, minimum: Decimal, maximum: Decimal): ) -def predict_balance(start_balance=Decimal("0")): - prediction_list = predict_all(Subject.objects.order_by("name")) +def predict_balance(subjects: Iterable[Subject], start_balance=Decimal("0")): + prediction_list = predict_all(subjects) today = timezone.now().date() future_transactions = [] minimum, maximum = Decimal(), Decimal() diff --git a/core/tests/test_prediction.py b/core/tests/test_prediction.py index 4f914c4..064040c 100644 --- a/core/tests/test_prediction.py +++ b/core/tests/test_prediction.py @@ -1,32 +1,37 @@ from datetime import date +from django.contrib.auth import get_user_model from django.test import TestCase from core.models import Subject, Transaction from core.prediction import predict_transactions +User = get_user_model() + 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): - subject = Subject.objects.create(name="rent") - predicted_transactions, prediction_info = predict_transactions(subject) + predicted_transactions, prediction_info = predict_transactions(self.subject) self.assertEqual(predicted_transactions, []) self.assertIsNone(prediction_info) - Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=subject) - predicted_transactions, prediction_info = predict_transactions(subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=self.subject) + predicted_transactions, prediction_info = predict_transactions(self.subject) self.assertEqual(predicted_transactions, []) self.assertIsNone(prediction_info) - Transaction.objects.create(amount=-333, booking_date=date(2020, 7, 2), subject=subject) - predicted_transactions, prediction_info = predict_transactions(subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 7, 2), subject=self.subject) + predicted_transactions, prediction_info = predict_transactions(self.subject) self.assertNotEqual(predicted_transactions, []) self.assertIsNotNone(prediction_info) def test_every_month(self): - subject = Subject.objects.create(name="rent") - 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=subject) - predicted_transactions, prediction_info = predict_transactions(subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=self.subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 7, 2), subject=self.subject) + predicted_transactions, prediction_info = predict_transactions(self.subject) first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1] self.assertEqual(first.booking_date, date(2020, 8, 2)) self.assertEqual(second.booking_date, date(2020, 9, 2)) @@ -39,10 +44,9 @@ class PredictionTestCase(TestCase): ) def test_every_3_months(self): - subject = Subject.objects.create(name="rent") - 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=subject) - predicted_transactions, prediction_info = predict_transactions(subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=self.subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 9, 2), subject=self.subject) + predicted_transactions, prediction_info = predict_transactions(self.subject) first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1] self.assertEqual(first.booking_date, date(2020, 12, 2)) self.assertEqual(second.booking_date, date(2021, 3, 2)) @@ -55,10 +59,9 @@ class PredictionTestCase(TestCase): ) def test_every_year(self): - subject = Subject.objects.create(name="rent") - 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=subject) - predicted_transactions, prediction_info = predict_transactions(subject) + Transaction.objects.create(amount=-333, booking_date=date(2019, 6, 2), subject=self.subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 6, 2), subject=self.subject) + predicted_transactions, prediction_info = predict_transactions(self.subject) self.assertEqual(len(predicted_transactions), 1) trans = predicted_transactions[0] self.assertEqual(trans.booking_date, date(2021, 6, 2)) @@ -69,12 +72,11 @@ class PredictionTestCase(TestCase): ) 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=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=subject) - Transaction.objects.create(amount=-333, booking_date=date(2020, 4, 30), subject=subject) - predicted_transactions, prediction_info = predict_transactions(subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 1, 31), subject=self.subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 3, 1), subject=self.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, 30), subject=self.subject) + predicted_transactions, prediction_info = predict_transactions(self.subject) first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1] self.assertEqual(first.booking_date, date(2020, 5, 30)) self.assertEqual(second.booking_date, date(2020, 6, 30)) @@ -87,12 +89,11 @@ class PredictionTestCase(TestCase): ) def test_no_monthly_pattern(self): - subject = Subject.objects.create(name="rent") - 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=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=subject) - predicted_transactions, prediction_info = predict_transactions(subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 1, 15), subject=self.subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 2, 10), subject=self.subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 4, 25), subject=self.subject) + Transaction.objects.create(amount=-333, booking_date=date(2020, 5, 5), subject=self.subject) + predicted_transactions, prediction_info = predict_transactions(self.subject) first, second, last = predicted_transactions[0], predicted_transactions[1], predicted_transactions[-1] self.assertEqual(first.booking_date, date(2020, 6, 11)) self.assertEqual(second.booking_date, date(2020, 7, 18)) @@ -105,10 +106,9 @@ class PredictionTestCase(TestCase): ) def test_amount_change(self): - subject = Subject.objects.create(name="rent") - 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=subject) - Transaction.objects.create(amount=404, booking_date=date(2020, 8, 2), subject=subject) - predicted_transactions, prediction_info = predict_transactions(subject) + Transaction.objects.create(amount=1337, booking_date=date(2020, 6, 2), subject=self.subject) + Transaction.objects.create(amount=1312, booking_date=date(2020, 7, 2), subject=self.subject) + Transaction.objects.create(amount=404, booking_date=date(2020, 8, 2), subject=self.subject) + predicted_transactions, prediction_info = predict_transactions(self.subject) for tr in predicted_transactions: self.assertEqual(tr.amount, 404) diff --git a/core/tests/test_price_field.py b/core/tests/test_price_field.py deleted file mode 100644 index fd401d0..0000000 --- a/core/tests/test_price_field.py +++ /dev/null @@ -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")) diff --git a/core/views.py b/core/views.py index 2536eb3..3674bb2 100644 --- a/core/views.py +++ b/core/views.py @@ -11,6 +11,7 @@ class BalanceView(TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + subjects = Subject.objects.filter(user=self.request.user).order_by("name") amount, amount_error = Decimal(), False if amount_query := self.request.GET.get("amount"): try: @@ -19,7 +20,7 @@ class BalanceView(TemplateView): amount_error = True context["amount"] = round(amount, 2) context["amount_error"] = amount_error - context["future_transactions"] = predict_balance(amount) + context["future_transactions"] = predict_balance(subjects, amount) return context @@ -28,5 +29,6 @@ class SubjectsView(TemplateView): def get_context_data(self, **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