From ee7e6e60a788ab37fbad3b085da7d87c854c5d78 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 9 Apr 2023 08:58:03 +0200 Subject: [PATCH 001/325] Update page titles --- nummi/main/templates/main/account_form.html | 1 + nummi/main/templates/main/category_form.html | 1 + nummi/main/templates/main/invoice_form.html | 1 + nummi/main/templates/main/list/snapshot.html | 5 +++++ nummi/main/templates/main/list/transaction.html | 8 ++++++++ nummi/main/templates/main/search.html | 3 +++ nummi/main/templates/main/snapshot_form.html | 1 + nummi/main/templates/main/transaction_form.html | 1 + 8 files changed, 21 insertions(+) diff --git a/nummi/main/templates/main/account_form.html b/nummi/main/templates/main/account_form.html index 292c2b0..cd424f9 100644 --- a/nummi/main/templates/main/account_form.html +++ b/nummi/main/templates/main/account_form.html @@ -2,6 +2,7 @@ {% load static %} {% load main_extras %} {% load i18n %} +{% block title %}{{ form.instance }} – Nummi{% endblock %} {% block link %} {{ block.super }} Date: Fri, 14 Apr 2023 15:33:43 +0200 Subject: [PATCH 002/325] All transactions endpoint --- nummi/api/__init__.py | 0 nummi/api/admin.py | 3 +++ nummi/api/apps.py | 6 ++++++ nummi/api/migrations/__init__.py | 0 nummi/api/models.py | 3 +++ nummi/api/tests.py | 3 +++ nummi/api/urls.py | 7 +++++++ nummi/api/views.py | 13 +++++++++++++ nummi/nummi/urls.py | 1 + 9 files changed, 36 insertions(+) create mode 100644 nummi/api/__init__.py create mode 100644 nummi/api/admin.py create mode 100644 nummi/api/apps.py create mode 100644 nummi/api/migrations/__init__.py create mode 100644 nummi/api/models.py create mode 100644 nummi/api/tests.py create mode 100644 nummi/api/urls.py create mode 100644 nummi/api/views.py diff --git a/nummi/api/__init__.py b/nummi/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/api/admin.py b/nummi/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/nummi/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/nummi/api/apps.py b/nummi/api/apps.py new file mode 100644 index 0000000..878e7d5 --- /dev/null +++ b/nummi/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "api" diff --git a/nummi/api/migrations/__init__.py b/nummi/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/api/models.py b/nummi/api/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/nummi/api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/nummi/api/tests.py b/nummi/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/nummi/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/nummi/api/urls.py b/nummi/api/urls.py new file mode 100644 index 0000000..858013a --- /dev/null +++ b/nummi/api/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("transactions", views.TransactionListView.as_view(), name="index"), +] diff --git a/nummi/api/views.py b/nummi/api/views.py new file mode 100644 index 0000000..ca7e5ad --- /dev/null +++ b/nummi/api/views.py @@ -0,0 +1,13 @@ +from django.http import JsonResponse +from django.views import View +from django.views.generic.list import MultipleObjectMixin + +from main.models import Transaction +from main.views import UserMixin + + +class TransactionListView(UserMixin, MultipleObjectMixin, View): + model = Transaction + + def get(self, request, *args, **kwargs): + return JsonResponse({"transactions": list(self.get_queryset().values())}) diff --git a/nummi/nummi/urls.py b/nummi/nummi/urls.py index 14d3517..a888c80 100644 --- a/nummi/nummi/urls.py +++ b/nummi/nummi/urls.py @@ -20,6 +20,7 @@ from django.urls import include, path urlpatterns = i18n_patterns( path("", include("main.urls")), path("plot/", include("plot.urls")), + path("api/", include("api.urls")), path("admin/", admin.site.urls), prefix_default_language=False, ) From cba2358c0b57b33705afb4ae567186bc3781e26a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 14 Apr 2023 15:42:49 +0200 Subject: [PATCH 003/325] Add snapshot, account and category endpoints --- nummi/api/urls.py | 5 ++++- nummi/api/views.py | 23 ++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/nummi/api/urls.py b/nummi/api/urls.py index 858013a..1579b84 100644 --- a/nummi/api/urls.py +++ b/nummi/api/urls.py @@ -3,5 +3,8 @@ from django.urls import path from . import views urlpatterns = [ - path("transactions", views.TransactionListView.as_view(), name="index"), + path("transactions", views.TransactionListView.as_view(), name="transactions"), + path("categories", views.CategoryListView.as_view(), name="categories"), + path("accounts", views.AccountListView.as_view(), name="accounts"), + path("snapshots", views.SnapshotListView.as_view(), name="snapshots"), ] diff --git a/nummi/api/views.py b/nummi/api/views.py index ca7e5ad..ce1749a 100644 --- a/nummi/api/views.py +++ b/nummi/api/views.py @@ -2,7 +2,7 @@ from django.http import JsonResponse from django.views import View from django.views.generic.list import MultipleObjectMixin -from main.models import Transaction +from main.models import Account, Category, Snapshot, Transaction from main.views import UserMixin @@ -11,3 +11,24 @@ class TransactionListView(UserMixin, MultipleObjectMixin, View): def get(self, request, *args, **kwargs): return JsonResponse({"transactions": list(self.get_queryset().values())}) + + +class CategoryListView(UserMixin, MultipleObjectMixin, View): + model = Category + + def get(self, request, *args, **kwargs): + return JsonResponse({"categories": list(self.get_queryset().values())}) + + +class AccountListView(UserMixin, MultipleObjectMixin, View): + model = Account + + def get(self, request, *args, **kwargs): + return JsonResponse({"accounts": list(self.get_queryset().values())}) + + +class SnapshotListView(UserMixin, MultipleObjectMixin, View): + model = Snapshot + + def get(self, request, *args, **kwargs): + return JsonResponse({"snapshots": list(self.get_queryset().values())}) From 63908cd837482971ac39eaa9e54e7b1d36e63c77 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 16 Apr 2023 15:58:31 +0200 Subject: [PATCH 004/325] Add quarterly api endpoint --- nummi/api/urls.py | 1 + nummi/api/views.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/nummi/api/urls.py b/nummi/api/urls.py index 1579b84..cb74a80 100644 --- a/nummi/api/urls.py +++ b/nummi/api/urls.py @@ -7,4 +7,5 @@ urlpatterns = [ path("categories", views.CategoryListView.as_view(), name="categories"), path("accounts", views.AccountListView.as_view(), name="accounts"), path("snapshots", views.SnapshotListView.as_view(), name="snapshots"), + path("history", views.HistoryView.as_view(), name="history"), ] diff --git a/nummi/api/views.py b/nummi/api/views.py index ce1749a..876d5fd 100644 --- a/nummi/api/views.py +++ b/nummi/api/views.py @@ -1,3 +1,5 @@ +from django.db.models import Sum +from django.db.models.functions import ExtractQuarter, ExtractYear from django.http import JsonResponse from django.views import View from django.views.generic.list import MultipleObjectMixin @@ -32,3 +34,18 @@ class SnapshotListView(UserMixin, MultipleObjectMixin, View): def get(self, request, *args, **kwargs): return JsonResponse({"snapshots": list(self.get_queryset().values())}) + + +class HistoryView(UserMixin, MultipleObjectMixin, View): + model = Transaction + + def get(self, request, *args, **kwargs): + return JsonResponse( + { + "data": list( + self.get_queryset() + .values("category__name", quarter=ExtractQuarter("date"), year=ExtractYear("date")) + .annotate(Sum("value")) + ) + } + ) From 656449db9f9b1d7e703474cff7548a9690125d29 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 17 Apr 2023 17:22:34 +0200 Subject: [PATCH 005/325] HTML/CSS only plot implemented for history on homepage --- nummi/main/static/main/css/plot.css | 79 ++++++++++++++ nummi/main/templates/main/category_form.html | 2 - nummi/main/templates/main/index.html | 5 + nummi/main/templates/main/plot/history.html | 40 +++++++ nummi/main/templates/main/snapshot_form.html | 2 +- nummi/main/templatetags/main_extras.py | 9 +- nummi/main/views.py | 26 ++++- nummi/nummi/urls.py | 1 - nummi/plot/__init__.py | 0 nummi/plot/admin.py | 1 - nummi/plot/apps.py | 6 -- nummi/plot/migrations/__init__.py | 0 nummi/plot/models.py | 1 - nummi/plot/nummi.mplstyle | 13 --- nummi/plot/tests.py | 1 - nummi/plot/urls.py | 9 -- nummi/plot/views.py | 108 ------------------- 17 files changed, 155 insertions(+), 148 deletions(-) create mode 100644 nummi/main/static/main/css/plot.css create mode 100644 nummi/main/templates/main/plot/history.html delete mode 100644 nummi/plot/__init__.py delete mode 100644 nummi/plot/admin.py delete mode 100644 nummi/plot/apps.py delete mode 100644 nummi/plot/migrations/__init__.py delete mode 100644 nummi/plot/models.py delete mode 100644 nummi/plot/nummi.mplstyle delete mode 100644 nummi/plot/tests.py delete mode 100644 nummi/plot/urls.py delete mode 100644 nummi/plot/views.py diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css new file mode 100644 index 0000000..d43bfa2 --- /dev/null +++ b/nummi/main/static/main/css/plot.css @@ -0,0 +1,79 @@ +.plot table { + border-collapse: collapse; + width: 100%; +} +.plot tr { + padding-bottom: .5rem; +} +.plot th {text-align: center} +.plot th.r {text-align: right} +.plot th.l {text-align: left} + +.plot td, .plot th, .plot td.bar div { + position: relative; + height: 2rem; + line-height: 2rem; +} +.plot td, .plot th { + padding: .5rem var(--gap); +} + +.plot td:not(.bar), .plot tbody th { + width: 8rem; +} +.plot td.bar { + position: relative; + padding: 0; +} +.plot td.bar div { + position: absolute; + top: 0; +} +.plot td.m { + text-align: right; +} +.plot td.value { + padding: 0 var(--gap); + font-feature-settings: var(--num); + text-align: right; +} + +.plot td.bar div:not(.tot) { + width: 0; + box-sizing: border-box; + z-index: 1; + display: inline-block; +} +.plot td.bar.p div { + left: 0; + border-radius: 0 var(--radius) var(--radius) 0; +} +.plot td.bar.m div { + right: 0; + border-radius: var(--radius) 0 0 var(--radius); +} +.plot td.bar.m div:not(.tot) { + background: var(--red-1); +} +.plot td.bar.p div:not(.tot) { + background: var(--green-1); +} + +.plot td.bar div.tot { + z-index: 10; + height: .5rem; + background: black; +} +.plot td.bar div.tot span { + position: absolute; + display: inline-block; + white-space: nowrap; + margin: 0 var(--gap); + font-weight: 650; + top: .5rem; + line-height: 1.5rem; + height: 1.5rem; + font-feature-settings: var(--num); +} +.plot td.bar.p div.tot span {left: 0} +.plot td.bar.m div.tot span {right: 0} diff --git a/nummi/main/templates/main/category_form.html b/nummi/main/templates/main/category_form.html index 05c2254..c7046d4 100644 --- a/nummi/main/templates/main/category_form.html +++ b/nummi/main/templates/main/category_form.html @@ -21,8 +21,6 @@ {{ form }} {% if form.instance.transactions %} - Graph representing value over time

{% translate "Transactions" %}

{% include "main/table/transaction.html" %} {% endif %} diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 503d0fe..96ca5bd 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -10,6 +10,9 @@ + {% endblock %} {% block body %} {% if accounts %} @@ -25,6 +28,8 @@ {% if transactions %}

{% translate "Transactions" %}

{% include "main/table/transaction.html" %} +

{% translate "History" %}

+ {% include "main/plot/history.html" %} {% endif %} {% if categories %}

{% translate "Categories" %}

diff --git a/nummi/main/templates/main/plot/history.html b/nummi/main/templates/main/plot/history.html new file mode 100644 index 0000000..2ca1511 --- /dev/null +++ b/nummi/main/templates/main/plot/history.html @@ -0,0 +1,40 @@ +{% load main_extras %} +{% load i18n %} +
+ + + + + + + + + + {% spaceless %} + {% for date in history.data %} + + + + + + + + {% endfor %} + {% endspaceless %} + +
{% translate "Month" %}{% translate "Expenses" %}{% translate "Income" %}
{{ date.month|date:"M y" }}{{ date.sum_m|pmrvalue }} +
+ {% if date.sum < 0 %} +
+ {{ date.sum|pmrvalue }} +
+ {% endif %} +
+
+ {% if date.sum > 0 %} +
+ {{ date.sum|pmrvalue }} +
+ {% endif %} +
{{ date.sum_p|pmrvalue }}
+
diff --git a/nummi/main/templates/main/snapshot_form.html b/nummi/main/templates/main/snapshot_form.html index bf5df46..8785ed8 100644 --- a/nummi/main/templates/main/snapshot_form.html +++ b/nummi/main/templates/main/snapshot_form.html @@ -40,7 +40,7 @@ {{ cat.category__name }} {% else %} - + {% endif %}
{{ cat.sum_m|pmvalue }}
diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 3115d01..1e0dbd3 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -6,12 +6,12 @@ register = template.Library() @register.filter -def value(val, pm=False): +def value(val, pm=False, r=2): if not val: return mark_safe("–") _prefix = "" _suffix = " €" - _val = formats.number_format(val, 2, use_l10n=True, force_grouping=True) + _val = formats.number_format(val, r, use_l10n=True, force_grouping=True) if val > 0: if pm: @@ -28,6 +28,11 @@ def pmvalue(val): return value(val, True) +@register.filter +def pmrvalue(val): + return value(val, True, r=0) + + @register.inclusion_tag("main/tag/form_buttons.html") def form_buttons(instance): return { diff --git a/nummi/main/views.py b/nummi/main/views.py index 9b078d7..6103e5d 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -41,12 +41,34 @@ class IndexView(LoginRequiredMixin, TemplateView): _max = 8 _transactions = Transaction.objects.filter(user=self.request.user) _snapshots = Snapshot.objects.filter(user=self.request.user) + _history = ( + _transactions.filter(category__budget=True) + .values(month=models.functions.TruncMonth("date")) + .annotate( + sum_p=models.Sum("value", filter=models.Q(value__gt=0)), + sum_m=models.Sum("value", filter=models.Q(value__lt=0)), + sum=models.Sum("value"), + ) + .order_by("-month") + ) res = { "accounts": Account.objects.filter(user=self.request.user), "transactions": _transactions[:_max], "categories": Category.objects.filter(user=self.request.user), "snapshots": _snapshots[:_max], + "history": { + "data": _history, + "max": max( + map( + lambda x: abs(x) if x else 0, + _history.aggregate( + max=models.Max("sum_p"), + min=models.Min("sum_m"), + ).values(), + ) + ), + }, } if _transactions.count() > _max: res["transactions_url"] = reverse_lazy("transactions") @@ -233,9 +255,7 @@ class SnapshotUpdateView(NummiUpdateView): ) if _transactions: data["categories"] = ( - _transactions.values( - "category", "category__name", "category__icon" - ) + _transactions.values("category", "category__name", "category__icon") .annotate( sum=models.Sum("value"), sum_m=models.Sum("value", filter=models.Q(value__lt=0)), diff --git a/nummi/nummi/urls.py b/nummi/nummi/urls.py index 14d3517..683dc20 100644 --- a/nummi/nummi/urls.py +++ b/nummi/nummi/urls.py @@ -19,7 +19,6 @@ from django.urls import include, path urlpatterns = i18n_patterns( path("", include("main.urls")), - path("plot/", include("plot.urls")), path("admin/", admin.site.urls), prefix_default_language=False, ) diff --git a/nummi/plot/__init__.py b/nummi/plot/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nummi/plot/admin.py b/nummi/plot/admin.py deleted file mode 100644 index 846f6b4..0000000 --- a/nummi/plot/admin.py +++ /dev/null @@ -1 +0,0 @@ -# Register your models here. diff --git a/nummi/plot/apps.py b/nummi/plot/apps.py deleted file mode 100644 index 1f7a601..0000000 --- a/nummi/plot/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class PlotConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "plot" diff --git a/nummi/plot/migrations/__init__.py b/nummi/plot/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nummi/plot/models.py b/nummi/plot/models.py deleted file mode 100644 index 6b20219..0000000 --- a/nummi/plot/models.py +++ /dev/null @@ -1 +0,0 @@ -# Create your models here. diff --git a/nummi/plot/nummi.mplstyle b/nummi/plot/nummi.mplstyle deleted file mode 100644 index b49262b..0000000 --- a/nummi/plot/nummi.mplstyle +++ /dev/null @@ -1,13 +0,0 @@ -font.family: Inter - -lines.linewidth: 2 - -figure.autolayout: True -figure.figsize: 8, 4 -figure.dpi: 300 - -axes.prop_cycle: cycler('color', ["66cc66", "338033", "99ff99", "802653", "cc6699"]) -axes.axisbelow: True -axes.grid: True - -svg.fonttype: none diff --git a/nummi/plot/tests.py b/nummi/plot/tests.py deleted file mode 100644 index a39b155..0000000 --- a/nummi/plot/tests.py +++ /dev/null @@ -1 +0,0 @@ -# Create your tests here. diff --git a/nummi/plot/urls.py b/nummi/plot/urls.py deleted file mode 100644 index 1412ba8..0000000 --- a/nummi/plot/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.urls import path - -from . import views - -urlpatterns = [ - path("timeline", views.timeline, name="plot-timeline"), - path("categories", views.categories, name="plot-categories"), - path("category/", views.category, name="plot-category"), -] diff --git a/nummi/plot/views.py b/nummi/plot/views.py deleted file mode 100644 index 46ebfa5..0000000 --- a/nummi/plot/views.py +++ /dev/null @@ -1,108 +0,0 @@ -import io - -import matplotlib -import matplotlib.pyplot as plt -from django.contrib.auth.decorators import login_required -from django.db import models -from django.http import HttpResponse -from django.shortcuts import get_object_or_404 -from django.utils.translation import gettext as _ -from matplotlib import dates as mdates - -from main.models import Category, Snapshot, Transaction - -matplotlib.use("Agg") -plt.style.use("./plot/nummi.mplstyle") - - -@login_required -def timeline(request): - _snapshots = Snapshot.objects.all() - - fig, ax = plt.subplots() - ax.step( - [s.date for s in _snapshots], - [s.value for s in _snapshots], - where="post", - ) - ax.set(ylabel=_("Snapshots"), ylim=0) - ax.xaxis.set_major_formatter( - mdates.ConciseDateFormatter(ax.xaxis.get_major_locator()) - ) - ax.autoscale(True, "x", True) - - _io = io.StringIO() - - fig.savefig(_io, format="svg") - - return HttpResponse(_io.getvalue(), headers={"Content-Type": "image/svg+xml"}) - - -@login_required -def categories(request): - _categories = Category.objects.filter(budget=True) - - fig, ax = plt.subplots(figsize=(8, _categories.count() / 4)) - ax.barh( - [str(c) for c in _categories][::-1], - [ - Transaction.objects.filter(category=c).aggregate(sum=models.Sum("value"))[ - "sum" - ] - for c in _categories - ][::-1], - ) - - _io = io.StringIO() - - fig.savefig(_io, format="svg") - - return HttpResponse(_io.getvalue(), headers={"Content-Type": "image/svg+xml"}) - - -@login_required -def category(request, uuid): - _category = get_object_or_404(Category, id=uuid) - _values_p = ( - Transaction.objects.filter(category=_category) - .filter(value__gt=0) - .annotate(m=models.functions.TruncMonth("date")) - .values("m") - .annotate(sum=models.Sum("value")) - .order_by("m") - ) - _values_m = ( - Transaction.objects.filter(category=_category) - .filter(value__lt=0) - .annotate(m=models.functions.TruncMonth("date")) - .values("m") - .annotate(sum=models.Sum("value")) - .order_by("m") - ) - - fig, ax = plt.subplots() - ax.bar( - [v["m"] for v in _values_p], - [v["sum"] for v in _values_p], - width=12, - color="#66cc66", - ) - ax.bar( - [v["m"] for v in _values_m], - [v["sum"] for v in _values_m], - width=12, - color="#cc6699", - ) - ax.xaxis.set_major_formatter( - mdates.ConciseDateFormatter(ax.xaxis.get_major_locator()) - ) - ax.autoscale(True, "x", True) - _ym, _yp = ax.get_ylim() - ax.set(ylim=(min(_ym, 0), max(_yp, 0))) - ax.set(ylabel=f"{_category.name} (€)") - - _io = io.StringIO() - - fig.savefig(_io, format="svg") - - return HttpResponse(_io.getvalue(), headers={"Content-Type": "image/svg+xml"}) From 65df9aae1844c0cf16a0ebf9985ba152f84e61bc Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 17 Apr 2023 18:16:50 +0200 Subject: [PATCH 006/325] Update translations for history --- nummi/main/locale/fr/LC_MESSAGES/django.mo | Bin 2644 -> 2759 bytes nummi/main/locale/fr/LC_MESSAGES/django.po | 109 ++++++++++++--------- 2 files changed, 65 insertions(+), 44 deletions(-) diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.mo b/nummi/main/locale/fr/LC_MESSAGES/django.mo index 353c059b89ed8460508685dcc96c8b71e3b39331..fb3fa083137992f87e9ba2f975d4c4a838998a0d 100644 GIT binary patch delta 1275 zcmX}sJ4{ni9LMoPy{)D4P%K5I1uKFA0zS|>2tk7nny?r&F@eSAn%3y8QXsHUopfl7 zriu#=PDYIegK?3V7zRxoTo_y=EN(2uMTvvoUwh&S|NA-T-19!Stb{++=U3~TM~2i( zM2V#eW6t8}CLW||$Cx^t!Od8}AU?#c_yqg#H3o3q`U^vpH&E^U{&N2sq{>9f<-BPm zqmC`O1v^k3kD)rA#7a!s@(5~xG1P)C+wxV^I9Xdih3Y?R>+jh5d&r-e+Ik!|)4sD2Iwja!9USOcoQ$=X)Q`P0^PQ=uIv zP)9$ATEGbEJG_MY*v9O84|OH8xD)5F2^Ub~eMOD;4YlAOsD=DQE#NPzofG2xYsggd zRue=~4Z2Vbj-m$WwMt7T{h1Nn>f)$=tS(l6LRH`x3QEn#f0-c zetP*d^>Ig`2Onsb@lM0R|}45hN38S+xu>uxdTkNbzS-bA|C=l`>KB5=jmIk^1% tf3H+J
onVXX?eV5;Qt~u{cx!z>1xDf1dhO?R6;%YEaQH+L;{{w2DZ%qII delta 1152 zcmYk*J7`l;9LMp0(j@jqtr`7gWdZsE)CuS%7Jz%Cc@AHDDX+xlYtLMXxWRoj z*24{Lw$JUywUiHG4o~1=K1=BJ)69}vdxDzq9G4CF8js*-RC^b*ReLXLf_4fUdFFV7-Bd&osAe1_WUx2WT^h&nS%UjMC` z{nyqfm`)yBu?bdLKf)VNkHE*wb`(sr97w$XMVgDGg z|9ZhMZ%8q^R+Mq`sBhMe>aZXA5{>&!*h#J-6`Fveoz$-GCKX!HzsPc<1#8m!SLyuO zR&o!izhIFpklRS+)Y!I-xPw&0$!>D93WQd>lk8IAztE%=+Esmxxa))Y>ZqZ{c1e*n!?TR{K- diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.po b/nummi/main/locale/fr/LC_MESSAGES/django.po index cfbfde1..75152a7 100644 --- a/nummi/main/locale/fr/LC_MESSAGES/django.po +++ b/nummi/main/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-01 10:02+0100\n" +"POT-Creation-Date: 2023-04-17 18:15+0200\n" "PO-Revision-Date: 2022-12-21 17:30+0100\n" "Last-Translator: edpibu \n" "Language-Team: edpibu \n" @@ -21,20 +21,28 @@ msgstr "" msgid "Add transactions" msgstr "Ajouter des transactions" +#: .\main\forms.py:97 .\main\templates\main\base.html:59 +#: .\main\templates\main\form\search.html:5 +#: .\main\templates\main\list\transaction.html:9 +#: .\main\templates\main\list\transaction.html:26 +#: .\main\templates\main\search.html:6 .\main\templates\main\search.html:15 +msgid "Search" +msgstr "Rechercher" + #: .\main\models.py:17 msgid "User" msgstr "Utilisateur" #: .\main\models.py:45 .\main\models.py:79 .\main\models.py:87 #: .\main\models.py:237 .\main\templates\main\base.html:39 -#: .\main\templates\main\table\snapshot.html:7 -#: .\main\templates\main\table\transaction.html:11 +#: .\main\templates\main\table\snapshot.html:10 +#: .\main\templates\main\table\transaction.html:15 msgid "Account" msgstr "Compte" #: .\main\models.py:45 .\main\models.py:97 .\main\models.py:208 -#: .\main\models.py:278 .\main\templates\main\table\transaction.html:7 -#: .\main\templates\main\transaction_form.html:27 +#: .\main\models.py:278 .\main\templates\main\table\transaction.html:8 +#: .\main\templates\main\transaction_form.html:28 msgid "Name" msgstr "Nom" @@ -46,13 +54,13 @@ msgstr "Icône" msgid "Default" msgstr "Défaut" -#: .\main\models.py:80 .\main\templates\main\index.html:16 +#: .\main\models.py:80 .\main\templates\main\index.html:19 msgid "Accounts" msgstr "Comptes" #: .\main\models.py:97 .\main\models.py:121 .\main\models.py:227 #: .\main\templates\main\base.html:49 -#: .\main\templates\main\table\transaction.html:10 +#: .\main\templates\main\table\transaction.html:12 msgid "Category" msgstr "Catégorie" @@ -60,8 +68,8 @@ msgstr "Catégorie" msgid "Budget" msgstr "Budget" -#: .\main\models.py:122 .\main\templates\main\index.html:30 -#: .\main\templates\main\snapshot_form.html:34 +#: .\main\models.py:122 .\main\templates\main\index.html:35 +#: .\main\templates\main\snapshot_form.html:35 msgid "Categories" msgstr "Catégories" @@ -81,7 +89,7 @@ msgstr "Valeur de fin" msgid "Start value" msgstr "Valeur de début" -#: .\main\models.py:141 .\main\templates\main\table\snapshot.html:9 +#: .\main\models.py:141 .\main\templates\main\table\snapshot.html:13 msgid "Difference" msgstr "Différence" @@ -102,26 +110,27 @@ msgstr "Relevé du %(date)s" msgid "Statement" msgstr "Relevé" -#: .\main\models.py:202 .\main\templates\main\account_form.html:23 -#: .\main\templates\main\list\snapshot.html:15 +#: .\main\models.py:202 .\main\templates\main\account_form.html:24 +#: .\main\templates\main\list\snapshot.html:6 +#: .\main\templates\main\list\snapshot.html:20 msgid "Statements" msgstr "Relevés" -#: .\main\models.py:208 .\main\models.py:271 .\main\templates\main\base.html:44 +#: .\main\models.py:208 .\main\models.py:271 .\main\templates\main\base.html:54 msgid "Transaction" msgstr "Transaction" -#: .\main\models.py:210 .\main\templates\main\table\transaction.html:12 +#: .\main\models.py:210 .\main\templates\main\table\transaction.html:17 msgid "Description" msgstr "Description" -#: .\main\models.py:212 .\main\templates\main\table\snapshot.html:8 -#: .\main\templates\main\table\transaction.html:8 +#: .\main\models.py:212 .\main\templates\main\table\snapshot.html:12 +#: .\main\templates\main\table\transaction.html:9 msgid "Value" msgstr "Valeur" -#: .\main\models.py:214 .\main\templates\main\table\snapshot.html:6 -#: .\main\templates\main\table\transaction.html:6 +#: .\main\models.py:214 .\main\templates\main\table\snapshot.html:8 +#: .\main\templates\main\table\transaction.html:7 msgid "Date" msgstr "Date" @@ -129,7 +138,7 @@ msgstr "Date" msgid "Real date" msgstr "Date réelle" -#: .\main\models.py:217 .\main\templates\main\table\transaction.html:9 +#: .\main\models.py:217 .\main\templates\main\table\transaction.html:10 msgid "Trader" msgstr "Commerçant" @@ -137,12 +146,13 @@ msgstr "Commerçant" msgid "Payment" msgstr "Paiement" -#: .\main\models.py:272 .\main\templates\main\account_form.html:27 -#: .\main\templates\main\category_form.html:25 -#: .\main\templates\main\index.html:26 -#: .\main\templates\main\list\transaction.html:15 -#: .\main\templates\main\snapshot_form.html:75 -#: .\main\templates\main\table\snapshot.html:10 +#: .\main\models.py:272 .\main\templates\main\account_form.html:28 +#: .\main\templates\main\category_form.html:24 +#: .\main\templates\main\index.html:29 +#: .\main\templates\main\list\transaction.html:6 +#: .\main\templates\main\list\transaction.html:23 +#: .\main\templates\main\snapshot_form.html:76 +#: .\main\templates\main\table\snapshot.html:14 msgid "Transactions" msgstr "Transactions" @@ -150,19 +160,15 @@ msgstr "Transactions" msgid "Invoice" msgstr "Facture" -#: .\main\models.py:318 .\main\templates\main\transaction_form.html:23 +#: .\main\models.py:318 .\main\templates\main\transaction_form.html:24 msgid "Invoices" msgstr "Factures" -#: .\main\templates\main\base.html:54 +#: .\main\templates\main\base.html:44 msgid "Snapshot" msgstr "Relevé" -#: .\main\templates\main\base.html:60 .\main\templates\main\base.html:62 -msgid "Search" -msgstr "Rechercher" - -#: .\main\templates\main\base.html:64 +#: .\main\templates\main\base.html:61 msgid "Log out" msgstr "Se déconnecter" @@ -181,7 +187,7 @@ msgstr "Confirmer" #: .\main\templates\main\form\base.html:17 #: .\main\templates\main\tag\form_buttons.html:4 -#: .\main\templates\main\transaction_form.html:34 +#: .\main\templates\main\transaction_form.html:35 msgid "Delete" msgstr "Supprimer" @@ -190,15 +196,19 @@ msgstr "Supprimer" msgid "Save" msgstr "Enregistrer" -#: .\main\templates\main\index.html:40 +#: .\main\templates\main\index.html:31 +msgid "History" +msgstr "Historique" + +#: .\main\templates\main\index.html:45 msgid "Snapshots" msgstr "Relevés" -#: .\main\templates\main\list\snapshot.html:35 +#: .\main\templates\main\list\snapshot.html:40 msgid "No snapshots to show" msgstr "Aucun relevé à afficher" -#: .\main\templates\main\list\transaction.html:35 +#: .\main\templates\main\list\transaction.html:46 msgid "No transactions to show" msgstr "Aucune transaction à afficher" @@ -206,26 +216,37 @@ msgstr "Aucune transaction à afficher" msgid "Log In" msgstr "Se connecter" -#: .\main\templates\main\table\snapshot.html:11 -msgid "Valid" -msgstr "Valide" +#: .\main\templates\main\plot\history.html:7 +msgid "Month" +msgstr "Mois" -#: .\main\templates\main\table\snapshot.html:15 +#: .\main\templates\main\plot\history.html:8 +msgid "Expenses" +msgstr "Dépenses" + +#: .\main\templates\main\plot\history.html:9 +msgid "Income" +msgstr "Revenus" + +#: .\main\templates\main\table\snapshot.html:18 msgid "New statement" msgstr "Nouveau relevé" -#: .\main\templates\main\table\snapshot.html:50 +#: .\main\templates\main\table\snapshot.html:53 msgid "View all statements" msgstr "Voir tous les relevés" -#: .\main\templates\main\table\transaction.html:16 +#: .\main\templates\main\table\transaction.html:21 msgid "New transaction" msgstr "Ajouter une transaction" -#: .\main\templates\main\table\transaction.html:55 +#: .\main\templates\main\table\transaction.html:64 msgid "View all transactions" msgstr "Voir toutes les transactions" -#: .\main\templates\main\transaction_form.html:39 +#: .\main\templates\main\transaction_form.html:40 msgid "New invoice" msgstr "Nouvelle facture" + +#~ msgid "Valid" +#~ msgstr "Valide" From 93a800bf79df648cddb50f228821b33c128d74e5 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 17 Apr 2023 18:22:16 +0200 Subject: [PATCH 007/325] Update dates --- nummi/main/static/main/css/main.css | 2 +- nummi/main/static/main/css/plot.css | 4 +++- nummi/main/templates/main/plot/history.html | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index ab488e9..671d522 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -36,7 +36,7 @@ --border: .5em; --radius: .25em; - --num: "tnum", "ss01", "case"; + --num: "tnum", "ss01", "ss02", "case"; } body { diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index d43bfa2..6754896 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -32,8 +32,10 @@ .plot td.m { text-align: right; } +.plot tbody th { + font-feature-settings: var(--num); +} .plot td.value { - padding: 0 var(--gap); font-feature-settings: var(--num); text-align: right; } diff --git a/nummi/main/templates/main/plot/history.html b/nummi/main/templates/main/plot/history.html index 2ca1511..6670b59 100644 --- a/nummi/main/templates/main/plot/history.html +++ b/nummi/main/templates/main/plot/history.html @@ -13,7 +13,7 @@ {% spaceless %} {% for date in history.data %} - {{ date.month|date:"M y" }} + {{ date.month|date:"Y-m" }} {{ date.sum_m|pmrvalue }}
From 8cf48cfdf57c451b47fbd2e5b90c2ebf0c7919dc Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 18 Apr 2023 09:31:11 +0200 Subject: [PATCH 008/325] Update chart on snapshot page --- nummi/main/static/main/css/chart.css | 59 -------------------- nummi/main/static/main/css/plot.css | 12 ++-- nummi/main/templates/main/plot/category.html | 58 +++++++++++++++++++ nummi/main/templates/main/plot/history.html | 6 ++ nummi/main/templates/main/snapshot_form.html | 41 +------------- nummi/main/views.py | 30 ++++------ 6 files changed, 85 insertions(+), 121 deletions(-) delete mode 100644 nummi/main/static/main/css/chart.css create mode 100644 nummi/main/templates/main/plot/category.html diff --git a/nummi/main/static/main/css/chart.css b/nummi/main/static/main/css/chart.css deleted file mode 100644 index fdf6ec2..0000000 --- a/nummi/main/static/main/css/chart.css +++ /dev/null @@ -1,59 +0,0 @@ -.chart { - display: grid; - grid-template-columns: auto auto 1fr 1fr auto; - grid-gap: var(--gap) 0; -} - -.chart > div { - position: relative; - height: 2rem; - line-height: 2rem; -} - -.chart .left { - text-align: right; -} -.chart .bar, -.chart .value { - display: inline-block; - height: 2rem; - line-height: 2rem; -} -.chart .value { - padding: 0 var(--gap); - font-feature-settings: var(--num); - text-align: right; -} -.chart .bar { - width: 0; - box-sizing: border-box; - z-index: 1; -} -.chart .bar.tot { - position: absolute; - z-index: 10; - height: .5rem; - background: black; -} - -.chart .left .bar.tot {right: 0} -.chart .right .bar.tot {left: 0} -.chart .left .bar {border-radius: var(--radius) 0 0 var(--radius)} -.chart .right .bar {border-radius: 0 var(--radius) var(--radius) 0} - -.chart .bar_m {background: var(--red-1)} -.chart .bar_p {background: var(--green-1)} - -.chart .bar span { - position: absolute; - display: inline-block; - white-space: nowrap; - margin: 0 var(--gap); - font-weight: 650; - top: .5rem; - line-height: 1.5rem; - height: 1.5rem; - font-feature-settings: var(--num); -} -.chart .right .bar span {left: 0} -.chart .left .bar span {right: 0} diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 6754896..034ea56 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -1,33 +1,35 @@ .plot table { border-collapse: collapse; width: 100%; + table-layout: auto; } +.plot col.desc, .plot col.value {width: 8rem} +.plot col.icon {width: 1ch} .plot tr { padding-bottom: .5rem; } -.plot th {text-align: center} +.plot th {text-align: left} .plot th.r {text-align: right} .plot th.l {text-align: left} +.plot td.c {text-align: center} .plot td, .plot th, .plot td.bar div { position: relative; height: 2rem; line-height: 2rem; + white-space: nowrap; } .plot td, .plot th { padding: .5rem var(--gap); } -.plot td:not(.bar), .plot tbody th { - width: 8rem; -} .plot td.bar { position: relative; padding: 0; } .plot td.bar div { position: absolute; - top: 0; + top: .5rem; } .plot td.m { text-align: right; diff --git a/nummi/main/templates/main/plot/category.html b/nummi/main/templates/main/plot/category.html new file mode 100644 index 0000000..0ad4584 --- /dev/null +++ b/nummi/main/templates/main/plot/category.html @@ -0,0 +1,58 @@ +{% load main_extras %} +{% load i18n %} +
+ + + + + + + + + + + + + + + + + {% spaceless %} + {% for cat in categories.data %} + + + + + + + + + {% endfor %} + {% endspaceless %} + +
{% translate "Category" %}{% translate "Expenses" %}{% translate "Income" %}
+ {% if cat.category %}{{ cat.category__name }}{% endif %} + + {% if cat.category %} + + {% else %} + + {% endif %} + {{ cat.sum_m|pmrvalue }} +
+ {% if cat.sum < 0 %} +
+ {{ cat.sum|pmrvalue }} +
+ {% endif %} +
+
+ {% if cat.sum > 0 %} +
+ {{ cat.sum|pmrvalue }} +
+ {% endif %} +
{{ cat.sum_p|pmrvalue }}
+
diff --git a/nummi/main/templates/main/plot/history.html b/nummi/main/templates/main/plot/history.html index 6670b59..60157af 100644 --- a/nummi/main/templates/main/plot/history.html +++ b/nummi/main/templates/main/plot/history.html @@ -2,6 +2,12 @@ {% load i18n %}
+ + + + + + diff --git a/nummi/main/templates/main/snapshot_form.html b/nummi/main/templates/main/snapshot_form.html index 8785ed8..3ecfb5b 100644 --- a/nummi/main/templates/main/snapshot_form.html +++ b/nummi/main/templates/main/snapshot_form.html @@ -12,7 +12,7 @@ href="{% static 'main/css/table.css' %}" type="text/css"/> {% endblock %} {% block body %} @@ -33,44 +33,7 @@ {% if categories %}

{% translate "Categories" %}

-
- {% for cat in categories %} -
- {% if cat.category %} - - {{ cat.category__name }} - {% else %} - - {% endif %} -
-
{{ cat.sum_m|pmvalue }}
-
-
- {% if cat.sum < 0 %} -
- {{ cat.sum|pmvalue }} -
- {% endif %} -
-
-
- {% if cat.sum >= 0 %} -
- {{ cat.sum|pmvalue }} -
- {% endif %} -
-
{{ cat.sum_p|pmvalue }}
- {% endfor %} -
+ {% include "main/plot/category.html" %} {% endif %} {% if not snapshot.adding %}

{% translate "Transactions" %} ({{ snapshot.sum|pmvalue }} / {{ snapshot.diff|pmvalue }})

diff --git a/nummi/main/views.py b/nummi/main/views.py index 6103e5d..794c2a1 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -60,13 +60,10 @@ class IndexView(LoginRequiredMixin, TemplateView): "history": { "data": _history, "max": max( - map( - lambda x: abs(x) if x else 0, - _history.aggregate( - max=models.Max("sum_p"), - min=models.Min("sum_m"), - ).values(), - ) + _history.aggregate( + max=models.Max("sum_p"), + min=-models.Min("sum_m"), + ).values(), ), }, } @@ -254,7 +251,7 @@ class SnapshotUpdateView(NummiUpdateView): "snapshot_transactions", args=(snapshot.pk,) ) if _transactions: - data["categories"] = ( + _categories = ( _transactions.values("category", "category__name", "category__icon") .annotate( sum=models.Sum("value"), @@ -263,18 +260,15 @@ class SnapshotUpdateView(NummiUpdateView): ) .order_by("-sum") ) - data["cat_lim"] = max( - map( - lambda x: abs(x) if x else 0, - data["categories"] - .aggregate( + data["categories"] = { + "data": _categories, + "max": max( + _categories.aggregate( max=models.Max("sum_p"), min=models.Min("sum_m"), - ) - .values(), - ) - ) - data["cat_lim_m"] = -data["cat_lim"] + ).values(), + ), + } return data | { "new_transaction_url": reverse_lazy( From de9b73ed3ae7f01d814483c367bbc72ee39d136e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 18 Apr 2023 09:38:07 +0200 Subject: [PATCH 009/325] Add borders --- nummi/main/static/main/css/main.css | 1 + nummi/main/static/main/css/plot.css | 1 + 2 files changed, 2 insertions(+) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 671d522..552e652 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -11,6 +11,7 @@ --theme-2: var(--theme); --theme-3: #802653; --theme-4: #cc6699; + --gray: #dedede; --text-theme: var(--text); diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 034ea56..a3db30c 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -7,6 +7,7 @@ .plot col.icon {width: 1ch} .plot tr { padding-bottom: .5rem; + border: .1rem solid var(--gray); } .plot th {text-align: left} .plot th.r {text-align: right} From fdc9214b10cdf61361cf257499e1142dcbd6e56e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 18 Apr 2023 09:39:39 +0200 Subject: [PATCH 010/325] Add background to thead --- nummi/main/static/main/css/plot.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index a3db30c..4109c6b 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -3,6 +3,9 @@ width: 100%; table-layout: auto; } +.plot thead { + background: var(--bg-01); +} .plot col.desc, .plot col.value {width: 8rem} .plot col.icon {width: 1ch} .plot tr { From 6ca8ca649376ed15b5b78be5a41f1e871719b806 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 18 Apr 2023 10:20:45 +0200 Subject: [PATCH 011/325] Use html tables, update design --- nummi/main/static/main/css/plot.css | 42 +----- nummi/main/static/main/css/table.css | 74 +++-------- nummi/main/templates/main/plot/category.html | 4 +- nummi/main/templates/main/plot/history.html | 6 +- .../templates/main/table/transaction.html | 122 +++++++++++------- 5 files changed, 101 insertions(+), 147 deletions(-) diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 4109c6b..e956ef4 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -1,31 +1,4 @@ -.plot table { - border-collapse: collapse; - width: 100%; - table-layout: auto; -} -.plot thead { - background: var(--bg-01); -} -.plot col.desc, .plot col.value {width: 8rem} -.plot col.icon {width: 1ch} -.plot tr { - padding-bottom: .5rem; - border: .1rem solid var(--gray); -} -.plot th {text-align: left} -.plot th.r {text-align: right} -.plot th.l {text-align: left} -.plot td.c {text-align: center} - -.plot td, .plot th, .plot td.bar div { - position: relative; - height: 2rem; - line-height: 2rem; - white-space: nowrap; -} -.plot td, .plot th { - padding: .5rem var(--gap); -} +col.bar {width: auto} .plot td.bar { position: relative; @@ -33,17 +6,8 @@ } .plot td.bar div { position: absolute; - top: .5rem; -} -.plot td.m { - text-align: right; -} -.plot tbody th { - font-feature-settings: var(--num); -} -.plot td.value { - font-feature-settings: var(--num); - text-align: right; + height: .5rem; + top: 0; } .plot td.bar div:not(.tot) { diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index 86710ac..1b27340 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -1,60 +1,26 @@ -.table { - display: grid; - margin: 2em 0; - border-radius: var(--radius); - overflow: hidden; - border-bottom: var(--border) solid var(--theme); +table { + border-collapse: collapse; + width: 100%; } -.table.col2 {grid-template-columns: repeat(2, auto)} -.table.col3 {grid-template-columns: repeat(3, auto)} -.table.col4 {grid-template-columns: repeat(4, auto)} -.table.col5 {grid-template-columns: repeat(5, auto)} -.table.col6 {grid-template-columns: repeat(6, auto)} -.table.col1-1 {grid-template-columns: min-content auto} -.table.col1-1-1 {grid-template-columns: min-content auto min-content} -.table.col2-4 {grid-template-columns: repeat(2, min-content) repeat(4, auto)} -.table.col1-5 {grid-template-columns: min-content repeat(5, auto)} -.table.col2-5 {grid-template-columns: repeat(2, min-content) repeat(5, auto)} -.table.col1-6 {grid-template-columns: min-content repeat(6, auto)} -.table.col1-7 {grid-template-columns: min-content repeat(7, auto)} - -.table > div { - display: contents; +thead { + background: var(--bg-01); } - -.table > div > * { - padding: 1em; +col {width: 8rem} +col.icon {width: 1ch} +tr { + border: .1rem solid var(--gray); + height: 2rem; + line-height: 2rem; +} +td, th { + padding: 0 var(--gap); + position: relative; white-space: nowrap; -} -.table > div:nth-child(odd) > * {background: var(--bg-01)} -.table > div:nth-child(even) > * {background: var(--bg)} - -.table > div.header > * { - background: var(--theme); -} - -.table > div > .center { - text-align: center; -} -.table > div > .right { - text-align: right; -} -.table > div > .num { - font-feature-settings: var(--num); -} -.table > div > span.value { - filter: brightness(95%); - font-weight: 600; -} - -.table > div > span.text { - overflow: hidden; text-overflow: ellipsis; } -.table > div.full-line > * { - grid-column: 1 / -1; - text-align: center; -} -.table > .invoice.new > a { - grid-column: 2 / -1; +.date, .value { + font-feature-settings: var(--num); } +.l {text-align: left} +.r, .value {text-align: right} +.c, .date {text-align: center} diff --git a/nummi/main/templates/main/plot/category.html b/nummi/main/templates/main/plot/category.html index 0ad4584..8a15510 100644 --- a/nummi/main/templates/main/plot/category.html +++ b/nummi/main/templates/main/plot/category.html @@ -12,8 +12,8 @@ - - + + diff --git a/nummi/main/templates/main/plot/history.html b/nummi/main/templates/main/plot/history.html index 60157af..56da292 100644 --- a/nummi/main/templates/main/plot/history.html +++ b/nummi/main/templates/main/plot/history.html @@ -11,15 +11,15 @@ - - + + {% spaceless %} {% for date in history.data %} - + + + {% endif %} + + {% endfor %} + +
{% translate "Month" %}
{% translate "Category" %}{% translate "Expenses" %}{% translate "Income" %}{% translate "Expenses" %}{% translate "Income" %}
{% translate "Month" %}{% translate "Expenses" %}{% translate "Income" %}{% translate "Expenses" %}{% translate "Income" %}
{{ date.month|date:"Y-m" }}{{ date.month|date:"Y-m" }} {{ date.sum_m|pmrvalue }}
diff --git a/nummi/main/templates/main/table/transaction.html b/nummi/main/templates/main/table/transaction.html index 2a49595..7bb1c5b 100644 --- a/nummi/main/templates/main/table/transaction.html +++ b/nummi/main/templates/main/table/transaction.html @@ -1,21 +1,77 @@ {% load main_extras %} {% load i18n %} -
+ + + {{ trans.account }} +
{% comment %} {% else %}

{% translate "Transactions" %}

- {% include "transaction/transaction_table.html" %} + {% url "statement_transactions" statement.id as t_url %} + {% url "new_transaction" statement=statement.id as nt_url %} + {% transaction_table statement.transaction_set.all n_max=8 transactions_url=t_url new_transaction_url=nt_url %}

{% translate "Categories" %}

{% category_plot transactions budget=False statement=object %}
-{% endblock %} +{% endblock body %} diff --git a/nummi/transaction/templates/transaction/transaction_list.html b/nummi/transaction/templates/transaction/transaction_list.html index 890cec7..abd6ef5 100644 --- a/nummi/transaction/templates/transaction/transaction_list.html +++ b/nummi/transaction/templates/transaction/transaction_list.html @@ -1,12 +1,12 @@ {% extends "main/list.html" %} -{% load i18n %} +{% load i18n transaction_extras %} {% block name %} {% translate "Transactions" %} -{% endblock %} +{% endblock name %} {% block h2 %} {% translate "Transactions" %} -{% endblock %} +{% endblock h2 %} {% block table %} - {% url "new_transaction" as new_transaction_url %} - {% include "transaction/transaction_table.html" %} -{% endblock %} + {% url "new_transaction" as nt_url %} + {% transaction_table transactions new_transaction_url=nt_url %} +{% endblock table %} diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index 00f17d4..b614674 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -10,6 +10,18 @@ from ..utils import ac_url register = template.Library() +@register.inclusion_tag("transaction/transaction_table.html") +def transaction_table(transactions, **kwargs): + if (n_max := kwargs.get("n_max")) is not None: + if transactions.count() <= n_max: + del kwargs["transactions_url"] + transactions = transactions[:n_max] + + return kwargs | { + "transactions": transactions, + } + + @register.simple_tag(takes_context=True) def month_url(context, month, cls="", fmt="Y-m"): url_name, url_params = ac_url( From b4654ec44519368de299aad9d37e7d1f92f6c839 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 16:40:27 +0100 Subject: [PATCH 277/325] Add automatic icons to category select (#24) --- nummi/category/forms.py | 5 +++++ .../category/forms/widgets/category.html | 5 +++++ nummi/main/static/main/css/form.css | 6 ++++++ nummi/main/static/main/js/base.js | 12 ++++++++++++ nummi/transaction/forms.py | 15 +++++++++++++++ 5 files changed, 43 insertions(+) create mode 100644 nummi/category/templates/category/forms/widgets/category.html diff --git a/nummi/category/forms.py b/nummi/category/forms.py index b0bbf27..25f5d9e 100644 --- a/nummi/category/forms.py +++ b/nummi/category/forms.py @@ -1,3 +1,4 @@ +from django.forms.widgets import Select from main.forms import NummiForm from .models import Category @@ -11,3 +12,7 @@ class CategoryForm(NummiForm): "icon", "budget", ] + + +class CategorySelect(Select): + template_name = "category/forms/widgets/category.html" diff --git a/nummi/category/templates/category/forms/widgets/category.html b/nummi/category/templates/category/forms/widgets/category.html new file mode 100644 index 0000000..625832f --- /dev/null +++ b/nummi/category/templates/category/forms/widgets/category.html @@ -0,0 +1,5 @@ +{% load main_extras %} + + {{ "folder"|remix }} + {% include "django/forms/widgets/select.html" %} + diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index a4412a2..13502eb 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -67,6 +67,7 @@ form { border-bottom: 1px solid var(--gray); background: none; z-index: 1; + padding: 0; &:not([type="checkbox"]) { width: 100%; @@ -102,6 +103,11 @@ form { } } } + > .category-select { + display: grid; + grid-template-columns: min-content 1fr; + column-gap: 0.5rem; + } } } .buttons { diff --git a/nummi/main/static/main/js/base.js b/nummi/main/static/main/js/base.js index 6080811..54be5c3 100644 --- a/nummi/main/static/main/js/base.js +++ b/nummi/main/static/main/js/base.js @@ -18,4 +18,16 @@ for (let form of forms) { form.addEventListener("reset", (event) => { window.removeEventListener("beforeunload", beforeUnloadHandler); }); + + let categorySelect = form.querySelector(".category-select"); + if (categorySelect) { + let input = categorySelect.querySelector("select"); + let icon = categorySelect.querySelector("span"); + let icons = JSON.parse(input.dataset.icons); + + icon.className = `ri-${icons[input.value] || "folder"}-line`; + input.addEventListener("input", (event) => { + icon.className = `ri-${icons[input.value] || "folder"}-line`; + }); + } } diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index 9255ab7..45267b0 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -1,3 +1,6 @@ +import json + +from category.forms import CategorySelect from django.forms.widgets import TextInput from main.forms import NummiFileInput, NummiForm @@ -19,6 +22,9 @@ class TransactionForm(NummiForm): "payment", "description", ] + widgets = { + "category": CategorySelect(), + } meta_fieldsets = [ [ @@ -50,6 +56,15 @@ class TransactionForm(NummiForm): self.fields["payment"].widget = DatalistInput( options=get_datalist(_user, "payment") ) + self.fields["category"].widget.attrs |= { + "class": "category", + "data-icons": json.dumps( + { + str(cat.id): cat.icon + for cat in self.fields["category"].queryset.only("id", "icon") + } + ), + } if _disable_statement: self.fields["statement"].disabled = True From 224f55a8b1ea7d23859fd2603254892c4dcb2df8 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 17:30:59 +0100 Subject: [PATCH 278/325] Add icon field, closes #24 --- nummi/account/forms.py | 5 +++- nummi/category/forms.py | 5 +++- .../category/forms/widgets/category.html | 2 +- nummi/main/forms.py | 29 +++++++++++++++++++ nummi/main/static/main/css/form.css | 2 +- nummi/main/static/main/js/base.js | 29 +++++++++++++++++-- .../main}/forms/widgets/datalist.html | 0 .../templates/main/forms/widgets/icon.html | 5 ++++ nummi/main/utils.py | 9 ++++++ nummi/transaction/forms.py | 19 +----------- 10 files changed, 81 insertions(+), 24 deletions(-) rename nummi/{transaction/templates/transaction => main/templates/main}/forms/widgets/datalist.html (100%) create mode 100644 nummi/main/templates/main/forms/widgets/icon.html create mode 100644 nummi/main/utils.py diff --git a/nummi/account/forms.py b/nummi/account/forms.py index 4b66f90..cde1416 100644 --- a/nummi/account/forms.py +++ b/nummi/account/forms.py @@ -1,4 +1,4 @@ -from main.forms import NummiForm +from main.forms import IconInput, NummiForm from .models import Account @@ -12,3 +12,6 @@ class AccountForm(NummiForm): "default", "archived", ] + widgets = { + "icon": IconInput(), + } diff --git a/nummi/category/forms.py b/nummi/category/forms.py index 25f5d9e..9988c6f 100644 --- a/nummi/category/forms.py +++ b/nummi/category/forms.py @@ -1,5 +1,5 @@ from django.forms.widgets import Select -from main.forms import NummiForm +from main.forms import IconInput, NummiForm from .models import Category @@ -12,6 +12,9 @@ class CategoryForm(NummiForm): "icon", "budget", ] + widgets = { + "icon": IconInput, + } class CategorySelect(Select): diff --git a/nummi/category/templates/category/forms/widgets/category.html b/nummi/category/templates/category/forms/widgets/category.html index 625832f..fc6724b 100644 --- a/nummi/category/templates/category/forms/widgets/category.html +++ b/nummi/category/templates/category/forms/widgets/category.html @@ -1,5 +1,5 @@ {% load main_extras %} - + {{ "folder"|remix }} {% include "django/forms/widgets/select.html" %} diff --git a/nummi/main/forms.py b/nummi/main/forms.py index 82c0146..05a6ae4 100644 --- a/nummi/main/forms.py +++ b/nummi/main/forms.py @@ -1,4 +1,7 @@ from django import forms +from django.forms.widgets import TextInput + +from .utils import get_icons class NummiFileInput(forms.ClearableFileInput): @@ -19,3 +22,29 @@ class NummiForm(forms.ModelForm): yield ((self[f] for f in fieldset) for fieldset in group) else: yield ([f] for f in self) + + +class DatalistInput(TextInput): + template_name = "main/forms/widgets/datalist.html" + + def __init__(self, *args, options=[]): + self.options = options + super().__init__(*args) + + def get_context(self, *args): + context = super().get_context(*args) + name = context["widget"]["name"] + context["widget"]["attrs"]["list"] = f"{name}-list" + context["widget"]["attrs"]["autocomplete"] = "off" + context["widget"]["options"] = self.options + return context + + +class IconInput(DatalistInput): + template_name = "main/forms/widgets/icon.html" + icon_list = get_icons() + + def get_context(self, *args): + context = super().get_context(*args) + context["widget"]["options"] = self.icon_list + return context diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 13502eb..5cb1504 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -103,7 +103,7 @@ form { } } } - > .category-select { + > .ico-input { display: grid; grid-template-columns: min-content 1fr; column-gap: 0.5rem; diff --git a/nummi/main/static/main/js/base.js b/nummi/main/static/main/js/base.js index 54be5c3..f294d85 100644 --- a/nummi/main/static/main/js/base.js +++ b/nummi/main/static/main/js/base.js @@ -25,9 +25,34 @@ for (let form of forms) { let icon = categorySelect.querySelector("span"); let icons = JSON.parse(input.dataset.icons); - icon.className = `ri-${icons[input.value] || "folder"}-line`; - input.addEventListener("input", (event) => { + function setIcon(event) { icon.className = `ri-${icons[input.value] || "folder"}-line`; + } + setIcon(); + input.addEventListener("input", setIcon); + form.addEventListener("reset", (event) => { + setTimeout(setIcon, 0); + }); + } + + let iconSelect = form.querySelector(".icon-select"); + if (iconSelect) { + let input = iconSelect.querySelector("input"); + let icon = iconSelect.querySelector("span"); + let icons = Array.from(iconSelect.querySelector("datalist").options).map( + (opt) => opt.value, + ); + + function setIcon(event) { + console.log(input.value); + icon.className = `ri-${ + icons.includes(input.value) ? input.value : "square" + }-line`; + } + setIcon(); + input.addEventListener("input", setIcon); + form.addEventListener("reset", (event) => { + setTimeout(setIcon, 0); }); } } diff --git a/nummi/transaction/templates/transaction/forms/widgets/datalist.html b/nummi/main/templates/main/forms/widgets/datalist.html similarity index 100% rename from nummi/transaction/templates/transaction/forms/widgets/datalist.html rename to nummi/main/templates/main/forms/widgets/datalist.html diff --git a/nummi/main/templates/main/forms/widgets/icon.html b/nummi/main/templates/main/forms/widgets/icon.html new file mode 100644 index 0000000..39e0e3c --- /dev/null +++ b/nummi/main/templates/main/forms/widgets/icon.html @@ -0,0 +1,5 @@ +{% load main_extras %} + + {{ "square"|remix }} + {% include "main/forms/widgets/datalist.html" %} + diff --git a/nummi/main/utils.py b/nummi/main/utils.py new file mode 100644 index 0000000..2a8bb28 --- /dev/null +++ b/nummi/main/utils.py @@ -0,0 +1,9 @@ +import json +from urllib import request + + +def get_icons(): + url = "https://cdn.jsdelivr.net/npm/remixicon@4.5.0/fonts/remixicon.glyph.json" + data = json.loads(request.urlopen(url).read()) + + return [i.removesuffix("-line") for i in data.keys() if i.endswith("-line")] diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index 45267b0..5c6160e 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -1,8 +1,7 @@ import json from category.forms import CategorySelect -from django.forms.widgets import TextInput -from main.forms import NummiFileInput, NummiForm +from main.forms import DatalistInput, NummiFileInput, NummiForm from .models import Invoice, Transaction from .utils import get_datalist @@ -82,19 +81,3 @@ class InvoiceForm(NummiForm): widgets = { "file": NummiFileInput, } - - -class DatalistInput(TextInput): - template_name = "transaction/forms/widgets/datalist.html" - - def __init__(self, *args, options=[]): - self.options = options - super().__init__(*args) - - def get_context(self, *args): - context = super().get_context(*args) - name = context["widget"]["name"] - context["widget"]["attrs"]["list"] = f"{name}-list" - context["widget"]["attrs"]["autocomplete"] = "off" - context["widget"]["options"] = self.options - return context From 57b315c842cd5136abcfcff36751c9ef17dc42d1 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 17:33:25 +0100 Subject: [PATCH 279/325] Fix bug with js for archived accounts --- nummi/account/templates/account/account_list.html | 1 - nummi/main/static/main/js/base.js | 10 ++++++++++ nummi/main/static/main/js/index.js | 9 --------- nummi/main/templates/main/index.html | 1 - 4 files changed, 10 insertions(+), 11 deletions(-) delete mode 100644 nummi/main/static/main/js/index.js diff --git a/nummi/account/templates/account/account_list.html b/nummi/account/templates/account/account_list.html index 9193e05..4c3c711 100644 --- a/nummi/account/templates/account/account_list.html +++ b/nummi/account/templates/account/account_list.html @@ -4,7 +4,6 @@ {{ block.super }} {% css "main/css/table.css" %} {% css "main/css/plot.css" %} - {% js "main/js/index.js" %} {% endblock link %} {% block name %} {% translate "Account" %} diff --git a/nummi/main/static/main/js/base.js b/nummi/main/static/main/js/base.js index f294d85..e07635f 100644 --- a/nummi/main/static/main/js/base.js +++ b/nummi/main/static/main/js/base.js @@ -56,3 +56,13 @@ for (let form of forms) { }); } } + +let accounts = document.querySelector("dl.accounts"); +let toggle = accounts.querySelector("input#show-archived-accounts"); + +if (toggle) { + accounts.classList.toggle("show-archive", toggle.checked); + toggle.addEventListener("change", (event) => { + accounts.classList.toggle("show-archive", toggle.checked); + }); +} diff --git a/nummi/main/static/main/js/index.js b/nummi/main/static/main/js/index.js deleted file mode 100644 index 550651b..0000000 --- a/nummi/main/static/main/js/index.js +++ /dev/null @@ -1,9 +0,0 @@ -let accounts = document.querySelector("dl.accounts"); -let toggle = accounts.querySelector("input#show-archived-accounts"); - -accounts.classList.toggle("show-archive", toggle.checked); -if (toggle) { - toggle.addEventListener("change", (event) => { - accounts.classList.toggle("show-archive", toggle.checked); - }); -} diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index ce81ed3..a5329e3 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -6,7 +6,6 @@ {{ block.super }} {% css "main/css/table.css" %} {% css "main/css/plot.css" %} - {% js "main/js/index.js" %} {% endblock link %} {% block body %}
From 1f22ac7042c2bfd37dad64124fdbc425a30b1831 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 17:36:45 +0100 Subject: [PATCH 280/325] JS bugfix --- nummi/main/static/main/js/base.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nummi/main/static/main/js/base.js b/nummi/main/static/main/js/base.js index e07635f..d4c6ea1 100644 --- a/nummi/main/static/main/js/base.js +++ b/nummi/main/static/main/js/base.js @@ -58,11 +58,12 @@ for (let form of forms) { } let accounts = document.querySelector("dl.accounts"); -let toggle = accounts.querySelector("input#show-archived-accounts"); - -if (toggle) { - accounts.classList.toggle("show-archive", toggle.checked); - toggle.addEventListener("change", (event) => { +if (accounts) { + let toggle = accounts.querySelector("input#show-archived-accounts"); + if (toggle) { accounts.classList.toggle("show-archive", toggle.checked); - }); + toggle.addEventListener("change", (event) => { + accounts.classList.toggle("show-archive", toggle.checked); + }); + } } From c6c67b9f935bc03b7298944df73e6f7308789249 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 12:33:33 +0100 Subject: [PATCH 281/325] Fix invoices not being shown on transactions --- nummi/transaction/models.py | 4 ---- .../templates/transaction/invoice_table.html | 5 ++--- .../templates/transaction/transaction_detail.html | 10 +++++----- .../templates/transaction/transaction_table.html | 2 +- nummi/transaction/templatetags/transaction_extras.py | 8 ++++++++ 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/nummi/transaction/models.py b/nummi/transaction/models.py index 0fb8dd8..adc78c0 100644 --- a/nummi/transaction/models.py +++ b/nummi/transaction/models.py @@ -69,10 +69,6 @@ class Transaction(UserModel): if self.statement: return self.statement.account - @property - def has_invoice(self): - return self.invoices.count() > 0 - class Meta: ordering = ["-date", "statement"] verbose_name = _("Transaction") diff --git a/nummi/transaction/templates/transaction/invoice_table.html b/nummi/transaction/templates/transaction/invoice_table.html index f67b659..7b5919b 100644 --- a/nummi/transaction/templates/transaction/invoice_table.html +++ b/nummi/transaction/templates/transaction/invoice_table.html @@ -1,8 +1,7 @@ -{% load main_extras %} -{% load i18n %} +{% load i18n main_extras %}
    - {% for invoice in transaction.invoices %} + {% for invoice in invoices %}
  • {{ "file"|remix }}{{ invoice.name }} [{{ invoice.file|extension }}] {{ "file-edit"|remix }}{% translate "Edit" %} diff --git a/nummi/transaction/templates/transaction/transaction_detail.html b/nummi/transaction/templates/transaction/transaction_detail.html index a879a3e..4cfd068 100644 --- a/nummi/transaction/templates/transaction/transaction_detail.html +++ b/nummi/transaction/templates/transaction/transaction_detail.html @@ -1,16 +1,16 @@ {% extends "main/form/base.html" %} {% load i18n %} -{% load main_extras %} +{% load main_extras transaction_extras %} {% block title %} {{ transaction }} – Nummi -{% endblock %} +{% endblock title %} {% block link %} {{ block.super }} {% css "main/css/form.css" %} {% css "main/css/table.css" %} {% css "main/css/plot.css" %} -{% endblock %} +{% endblock link %} {% block body %}

    {{ transaction }}

    @@ -43,6 +43,6 @@

    {% translate "Invoices" %}

    - {% include "transaction/invoice_table.html" %} + {% invoice_table transaction %}
    -{% endblock %} +{% endblock body %} diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index 396c511..6e34f14 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -39,7 +39,7 @@ {% for trans in transactions %} - {% for invoice in trans.invoices %} + {% for invoice in trans.invoice_set.all %} {{ "attachment"|remix }} {% endfor %} diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index b614674..7817cfb 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -22,6 +22,14 @@ def transaction_table(transactions, **kwargs): } +@register.inclusion_tag("transaction/invoice_table.html") +def invoice_table(transaction, **kwargs): + return kwargs | { + "transaction": transaction, + "invoices": transaction.invoice_set.all(), + } + + @register.simple_tag(takes_context=True) def month_url(context, month, cls="", fmt="Y-m"): url_name, url_params = ac_url( From 2cba5c6d837d76ec6a5af09c72febf6c80e2c958 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 12:46:19 +0100 Subject: [PATCH 282/325] Hide archived accounts in select for new statements --- nummi/statement/forms.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nummi/statement/forms.py b/nummi/statement/forms.py index e7afbb3..dbf77df 100644 --- a/nummi/statement/forms.py +++ b/nummi/statement/forms.py @@ -1,4 +1,3 @@ -from account.models import Account from django import forms from django.utils.translation import gettext_lazy as _ from main.forms import NummiFileInput, NummiForm @@ -28,7 +27,7 @@ class StatementForm(NummiForm): _user = kwargs.get("user") _disable_account = kwargs.pop("disable_account", False) super().__init__(*args, **kwargs) - self.fields["account"].queryset = Account.objects.filter(user=_user) + self.fields["account"].queryset = _user.account_set.exclude(archived=True) self.fields["transactions"] = forms.MultipleChoiceField( choices=( ((_transaction.id), _transaction) From 26f97dd1797ba2506a1c0164d45d8198f9a602ce Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 13:05:46 +0100 Subject: [PATCH 283/325] Use only budgeted categories in history plot --- nummi/history/templatetags/history_extras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index d7cd2c1..7d278f6 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -11,7 +11,7 @@ register = template.Library() @register.inclusion_tag("history/plot.html") def history_plot(transactions, **kwargs): return kwargs | { - "history": history(transactions), + "history": history(transactions.exclude(category__budget=False)), } From a6fc7f5a921162ae5f3f20cdccbaf8c77931f8b0 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 14:25:40 +0100 Subject: [PATCH 284/325] Move statement sum and diff to properties instead of model fields Fix #32 --- ...ove_statement_diff_remove_statement_sum.py | 20 +++++++++++ nummi/statement/models.py | 36 +++++-------------- 2 files changed, 29 insertions(+), 27 deletions(-) create mode 100644 nummi/statement/migrations/0003_remove_statement_diff_remove_statement_sum.py diff --git a/nummi/statement/migrations/0003_remove_statement_diff_remove_statement_sum.py b/nummi/statement/migrations/0003_remove_statement_diff_remove_statement_sum.py new file mode 100644 index 0000000..d76d0f8 --- /dev/null +++ b/nummi/statement/migrations/0003_remove_statement_diff_remove_statement_sum.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2 on 2025-01-03 13:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("statement", "0002_alter_statement_table"), + ] + + operations = [ + migrations.RemoveField( + model_name="statement", + name="diff", + ), + migrations.RemoveField( + model_name="statement", + name="sum", + ), + ] diff --git a/nummi/statement/models.py b/nummi/statement/models.py index c5e0337..fe661a8 100644 --- a/nummi/statement/models.py +++ b/nummi/statement/models.py @@ -4,7 +4,7 @@ from uuid import uuid4 from account.models import AccountModel from django.core.validators import FileExtensionValidator -from django.db import models, transaction +from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ from media.utils import get_path @@ -22,20 +22,6 @@ class Statement(AccountModel): start_value = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name=_("Start value") ) - diff = models.DecimalField( - max_digits=12, - decimal_places=2, - default=0, - verbose_name=_("Difference"), - editable=False, - ) - sum = models.DecimalField( - max_digits=12, - decimal_places=2, - default=0, - verbose_name=_("Transaction difference"), - editable=False, - ) file = models.FileField( upload_to=get_path, validators=[FileExtensionValidator(["pdf"])], @@ -54,21 +40,17 @@ class Statement(AccountModel): if _prever.file and _prever.file != self.file: Path(_prever.file.path).unlink(missing_ok=True) - with transaction.atomic(): - for trans in self.transaction_set.all(): - trans.save() - - self.diff = self.value - self.start_value - self.sum = ( - self.transaction_set.aggregate(sum=models.Sum("value")).get("sum", 0) or 0 - ) super().save(*args, **kwargs) - def update_sum(self): - self.sum = ( - self.transaction_set.aggregate(sum=models.Sum("value")).get("sum", 0) or 0 + @property + def sum(self): + return self.transaction_set.aggregate(sum=models.Sum("value", default=0)).get( + "sum" ) - super().save() + + @property + def diff(self): + return self.value - self.start_value def delete(self, *args, **kwargs): if self.file: From c14e075cad167a10ad19389845be38dda61535b9 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 14:54:47 +0100 Subject: [PATCH 285/325] Add VS Code debug config --- .vscode/launch.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0d096fe --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Python Debugger: Django", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}\\nummi\\manage.py", + "args": [ + "runserver" + ], + "env": { + "NUMMI_CONFIG": "${workspaceFolder}\\env\\config.toml" + }, + "django": true + } + ] +} \ No newline at end of file From 5b49836bada5666ef73a5c9aa01ae07086d8411a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 14:55:16 +0100 Subject: [PATCH 286/325] Fix history plot Fix #31 --- nummi/account/templates/account/account_detail.html | 2 +- nummi/category/templates/category/category_detail.html | 2 +- nummi/history/templatetags/history_extras.py | 10 +++++++--- nummi/transaction/views.py | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/nummi/account/templates/account/account_detail.html b/nummi/account/templates/account/account_detail.html index 5b7fa17..6d7e408 100644 --- a/nummi/account/templates/account/account_detail.html +++ b/nummi/account/templates/account/account_detail.html @@ -22,6 +22,6 @@

    {% translate "History" %}

    - {% history_plot account.transactions %} + {% history_plot account.transactions account=account %}
    {% endblock body %} diff --git a/nummi/category/templates/category/category_detail.html b/nummi/category/templates/category/category_detail.html index 3c13cbb..02b9635 100644 --- a/nummi/category/templates/category/category_detail.html +++ b/nummi/category/templates/category/category_detail.html @@ -20,6 +20,6 @@

    {% translate "History" %}

    - {% history_plot category.transaction_set.all %} + {% history_plot category.transaction_set category=category %}
    {% endblock body %} diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index 7d278f6..e8f940b 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -10,9 +10,13 @@ register = template.Library() @register.inclusion_tag("history/plot.html") def history_plot(transactions, **kwargs): - return kwargs | { - "history": history(transactions.exclude(category__budget=False)), - } + options = kwargs + if "category" in kwargs or "account" in kwargs: + options["history"] = history(transactions.all()) + else: + options["history"] = history(transactions.exclude(category__budget=False)) + + return options @register.simple_tag diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 90e681d..c3f854f 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -136,7 +136,7 @@ class TransactionACMixin: Account.objects.filter(user=self.request.user), pk=self.kwargs["account"], ) - return super().get_queryset().filter(account=self.account) + return super().get_queryset().filter(statement__account=self.account) if "category" in self.kwargs: self.category = get_object_or_404( Category.objects.filter(user=self.request.user), From d8961c8198b56c6a59474ac7ee504cd500d063f3 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 15:21:06 +0100 Subject: [PATCH 287/325] Improve forms Form errors displayed correctly (fix #30) Fix #26 Close #20 --- nummi/main/static/main/css/form.css | 47 +++++++++++++++---- nummi/main/templates/main/form/fileinput.html | 33 +------------ nummi/main/templates/main/form/form_base.html | 8 +++- 3 files changed, 45 insertions(+), 43 deletions(-) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 5cb1504..06063b9 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -1,10 +1,3 @@ -form ul.errorlist { - color: var(--red); - font-weight: 550; - list-style-type: "! "; - margin: 0; -} - form { display: grid; gap: 0.5rem; @@ -29,26 +22,43 @@ form { border: none; } + ul.errorlist { + color: var(--red); + font-weight: 550; + list-style: none; + padding: 0; + margin: 0; + } + .field { display: grid; grid-auto-rows: min-content; align-items: center; - overflow: hidden; column-gap: 0.5rem; + ul.errorlist { + font-size: 0.8rem; + } + &:has(> textarea) { grid-template-rows: min-content 1fr; } - &:has(input[type="checkbox"]) { + &:has(> input[type="checkbox"]) { grid-template-columns: min-content 1fr; > label { font-size: inherit; grid-row: 1; grid-column: 2; + padding: 0.5rem; + line-height: initial; } > input { grid-row: 1; grid-column: 1; + margin: 0.5rem; + } + &:has(> :focus) { + background: var(--bg-01); } } @@ -64,13 +74,19 @@ form { font: inherit; line-height: initial; border: none; + border: 1px solid transparent; border-bottom: 1px solid var(--gray); background: none; z-index: 1; - padding: 0; + padding: 0.5rem; + + &:has(~ ul.errorlist) { + border-color: var(--red); + } &:not([type="checkbox"]) { width: 100%; + margin: 0; } &[name*="value"] { @@ -95,6 +111,9 @@ form { grid-template-columns: 1fr; grid-auto-columns: max-content; grid-auto-flow: column; + a { + padding: 0.5rem; + } } input[type="file"] { @@ -107,6 +126,14 @@ form { display: grid; grid-template-columns: min-content 1fr; column-gap: 0.5rem; + align-items: center; + span[class|="ri"] { + padding: 0.5rem; + } + + &:has(> :focus) { + background: var(--bg-01); + } } } } diff --git a/nummi/main/templates/main/form/fileinput.html b/nummi/main/templates/main/form/fileinput.html index 77989ae..8dd3985 100644 --- a/nummi/main/templates/main/form/fileinput.html +++ b/nummi/main/templates/main/form/fileinput.html @@ -5,7 +5,7 @@
    {{ "file"|remix }}{% translate "File" %} [{{ widget.value|extension }}] {% if not widget.required %} - +
    -{% comment %} - - {% if widget.is_initial %} - - - - - {% if not widget.required %} - - - - - {% endif %} - - - - - -
    {{ widget.initial_text }} - {% translate "File" %} [{{ widget.value|extension }}] -
    - - - -
    {{ widget.input_text }} - {% else %} -
    - {% endif %} - -
    -{% endcomment %} diff --git a/nummi/main/templates/main/form/form_base.html b/nummi/main/templates/main/form/form_base.html index 86341b8..4989c4b 100644 --- a/nummi/main/templates/main/form/form_base.html +++ b/nummi/main/templates/main/form/form_base.html @@ -12,10 +12,16 @@ {% for fieldset in group %}
    {% for field in fieldset %} - {% if field.errors %}

    {{ field.errors }}

    {% endif %}
    {{ field }} + {% if field.errors %} +
      + {% for error in field.errors %} +
    • {{ "error-warning"|remix }}{{ error }}
    • + {% endfor %} +
    + {% endif %}
    {% endfor %}
    From 7478404d8a313a05e9835ad334147bb767b9a5f8 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 15:55:58 +0100 Subject: [PATCH 288/325] Improve statement details page Fix #29 --- nummi/main/static/main/css/main.css | 48 +++++++++++++++++-- .../templates/statement/statement_detail.html | 27 +++++++++-- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 9181c72..eb4eee6 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -423,13 +423,14 @@ ul.statements { } .statement-details { - overflow-x: auto; display: grid; - grid-template-columns: repeat(4, min-content); + @media (width > 720px) { + grid-template-columns: repeat(3, min-content); + } gap: var(--gap); align-items: center; - > span:nth-child(2) { + > span.evolution { display: grid; grid-auto-rows: min-content; > span[class^="ri-"] { @@ -439,12 +440,49 @@ ul.statements { text-align: right; } } - > span:first-child, - > span:nth-child(3) { + > span.start, + > span.end, + > span.file, + > span.incons { display: grid; border: var(--gray) 1px solid; padding: var(--gap); + &.file, + &.incons { + grid-column: 1 / -1; + } + &.incons { + border-color: var(--red); + grid-template-columns: min-content 1fr; + grid-auto-flow: column; + align-items: center; + gap: 0.5rem; + + > .ds { + display: grid; + grid-auto-rows: min-content; + } + .value { + text-align: right; + } + .diff { + font-weight: 650; + } + .sum { + text-decoration: line-through; + } + .links { + grid-column: 1 / -1; + grid-row: 2; + display: grid; + gap: inherit; + a { + color: var(--red); + } + } + } + > .date { text-align: right; } diff --git a/nummi/statement/templates/statement/statement_detail.html b/nummi/statement/templates/statement/statement_detail.html index e31ffcc..bb7c795 100644 --- a/nummi/statement/templates/statement/statement_detail.html +++ b/nummi/statement/templates/statement/statement_detail.html @@ -19,22 +19,39 @@ {{ "file-edit"|remix }}{% translate "Edit statement" %}

    - + {% if statement.sum != statement.diff %} + + {{ statement.sum|check:statement.diff }} + + {{ statement.diff|pmvalue }} + {{ statement.sum|pmvalue }} + + + {{ "file-edit"|remix }}{% translate "Edit statement" %} + {{ "add-circle"|remix }}{% translate "Add transaction" %} + + + {% endif %} + {% if statement.file %} + + {{ "file"|remix }}{{ statement }} [{{ statement.file|extension }}] + + {% endif %} + {{ statement.start_date|date:"Y-m-d" }} {{ statement.start_value|value }} - + {{ statement.sum|pmvalue }} - {{ "arrow-right"|remix }} + {{ "funds"|remix }} {% if statement.diff != statement.sum %} {{ statement.diff|pmvalue }} {% endif %} - + {{ statement.date|date:"Y-m-d" }} {{ statement.value|value }} - {{ statement.sum|check:statement.diff }}

    {% translate "Transactions" %}

    From 38ab29809471268945c7518c1ea4f356e9868fd3 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 16:25:49 +0100 Subject: [PATCH 289/325] Improve transaction form Default to no statement (fix #28) Add link to statement on statement transaction creation Fix textarea height --- nummi/main/static/main/css/form.css | 7 +++++++ nummi/statement/forms.py | 5 +++++ .../statement/forms/widgets/statement.html | 12 ++++++++++++ nummi/transaction/forms.py | 2 ++ nummi/transaction/views.py | 17 ++++++----------- 5 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 nummi/statement/templates/statement/forms/widgets/statement.html diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 06063b9..ec5fa90 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -42,6 +42,9 @@ form { &:has(> textarea) { grid-template-rows: min-content 1fr; + textarea { + height: 100%; + } } &:has(> input[type="checkbox"]) { grid-template-columns: min-content 1fr; @@ -68,6 +71,10 @@ form { z-index: 10; } + > a { + padding: 0.5rem; + } + input, select, textarea { diff --git a/nummi/statement/forms.py b/nummi/statement/forms.py index dbf77df..c3841f2 100644 --- a/nummi/statement/forms.py +++ b/nummi/statement/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.forms.widgets import Select from django.utils.translation import gettext_lazy as _ from main.forms import NummiFileInput, NummiForm from transaction.models import Transaction @@ -50,3 +51,7 @@ class StatementForm(NummiForm): instance.transaction_set.add(*new_transactions, bulk=False) return instance + + +class StatementSelect(Select): + template_name = "statement/forms/widgets/statement.html" diff --git a/nummi/statement/templates/statement/forms/widgets/statement.html b/nummi/statement/templates/statement/forms/widgets/statement.html new file mode 100644 index 0000000..0689a39 --- /dev/null +++ b/nummi/statement/templates/statement/forms/widgets/statement.html @@ -0,0 +1,12 @@ +{% load main_extras %} +{% if widget.attrs.disabled %} + {% for group_name, group_choices, group_index in widget.optgroups %} + {% for option in group_choices %} + {% if option.selected %} + {{ "file"|remix }}{{ option.label }} + {% endif %} + {% endfor %} + {% endfor %} +{% else %} + {% include "django/forms/widgets/select.html" %} +{% endif %} diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index 5c6160e..31f7b09 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -2,6 +2,7 @@ import json from category.forms import CategorySelect from main.forms import DatalistInput, NummiFileInput, NummiForm +from statement.forms import StatementSelect from .models import Invoice, Transaction from .utils import get_datalist @@ -22,6 +23,7 @@ class TransactionForm(NummiForm): "description", ] widgets = { + "statement": StatementSelect(), "category": CategorySelect(), } diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index c3f854f..39f0f98 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -12,7 +12,6 @@ from main.views import ( NummiUpdateView, UserMixin, ) -from statement.models import Statement from .forms import InvoiceForm, TransactionForm from .models import Invoice, Transaction @@ -23,23 +22,19 @@ class TransactionCreateView(NummiCreateView): form_class = TransactionForm def get_initial(self): - _queryset = Statement.objects.filter(user=self.request.user) + initial = super().get_initial() if "statement" in self.kwargs: - self.statement = get_object_or_404(_queryset, pk=self.kwargs["statement"]) - else: - self.statement = _queryset.first() - return {"statement": self.statement} + initial["statement"] = get_object_or_404( + self.request.user.statement_set, pk=self.kwargs["statement"] + ) + + return initial def get_form_kwargs(self): if "statement" in self.kwargs: return super().get_form_kwargs() | {"disable_statement": True} return super().get_form_kwargs() - def get_context_data(self, **kwargs): - if "statement" in self.kwargs: - return super().get_context_data(**kwargs) | {"statement": self.statement} - return super().get_context_data(**kwargs) - class InvoiceCreateView(NummiCreateView): model = Invoice From f203d1db46e1a2275990ab9672045149d7b70f57 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 16:46:36 +0100 Subject: [PATCH 290/325] Fix transaction table (fix #33) --- .../transaction/transaction_table.html | 18 +++++------ .../templatetags/transaction_extras.py | 31 +++++++++---------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index 6e34f14..4879d3f 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -9,13 +9,13 @@ - {% if not category %}{% endif %} - {% if not account %}{% endif %} + {% if not hide_category %}{% endif %} + {% if not hide_account %}{% endif %} {% if new_transaction_url %} - + {% translate "Create transaction" %} @@ -27,10 +27,10 @@ {% translate "Expenses" %} {% translate "Income" %} {% translate "Trader" %} - {% if not category %} + {% if not hide_category %} {% translate "Category" %} {% endif %} - {% if not account %} + {% if not hide_account %} {% translate "Account" %} {% endif %} @@ -61,14 +61,14 @@ {{ trans.value|pmvalue }} {% if trans.value < 0 %}{% endif %} {{ trans.trader|default_if_none:"" }} - {% if not category %} + {% if not hide_category %} {% if trans.category %} {{ trans.category.icon|remix }}{{ trans.category }} {% endif %} {% endif %} - {% if not account %} + {% if not hide_account %} {% if trans.account %} {{ trans.account.icon|remix }}{{ trans.account|default_if_none:"" }} @@ -78,14 +78,14 @@ {% empty %} - {% translate "No transaction" %} + {% translate "No transaction" %} {% endfor %} {% if transactions_url %} - + {% translate "View all transactions" %} diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index 7817cfb..e2311a0 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -10,16 +10,25 @@ from ..utils import ac_url register = template.Library() -@register.inclusion_tag("transaction/transaction_table.html") -def transaction_table(transactions, **kwargs): - if (n_max := kwargs.get("n_max")) is not None: +@register.inclusion_tag("transaction/transaction_table.html", takes_context=True) +def transaction_table(context, transactions, n_max=None, **kwargs): + if n_max is not None: if transactions.count() <= n_max: del kwargs["transactions_url"] transactions = transactions[:n_max] - return kwargs | { - "transactions": transactions, - } + if "account" in context or "statement" in context: + kwargs.setdefault("hide_account", True) + if "category" in context: + kwargs.setdefault("hide_category", True) + + ncol = 8 + if kwargs.get("hide_account"): + ncol -= 1 + if kwargs.get("hide_category"): + ncol -= 1 + + return kwargs | {"transactions": transactions, "ncol": ncol} @register.inclusion_tag("transaction/invoice_table.html") @@ -54,13 +63,3 @@ def year_url(context, year, cls=""): f"""""" f"""""" ) - - -@register.simple_tag(takes_context=True) -def tr_colspan(context): - ncol = 8 - if context.get("category"): - ncol -= 1 - if context.get("account"): - ncol -= 1 - return ncol From 0cb4a681f1d74dbcd6909781cb8815024b1055ad Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 16:52:02 +0100 Subject: [PATCH 291/325] Refactor datalist input usage --- nummi/transaction/forms.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index 31f7b09..576347b 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -25,6 +25,9 @@ class TransactionForm(NummiForm): widgets = { "statement": StatementSelect(), "category": CategorySelect(), + "name": DatalistInput(), + "trader": DatalistInput(), + "payment": DatalistInput(), } meta_fieldsets = [ @@ -47,16 +50,17 @@ class TransactionForm(NummiForm): _disable_statement = kwargs.pop("disable_statement", False) super().__init__(*args, **kwargs) - self.fields["category"].queryset = _user.category_set - self.fields["statement"].queryset = _user.statement_set + self.fields["category"].queryset = self.fields["category"].queryset.filter( + user=_user + ) + self.fields["statement"].queryset = self.fields["statement"].queryset.filter( + user=_user + ) + + self.fields["name"].widget.options = get_datalist(_user, "name") + self.fields["trader"].widget.options = get_datalist(_user, "trader") + self.fields["payment"].widget.options = get_datalist(_user, "payment") - self.fields["name"].widget = DatalistInput(options=get_datalist(_user, "name")) - self.fields["trader"].widget = DatalistInput( - options=get_datalist(_user, "trader") - ) - self.fields["payment"].widget = DatalistInput( - options=get_datalist(_user, "payment") - ) self.fields["category"].widget.attrs |= { "class": "category", "data-icons": json.dumps( From ff519ea7d1cf45757ea4ec7d6d8b435e48570e99 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 18:50:50 +0100 Subject: [PATCH 292/325] Auto-fill transaction form Close #27 --- nummi/main/static/main/css/form.css | 11 +++ nummi/main/static/main/js/base.js | 70 ++++++++++++++++++- nummi/main/templates/main/form/form_base.html | 1 + nummi/transaction/forms.py | 29 ++++++++ nummi/transaction/views.py | 2 +- 5 files changed, 111 insertions(+), 2 deletions(-) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index ec5fa90..21fc448 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -1,3 +1,11 @@ +@keyframes border-pulse { + from { + border-color: var(--green); + } + to { + } +} + form { display: grid; gap: 0.5rem; @@ -90,6 +98,9 @@ form { &:has(~ ul.errorlist) { border-color: var(--red); } + &.autocompleted:not(.mod) { + border-bottom-color: var(--green); + } &:not([type="checkbox"]) { width: 100%; diff --git a/nummi/main/static/main/js/base.js b/nummi/main/static/main/js/base.js index d4c6ea1..eaafec5 100644 --- a/nummi/main/static/main/js/base.js +++ b/nummi/main/static/main/js/base.js @@ -44,7 +44,6 @@ for (let form of forms) { ); function setIcon(event) { - console.log(input.value); icon.className = `ri-${ icons.includes(input.value) ? input.value : "square" }-line`; @@ -55,6 +54,75 @@ for (let form of forms) { setTimeout(setIcon, 0); }); } + + let ac_json = form.querySelector("#autocomplete"); + if (ac_json) { + let autocomplete_data = JSON.parse(ac_json.textContent); + let fields = form.querySelectorAll("[name]"); + function clearMod(event) { + event.target.classList.add("mod"); + event.target.removeEventListener("input", clearMod); + } + function setMod() { + for (let f of fields) { + f.addEventListener("input", clearMod); + } + } + setMod(); + form.addEventListener("reset", (event) => { + for (let f of fields) { + f.classList.remove("mod", "autocompleted"); + } + setMod(); + }); + + let old_values = []; + + let field = form.querySelector(`[name="${autocomplete_data.field}"]`); + field.addEventListener("change", (event) => { + if (field.value in autocomplete_data.data) { + old_values = []; + for (let comp of autocomplete_data.data[field.value]) { + let f = form.querySelector(`[name="${comp.field}"]`); + if (!f.classList.contains("mod")) { + old_values.push({ field: f, value: f.value }); + + f.value = comp.value; + f.dispatchEvent(new Event("input")); + f.animate([{ borderColor: "var(--green)" }, {}], 750); + f.classList.remove("mod"); + f.classList.add("autocompleted"); + f.addEventListener("input", clearMod); + function ccAutocomplete(event) { + document.removeEventListener("keyup", cancelAutocomplete); + for (comp of old_values) { + comp.field.removeEventListener("input", ccAutocomplete); + } + } + f.addEventListener("input", ccAutocomplete); + } + } + function cancelAutocomplete(event) { + if (event.code == "Escape") { + for (comp of old_values) { + let f = comp.field; + f.value = comp.value; + f.dispatchEvent(new Event("input")); + f.animate([{ borderColor: "var(--red)" }, {}], 750); + f.classList.remove("mod"); + f.classList.remove("autocompleted"); + f.addEventListener("input", clearMod); + } + document.removeEventListener("keyup", cancelAutocomplete); + } + } + document.addEventListener("keyup", cancelAutocomplete); + form.addEventListener("reset", (event) => { + document.removeEventListener("keyup", cancelAutocomplete); + }); + } + }); + } } let accounts = document.querySelector("dl.accounts"); diff --git a/nummi/main/templates/main/form/form_base.html b/nummi/main/templates/main/form/form_base.html index 4989c4b..08afb61 100644 --- a/nummi/main/templates/main/form/form_base.html +++ b/nummi/main/templates/main/form/form_base.html @@ -1,6 +1,7 @@ {% load i18n %} {% load main_extras %} {% block fields %} + {% if form.autocomplete %}{{ form.autocomplete|json_script:"autocomplete" }}{% endif %} {% if form.non_field_errors %}
      {% for error in form.non_field_errors %}
    • {{ error }}
    • {% endfor %} diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index 576347b..ffecd63 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -48,6 +48,7 @@ class TransactionForm(NummiForm): def __init__(self, *args, **kwargs): _user = kwargs.get("user") _disable_statement = kwargs.pop("disable_statement", False) + _autocomplete = kwargs.pop("autocomplete", False) super().__init__(*args, **kwargs) self.fields["category"].queryset = self.fields["category"].queryset.filter( @@ -71,6 +72,34 @@ class TransactionForm(NummiForm): ), } + if _autocomplete: + self.autocomplete = { + "field": "name", + "data": { + t.name: [ + { + "field": "value", + "value": t.value, + }, + { + "field": "category", + "value": t.category.id if t.category else "", + }, + { + "field": "trader", + "value": t.trader, + }, + { + "field": "payment", + "value": t.payment, + }, + ] + for t in _user.transaction_set.order_by("name", "-date").distinct( + "name" + ) + }, + } + if _disable_statement: self.fields["statement"].disabled = True diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 39f0f98..6d8461b 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -33,7 +33,7 @@ class TransactionCreateView(NummiCreateView): def get_form_kwargs(self): if "statement" in self.kwargs: return super().get_form_kwargs() | {"disable_statement": True} - return super().get_form_kwargs() + return super().get_form_kwargs() | {"autocomplete": True} class InvoiceCreateView(NummiCreateView): From 0c5ba6899b2fbd4bf4717cb5f5389f22e18e771a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 19:04:40 +0100 Subject: [PATCH 293/325] Fix no autocomplete on statement transaction creation Fix #36 --- nummi/transaction/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 6d8461b..09c64da 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -31,9 +31,12 @@ class TransactionCreateView(NummiCreateView): return initial def get_form_kwargs(self): + form_kwargs = super().get_form_kwargs() if "statement" in self.kwargs: - return super().get_form_kwargs() | {"disable_statement": True} - return super().get_form_kwargs() | {"autocomplete": True} + form_kwargs["disable_statement"] = True + + form_kwargs["autocomplete"] = True + return form_kwargs class InvoiceCreateView(NummiCreateView): From ec5c0e2fefb1cc9dcc246cf992d8a81cc0d5b0ae Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 19:16:03 +0100 Subject: [PATCH 294/325] Fix update_sum left in transaction models --- .../templates/statement/forms/widgets/statement.html | 3 +-- nummi/transaction/forms.py | 1 + nummi/transaction/models.py | 11 ----------- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/nummi/statement/templates/statement/forms/widgets/statement.html b/nummi/statement/templates/statement/forms/widgets/statement.html index 0689a39..aad0402 100644 --- a/nummi/statement/templates/statement/forms/widgets/statement.html +++ b/nummi/statement/templates/statement/forms/widgets/statement.html @@ -7,6 +7,5 @@ {% endif %} {% endfor %} {% endfor %} -{% else %} - {% include "django/forms/widgets/select.html" %} {% endif %} +{% include "django/forms/widgets/select.html" %} diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index ffecd63..5583c84 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -102,6 +102,7 @@ class TransactionForm(NummiForm): if _disable_statement: self.fields["statement"].disabled = True + self.fields["statement"].widget.attrs["hidden"] = True class InvoiceForm(NummiForm): diff --git a/nummi/transaction/models.py b/nummi/transaction/models.py index adc78c0..37fc1d5 100644 --- a/nummi/transaction/models.py +++ b/nummi/transaction/models.py @@ -44,17 +44,6 @@ class Transaction(UserModel): verbose_name=_("Statement"), ) - def save(self, *args, **kwargs): - if Transaction.objects.filter(pk=self.pk): - prev_self = Transaction.objects.get(pk=self.pk) - else: - prev_self = None - super().save(*args, **kwargs) - if prev_self is not None and prev_self.statement: - prev_self.statement.update_sum() - if self.statement: - self.statement.update_sum() - def __str__(self): return f"{self.name}" From d2d101fe34f6e8c965920089911ade61426fd87a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 21:49:47 +0100 Subject: [PATCH 295/325] Resize block on textarea Fix #41 --- nummi/main/static/main/css/form.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 21fc448..3a25e1d 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -19,6 +19,7 @@ form { gap: 0.5rem; border: 1px solid var(--gray); padding: var(--gap); + block-size: min-content; .fieldset { display: grid; @@ -51,7 +52,7 @@ form { &:has(> textarea) { grid-template-rows: min-content 1fr; textarea { - height: 100%; + resize: block; } } &:has(> input[type="checkbox"]) { From cb1882ef8a8857373aa220c469d162c15acd8ad3 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 21:53:01 +0100 Subject: [PATCH 296/325] Default statement transaction date Fix #40 --- nummi/transaction/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 09c64da..64e1f53 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -27,6 +27,9 @@ class TransactionCreateView(NummiCreateView): initial["statement"] = get_object_or_404( self.request.user.statement_set, pk=self.kwargs["statement"] ) + initial["date"] = ( + initial["statement"].transaction_set.order_by("-date").first().date + ) return initial From cfb2ceb2c37f51a5d4c15985ccd6008a47bd4d03 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 3 Jan 2025 22:46:53 +0100 Subject: [PATCH 297/325] Fix #39 --- nummi/main/static/main/css/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index eb4eee6..3dc29d4 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -1,5 +1,5 @@ @import "https://rsms.me/inter/inter.css"; -@import "https://cdn.jsdelivr.net/npm/remixicon@4.0.0/fonts/remixicon.css"; +@import "https://cdn.jsdelivr.net/npm/remixicon@4.5.0/fonts/remixicon.css"; *, *::before, From 805c7d3dc0f95b487a78f859ffa6296c61cd2283 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 12:37:09 +0100 Subject: [PATCH 298/325] Enable file drag and drop on transaction details page Close #37 --- nummi/main/forms.py | 3 +- nummi/main/static/main/css/form.css | 36 +++++++++++-- nummi/main/static/main/css/main.css | 1 + nummi/main/templatetags/main_extras.py | 2 +- nummi/statement/forms.py | 2 +- nummi/transaction/forms.py | 30 ++++++++++- .../static/transaction/js/invoice_form.js | 22 ++++++++ .../transaction/transaction_detail.html | 11 ++++ nummi/transaction/urls.py | 5 ++ nummi/transaction/views.py | 54 ++++++++++++++++--- 10 files changed, 151 insertions(+), 15 deletions(-) create mode 100644 nummi/transaction/static/transaction/js/invoice_form.js diff --git a/nummi/main/forms.py b/nummi/main/forms.py index 05a6ae4..36e5628 100644 --- a/nummi/main/forms.py +++ b/nummi/main/forms.py @@ -12,7 +12,8 @@ class NummiForm(forms.ModelForm): template_name = "main/form/form_base.html" meta_fieldsets = [] - def __init__(self, *args, user, **kwargs): + def __init__(self, *args, **kwargs): + kwargs.pop("user", None) super().__init__(*args, **kwargs) @property diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 3a25e1d..73c9eb9 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -1,8 +1,32 @@ -@keyframes border-pulse { - from { - border-color: var(--green); +.drop-zone { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + grid-template-columns: 1fr; + grid-template-rows: 1fr; + align-items: center; + text-align: center; + color: transparent; + display: grid; + transition-property: backdrop-filter; + transition-duration: 750ms; + z-index: -1; + + > span { + font-weight: 650; + font-size: 2rem; + transition-property: color; + transition-duration: inherit; } - to { + + main.highlight > & { + z-index: 100; + backdrop-filter: blur(0.1rem); + > span { + color: var(--green); + } } } @@ -14,6 +38,10 @@ form { grid-template-columns: 1fr; } + &.hidden { + display: none; + } + .column { display: grid; gap: 0.5rem; diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 3dc29d4..a154868 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -85,6 +85,7 @@ footer { @media (width > 720px) { padding: 2rem; } + background: var(--bg); } main { position: relative; diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 0a9abcc..c5eea6c 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -59,7 +59,7 @@ def messageicon(level): @register.filter def extension(file): - return file.name.split(".")[-1].upper() + return file.name.split(".", 1)[1].upper() @register.filter diff --git a/nummi/statement/forms.py b/nummi/statement/forms.py index c3841f2..353ebe7 100644 --- a/nummi/statement/forms.py +++ b/nummi/statement/forms.py @@ -25,7 +25,7 @@ class StatementForm(NummiForm): ] def __init__(self, *args, **kwargs): - _user = kwargs.get("user") + _user = kwargs.pop("user") _disable_account = kwargs.pop("disable_account", False) super().__init__(*args, **kwargs) self.fields["account"].queryset = _user.account_set.exclude(archived=True) diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index 5583c84..807c00e 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -1,6 +1,8 @@ import json from category.forms import CategorySelect +from django import forms +from django.forms import formset_factory from main.forms import DatalistInput, NummiFileInput, NummiForm from statement.forms import StatementSelect @@ -46,7 +48,7 @@ class TransactionForm(NummiForm): ] def __init__(self, *args, **kwargs): - _user = kwargs.get("user") + _user = kwargs.pop("user") _disable_statement = kwargs.pop("disable_statement", False) _autocomplete = kwargs.pop("autocomplete", False) super().__init__(*args, **kwargs) @@ -117,3 +119,29 @@ class InvoiceForm(NummiForm): widgets = { "file": NummiFileInput, } + + +class MultipleFileInput(forms.ClearableFileInput): + allow_multiple_selected = True + + +class MultipleFileField(forms.FileField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("widget", MultipleFileInput()) + super().__init__(*args, **kwargs) + + def clean(self, data, initial=None): + single_file_clean = super().clean + if isinstance(data, (list, tuple)): + result = [single_file_clean(d, initial) for d in data] + else: + result = single_file_clean(data, initial) + return result + + +class MultipleInvoicesForm(forms.Form): + prefix = "invoices" + invoices = MultipleFileField() + + +InvoicesFormSet = formset_factory(MultipleInvoicesForm) diff --git a/nummi/transaction/static/transaction/js/invoice_form.js b/nummi/transaction/static/transaction/js/invoice_form.js new file mode 100644 index 0000000..d18abfa --- /dev/null +++ b/nummi/transaction/static/transaction/js/invoice_form.js @@ -0,0 +1,22 @@ +const dropArea = document.querySelector("main"); +const form = document.querySelector("form.invoices"); + +dropArea.addEventListener("dragover", (event) => { + event.preventDefault(); + dropArea.classList.add("highlight"); +}); + +dropArea.addEventListener("dragleave", () => { + dropArea.classList.remove("highlight"); +}); + +dropArea.addEventListener("drop", (event) => { + console.log(event); + event.preventDefault(); + dropArea.classList.remove("highlight"); + const files = event.dataTransfer.files; + console.log(files); + const input = form.querySelector("input[type=file]"); + input.files = files; + form.submit(); +}); diff --git a/nummi/transaction/templates/transaction/transaction_detail.html b/nummi/transaction/templates/transaction/transaction_detail.html index 4cfd068..7e58c0f 100644 --- a/nummi/transaction/templates/transaction/transaction_detail.html +++ b/nummi/transaction/templates/transaction/transaction_detail.html @@ -10,6 +10,7 @@ {% css "main/css/form.css" %} {% css "main/css/table.css" %} {% css "main/css/plot.css" %} + {% js "transaction/js/invoice_form.js" %} {% endblock link %} {% block body %}

      {{ transaction }}

      @@ -44,5 +45,15 @@

      {% translate "Invoices" %}

      {% invoice_table transaction %} +
      +
      + {{ "file-add"|remix }}{% translate "Add invoice" %} +
      {% endblock body %} diff --git a/nummi/transaction/urls.py b/nummi/transaction/urls.py index c39aef6..7b3c802 100644 --- a/nummi/transaction/urls.py +++ b/nummi/transaction/urls.py @@ -31,6 +31,11 @@ urlpatterns = [ views.InvoiceCreateView.as_view(), name="new_invoice", ), + path( + "/invoice/multiple", + views.MultipleInvoiceCreateView.as_view(), + name="multiple_invoice", + ), path( "/invoice/", views.InvoiceUpdateView.as_view(), diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 64e1f53..ebf2238 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -1,8 +1,13 @@ from account.models import Account from category.models import Category +from django.contrib import messages +from django.forms import ValidationError from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy +from django.utils.html import format_html +from django.utils.translation import gettext as _ from django.views.generic.dates import MonthArchiveView, YearArchiveView +from django.views.generic.edit import FormView from history.utils import history from main.views import ( NummiCreateView, @@ -13,7 +18,7 @@ from main.views import ( UserMixin, ) -from .forms import InvoiceForm, TransactionForm +from .forms import InvoiceForm, MultipleInvoicesForm, TransactionForm from .models import Invoice, Transaction @@ -57,6 +62,45 @@ class InvoiceCreateView(NummiCreateView): return reverse_lazy("transaction", args=(self.object.transaction.pk,)) +class MultipleInvoiceCreateView(FormView): + form_class = MultipleInvoicesForm + + def form_valid(self, form): + transaction = get_object_or_404( + self.request.user.transaction_set, pk=self.kwargs["transaction"] + ) + + invoices = [] + for file in form.cleaned_data["invoices"]: + invoice = Invoice( + transaction=transaction, + user=self.request.user, + file=file, + name=file.name.split(".", 1)[0], + ) + try: + invoice.full_clean() + except ValidationError as err: + for msg in err.messages: + messages.error( + self.request, + format_html( + "{msg} {file}. {err}", + msg=_("Error processing file"), + file=file.name, + err=msg, + ), + ) + else: + invoices.append(invoice) + + Invoice.objects.bulk_create(invoices) + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("transaction", args=(self.kwargs["transaction"],)) + + class TransactionUpdateView(NummiUpdateView): model = Transaction form_class = TransactionForm @@ -70,12 +114,8 @@ class TransactionDetailView(NummiDetailView): def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) - transaction = data.get("transaction") - - return data | { - "statement": transaction.statement, - "category": transaction.category, - } + data["invoices_form"] = MultipleInvoicesForm() + return data class InvoiceUpdateView(NummiUpdateView): From e1f29f90c6f40ce0bbed525509b0b58e1d2d8300 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 14:17:48 +0100 Subject: [PATCH 299/325] Move transaction link Close #35 Progress #38 --- .../templates/category/category_detail.html | 3 +++ nummi/main/static/main/css/main.css | 3 ++- nummi/main/static/main/css/table.css | 4 +--- nummi/main/templates/main/pagination.html | 9 ++------ .../main/templates/main/pagination_links.html | 13 ++++++++++++ nummi/main/templatetags/main_extras.py | 21 +++++++++++++++++++ .../templates/statement/statement_detail.html | 8 +++++-- .../transaction/transaction_list.html | 8 ++++--- .../transaction/transaction_table.html | 7 ------- 9 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 nummi/main/templates/main/pagination_links.html diff --git a/nummi/category/templates/category/category_detail.html b/nummi/category/templates/category/category_detail.html index 02b9635..10d3a80 100644 --- a/nummi/category/templates/category/category_detail.html +++ b/nummi/category/templates/category/category_detail.html @@ -16,6 +16,9 @@

      {% translate "Transactions" %}

      {% url "category_transactions" category.id as t_url %} +

      + {{ "list-check"|remixnl }}{% translate "View all transactions" %} +

      {% transaction_table category.transaction_set.all n_max=8 transactions_url=t_url %}
      diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index a154868..03b0bc2 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -522,7 +522,8 @@ ul.statements { } } -.category { +.category, +.big-link { padding: 0 var(--gap); border: var(--gray) 1px solid; margin-right: 0.5rem; diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index c6284ec..1021922 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -45,9 +45,7 @@ table { font-weight: 300; } } - tfoot tr:not(.new) { - background: var(--bg-01); - } + .l { text-align: left; } diff --git a/nummi/main/templates/main/pagination.html b/nummi/main/templates/main/pagination.html index 0cad121..a97e1d2 100644 --- a/nummi/main/templates/main/pagination.html +++ b/nummi/main/templates/main/pagination.html @@ -1,11 +1,6 @@ -{% load i18n transaction_extras %} +{% load i18n main_extras transaction_extras %} {% if page_obj %} -

      - {% for page in paginator.page_range %} - {{ page }} - {% endfor %} -

      +

      {% pagination_links page_obj %}

      {% endif %} {% if month %}

      {% year_url month %}

      diff --git a/nummi/main/templates/main/pagination_links.html b/nummi/main/templates/main/pagination_links.html new file mode 100644 index 0000000..fcd8531 --- /dev/null +++ b/nummi/main/templates/main/pagination_links.html @@ -0,0 +1,13 @@ +{% load i18n main_extras %} +{% if first.show %} + 1 + {% if first.dots %}{% endif %} +{% endif %} +{% for page in pages %} + {{ page.number }} +{% endfor %} +{% if last.show %} + {% if last.dots %}{% endif %} + {{ last.number }} +{% endif %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index c5eea6c..9dd97e4 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -89,3 +89,24 @@ def balance(accounts): return sum( statement.value for acc in accounts if (statement := acc.statement_set.first()) ) + + +@register.inclusion_tag("main/pagination_links.html") +def pagination_links(page_obj): + _n = 3 + return { + "pages": [ + {"number": p, "current": p == page_obj.number} + for p in page_obj.paginator.page_range + if abs(p - page_obj.number) < _n + ], + "first": { + "show": page_obj.number > _n, + "dots": page_obj.number > _n + 1, + }, + "last": { + "show": page_obj.number <= page_obj.paginator.num_pages - _n, + "dots": page_obj.number < page_obj.paginator.num_pages - _n, + "number": page_obj.paginator.num_pages, + }, + } diff --git a/nummi/statement/templates/statement/statement_detail.html b/nummi/statement/templates/statement/statement_detail.html index bb7c795..232b22f 100644 --- a/nummi/statement/templates/statement/statement_detail.html +++ b/nummi/statement/templates/statement/statement_detail.html @@ -56,8 +56,12 @@

      {% translate "Transactions" %}

      {% url "statement_transactions" statement.id as t_url %} - {% url "new_transaction" statement=statement.id as nt_url %} - {% transaction_table statement.transaction_set.all n_max=8 transactions_url=t_url new_transaction_url=nt_url %} +

      + {{ "add-circle"|remix }}{% translate "Add transaction" %} + {{ "list-check"|remixnl }}{% translate "View all transactions" %} +

      + {% transaction_table statement.transaction_set.all n_max=8 transactions_url=t_url %}

      {% translate "Categories" %}

      diff --git a/nummi/transaction/templates/transaction/transaction_list.html b/nummi/transaction/templates/transaction/transaction_list.html index abd6ef5..0c2ca7f 100644 --- a/nummi/transaction/templates/transaction/transaction_list.html +++ b/nummi/transaction/templates/transaction/transaction_list.html @@ -1,5 +1,5 @@ {% extends "main/list.html" %} -{% load i18n transaction_extras %} +{% load i18n main_extras transaction_extras %} {% block name %} {% translate "Transactions" %} {% endblock name %} @@ -7,6 +7,8 @@ {% translate "Transactions" %} {% endblock h2 %} {% block table %} - {% url "new_transaction" as nt_url %} - {% transaction_table transactions new_transaction_url=nt_url %} +

      + {{ "add-circle"|remix }}{% translate "Add transaction" %} +

      + {% transaction_table transactions %} {% endblock table %} diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index 4879d3f..7dbe47c 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -13,13 +13,6 @@ {% if not hide_account %}{% endif %} - {% if new_transaction_url %} - - - {% translate "Create transaction" %} - - - {% endif %} {{ "attachment"|remix }} {% translate "Date" %} From 6a17a3e333c9e9c42c5164b4c752c1de011e045f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 14:21:59 +0100 Subject: [PATCH 300/325] Add pre-commit hook to prevent commits to main --- .pre-commit-config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b84af83..d6f30de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,10 @@ repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + - id: no-commit-to-branch + args: ["--branch", "main"] - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: From 7851e8afbbac7287c35eaecc02685c7c963b0adb Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 14:33:57 +0100 Subject: [PATCH 301/325] Improve form buttons styling Close #34 --- nummi/main/static/main/css/form.css | 36 ++++++++++++++-------------- nummi/main/static/main/css/main.css | 8 +++---- nummi/main/static/main/css/table.css | 5 ++-- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 73c9eb9..5685fa5 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -186,33 +186,33 @@ form { } .buttons { grid-column: 1 / -1; - display: grid; - grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); - grid-auto-rows: 1fr; - gap: 0.5rem; - align-items: center; - - input { + line-height: 2rem; + input, + a { font: inherit; - line-height: 1.5; - border-radius: var(--radius); - padding: 0 var(--gap); cursor: pointer; + padding: 0 var(--gap); + border: var(--gray) 1px solid; + margin-right: 0.5rem; + margin-bottom: 0.5rem; + color: inherit; &:hover { text-decoration: underline; } - &[type="submit"] { - border: 0.1rem solid var(--green); - background: var(--green-1); - } - &[type="reset"] { - border: 0.1rem solid var(--red); - background: var(--red-1); - } + } + + input[type="submit"] { + background: var(--green-1); + border-color: var(--green); + } + input[type="reset"] { + background: var(--bg-1); } a.del { color: var(--red); + border-color: var(--red); + border-style: dashed; } } } diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 03b0bc2..b0ef3b0 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -24,7 +24,7 @@ --bg-inv: var(--theme-1); --text-inv: #ffffffde; - --bg-01: #f0f0f0; + --bg-1: #f0f0f0; --text-green: #296629; --text-link: var(--text-green); @@ -118,7 +118,7 @@ nav { top: 0; overflow-y: auto; - background: var(--bg-01); + background: var(--bg-1); line-height: 2rem; h1 img { @@ -240,7 +240,7 @@ footer { color: var(--bg); } &.white { - background: var(--bg-01); + background: var(--bg-1); } border-radius: var(--radius); height: 1.5em; @@ -283,7 +283,7 @@ ul.messages { list-style-type: none; margin: 0; margin-bottom: var(--gap); - background: var(--bg-01); + background: var(--bg-1); padding: 0; li { diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index 1021922..d43b15e 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -1,5 +1,4 @@ -.table, -form { +.table { overflow-x: auto; width: 100%; } @@ -17,7 +16,7 @@ table { width: 1ch; } thead tr:not(.new) { - background: var(--bg-01); + background: var(--bg-1); } tr { border: 1px solid var(--gray); From b848bf8d650d34c39001004b84d67b729194aa9e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 18:28:37 +0100 Subject: [PATCH 302/325] Implement filters and sorting for transactions Close #5 --- nummi/main/static/main/css/form.css | 227 +++++++++--------- nummi/main/static/main/css/main.css | 19 ++ nummi/main/static/main/js/base.js | 11 + .../main/templates/main/pagination_links.html | 6 +- nummi/main/templatetags/main_extras.py | 12 +- nummi/transaction/forms.py | 53 ++++ .../transaction/transaction_filters.html | 19 ++ .../transaction/transaction_list.html | 5 + .../templatetags/transaction_extras.py | 12 +- nummi/transaction/views.py | 57 ++++- 10 files changed, 297 insertions(+), 124 deletions(-) create mode 100644 nummi/transaction/templates/transaction/transaction_filters.html diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 5685fa5..f4fd25e 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -48,141 +48,140 @@ form { border: 1px solid var(--gray); padding: var(--gap); block-size: min-content; + } + .fieldset { + display: grid; + grid-auto-columns: 1fr; + grid-auto-flow: column; + gap: inherit; + padding: 0; + margin: 0; + border: none; + } - .fieldset { - display: grid; - grid-auto-columns: 1fr; - grid-auto-flow: column; - gap: inherit; - padding: 0; - margin: 0; - border: none; - } + ul.errorlist { + color: var(--red); + font-weight: 550; + list-style: none; + padding: 0; + margin: 0; + } + + .field { + display: grid; + grid-auto-rows: min-content; + align-items: center; + column-gap: 0.5rem; ul.errorlist { - color: var(--red); - font-weight: 550; - list-style: none; - padding: 0; - margin: 0; + font-size: 0.8rem; } - .field { - display: grid; - grid-auto-rows: min-content; - align-items: center; - column-gap: 0.5rem; - - ul.errorlist { - font-size: 0.8rem; - } - - &:has(> textarea) { - grid-template-rows: min-content 1fr; - textarea { - resize: block; - } - } - &:has(> input[type="checkbox"]) { - grid-template-columns: min-content 1fr; - > label { - font-size: inherit; - grid-row: 1; - grid-column: 2; - padding: 0.5rem; - line-height: initial; - } - > input { - grid-row: 1; - grid-column: 1; - margin: 0.5rem; - } - &:has(> :focus) { - background: var(--bg-01); - } - } - - > label { - font-size: 0.8rem; - line-height: 0.8rem; - z-index: 10; - } - - > a { - padding: 0.5rem; - } - - input, - select, + &:has(> textarea) { + grid-template-rows: min-content 1fr; textarea { - font: inherit; - line-height: initial; - border: none; - border: 1px solid transparent; - border-bottom: 1px solid var(--gray); - background: none; - z-index: 1; + resize: block; + } + } + &:has(> input[type="checkbox"]) { + grid-template-columns: min-content 1fr; + > label { + font-size: inherit; + grid-row: 1; + grid-column: 2; padding: 0.5rem; + line-height: initial; + } + > input { + grid-row: 1; + grid-column: 1; + margin: 0.5rem; + } + &:has(> :focus) { + background: var(--bg-1); + } + } - &:has(~ ul.errorlist) { - border-color: var(--red); - } - &.autocompleted:not(.mod) { - border-bottom-color: var(--green); - } + > label { + font-size: 0.8rem; + line-height: 0.8rem; + z-index: 10; + } - &:not([type="checkbox"]) { - width: 100%; - margin: 0; - } + > a { + padding: 0.5rem; + } - &[name*="value"] { - text-align: right; - font-feature-settings: var(--num); - } - &[name*="date"] { - font-feature-settings: var(--num); - } + input, + select, + textarea { + font: inherit; + line-height: initial; + border: none; + border: 1px solid transparent; + border-bottom: 1px solid var(--gray); + background: none; + z-index: 1; + padding: 0.5rem; - &:focus { - outline: none; - background: var(--bg-01); - } + &:has(~ ul.errorlist) { + border-color: var(--red); + } + &.autocompleted:not(.mod) { + border-bottom-color: var(--green); } - > .file-input { - display: grid; - - > .current { - display: grid; - grid-template-columns: 1fr; - grid-auto-columns: max-content; - grid-auto-flow: column; - a { - padding: 0.5rem; - } - } - - input[type="file"] { - &::file-selector-button { - display: none; - } - } + &:not([type="checkbox"]) { + width: 100%; + margin: 0; } - > .ico-input { + + &[name*="value"] { + text-align: right; + font-feature-settings: var(--num); + } + &[name*="date"] { + font-feature-settings: var(--num); + } + + &:focus { + outline: none; + background: var(--bg-1); + } + } + + > .file-input { + display: grid; + + > .current { display: grid; - grid-template-columns: min-content 1fr; - column-gap: 0.5rem; - align-items: center; - span[class|="ri"] { + grid-template-columns: 1fr; + grid-auto-columns: max-content; + grid-auto-flow: column; + a { padding: 0.5rem; } + } - &:has(> :focus) { - background: var(--bg-01); + input[type="file"] { + &::file-selector-button { + display: none; } } } + > .ico-input { + display: grid; + grid-template-columns: min-content 1fr; + column-gap: 0.5rem; + align-items: center; + span[class|="ri"] { + padding: 0.5rem; + } + + &:has(> :focus) { + background: var(--bg-1); + } + } } .buttons { grid-column: 1 / -1; diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index b0ef3b0..a3f9ed5 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -538,3 +538,22 @@ ul.statements { .value { font-feature-settings: var(--num); } + +details { + border: var(--gray) 1px solid; + margin-bottom: var(--gap); + + summary { + font-weight: 650; + cursor: pointer; + padding: var(--gap); + } + + &[open] summary { + background: var(--bg-1); + } + + form { + padding: var(--gap); + } +} diff --git a/nummi/main/static/main/js/base.js b/nummi/main/static/main/js/base.js index eaafec5..3cf2d92 100644 --- a/nummi/main/static/main/js/base.js +++ b/nummi/main/static/main/js/base.js @@ -135,3 +135,14 @@ if (accounts) { }); } } + +const filterForm = document.querySelector("form.filter"); +if (filterForm) { + filterForm.addEventListener("submit", (event) => { + for (element of filterForm.elements) { + if (element.value == "") { + element.disabled = true; + } + } + }); +} diff --git a/nummi/main/templates/main/pagination_links.html b/nummi/main/templates/main/pagination_links.html index fcd8531..e2a55b9 100644 --- a/nummi/main/templates/main/pagination_links.html +++ b/nummi/main/templates/main/pagination_links.html @@ -1,13 +1,13 @@ {% load i18n main_extras %} {% if first.show %} - 1 + 1 {% if first.dots %}{% endif %} {% endif %} {% for page in pages %} - {{ page.number }} {% endfor %} {% if last.show %} {% if last.dots %}{% endif %} - {{ last.number }} + {{ last.number }} {% endif %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 9dd97e4..1b1e5dc 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -91,10 +91,11 @@ def balance(accounts): ) -@register.inclusion_tag("main/pagination_links.html") -def pagination_links(page_obj): +@register.inclusion_tag("main/pagination_links.html", takes_context=True) +def pagination_links(context, page_obj): _n = 3 return { + "request": context["request"], "pages": [ {"number": p, "current": p == page_obj.number} for p in page_obj.paginator.page_range @@ -110,3 +111,10 @@ def pagination_links(page_obj): "number": page_obj.paginator.num_pages, }, } + + +@register.simple_tag(takes_context=True) +def page_url(context, page): + query = context["request"].GET.copy() + query["page"] = page + return query.urlencode() diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index 807c00e..15d3dbd 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -3,6 +3,7 @@ import json from category.forms import CategorySelect from django import forms from django.forms import formset_factory +from django.utils.translation import gettext_lazy as _ from main.forms import DatalistInput, NummiFileInput, NummiForm from statement.forms import StatementSelect @@ -145,3 +146,55 @@ class MultipleInvoicesForm(forms.Form): InvoicesFormSet = formset_factory(MultipleInvoicesForm) + + +class DateInput(forms.DateInput): + input_type = "date" + + def __init__(self, attrs=None): + super().__init__(attrs) + self.format = "%Y-%m-%d" + + +class DateField(forms.DateField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("widget", DateInput()) + super().__init__(*args, **kwargs) + + +class TransactionFiltersForm(forms.Form): + start_date = DateField(required=False) + end_date = DateField(required=False) + category = forms.ModelChoiceField( + queryset=None, required=False, widget=CategorySelect() + ) + account = forms.ModelChoiceField(queryset=None, required=False) + search = forms.CharField(label=_("Search"), required=False) + sort_by = forms.ChoiceField( + label=_("Sort by"), + choices=[ + ("", _("Default")), + ("date", _("Date +")), + ("-date", _("Date -")), + ("value", _("Value +")), + ("-value", _("Value -")), + ], + required=False, + ) + + def __init__(self, *args, **kwargs): + _user = kwargs.pop("user") + super().__init__(*args, **kwargs) + + self.fields["category"].queryset = _user.category_set + self.fields["account"].queryset = _user.account_set + + self.fields["category"].widget.attrs |= { + "class": "category", + "data-icons": json.dumps( + { + str(cat.id): cat.icon + for cat in self.fields["category"].queryset.only("id", "icon") + } + ), + } diff --git a/nummi/transaction/templates/transaction/transaction_filters.html b/nummi/transaction/templates/transaction/transaction_filters.html new file mode 100644 index 0000000..907a14f --- /dev/null +++ b/nummi/transaction/templates/transaction/transaction_filters.html @@ -0,0 +1,19 @@ +{% load i18n %} +
      + {% translate "Filters" %} +
      + {% for field in form %} +
      + + {{ field }} +
      + {% endfor %} +
      + + + {% if filters %} + {% translate "Clear" %} + {% endif %} +
      +
      +
      diff --git a/nummi/transaction/templates/transaction/transaction_list.html b/nummi/transaction/templates/transaction/transaction_list.html index 0c2ca7f..afd3b7c 100644 --- a/nummi/transaction/templates/transaction/transaction_list.html +++ b/nummi/transaction/templates/transaction/transaction_list.html @@ -3,6 +3,10 @@ {% block name %} {% translate "Transactions" %} {% endblock name %} +{% block link %} + {{ block.super }} + {% css "main/css/form.css" %} +{% endblock link %} {% block h2 %} {% translate "Transactions" %} {% endblock h2 %} @@ -10,5 +14,6 @@

      {{ "add-circle"|remix }}{% translate "Add transaction" %}

      + {% transaction_filters form=filter_form %} {% transaction_table transactions %} {% endblock table %} diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index e2311a0..e905a02 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -17,10 +17,8 @@ def transaction_table(context, transactions, n_max=None, **kwargs): del kwargs["transactions_url"] transactions = transactions[:n_max] - if "account" in context or "statement" in context: - kwargs.setdefault("hide_account", True) - if "category" in context: - kwargs.setdefault("hide_category", True) + kwargs.setdefault("hide_account", "account" in context or "statement" in context) + kwargs.setdefault("hide_category", "category" in context) ncol = 8 if kwargs.get("hide_account"): @@ -31,6 +29,12 @@ def transaction_table(context, transactions, n_max=None, **kwargs): return kwargs | {"transactions": transactions, "ncol": ncol} +@register.inclusion_tag("transaction/transaction_filters.html", takes_context=True) +def transaction_filters(context, **kwargs): + kwargs.setdefault("filters", context.get("filters")) + return kwargs + + @register.inclusion_tag("transaction/invoice_table.html") def invoice_table(transaction, **kwargs): return kwargs | { diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index ebf2238..41fd184 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -1,6 +1,13 @@ from account.models import Account from category.models import Category from django.contrib import messages +from django.contrib.postgres.search import ( + SearchQuery, + SearchRank, + SearchVector, + TrigramSimilarity, +) +from django.db import models from django.forms import ValidationError from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy @@ -18,7 +25,12 @@ from main.views import ( UserMixin, ) -from .forms import InvoiceForm, MultipleInvoicesForm, TransactionForm +from .forms import ( + InvoiceForm, + MultipleInvoicesForm, + TransactionFiltersForm, + TransactionForm, +) from .models import Invoice, Transaction @@ -167,6 +179,49 @@ class TransactionListView(NummiListView): context_object_name = "transactions" paginate_by = 50 + def get_queryset(self, **kwargs): + queryset = super().get_queryset(**kwargs) + + if date := self.request.GET.get("start_date"): + queryset = queryset.filter(date__gte=date) + if date := self.request.GET.get("end_date"): + queryset = queryset.filter(date__lte=date) + if category := self.request.GET.get("category"): + queryset = queryset.filter(category=category) + if account := self.request.GET.get("account"): + queryset = queryset.filter(statement__account=account) + if search := self.request.GET.get("search"): + queryset = ( + queryset.annotate( + rank=SearchRank( + SearchVector("name", weight="A") + + SearchVector("description", weight="B") + + SearchVector("trader", weight="B") + + SearchVector("category__name", weight="C"), + SearchQuery(search, search_type="websearch"), + ), + similarity=TrigramSimilarity("name", search), + ) + .filter(models.Q(rank__gte=0.1) | models.Q(similarity__gte=0.3)) + .order_by("-rank", "-date") + ) + if sort_by := self.request.GET.get("sort_by"): + queryset = queryset.order_by(sort_by) + + return queryset + + def get_context_data(self, **kwargs): + data = super().get_context_data(**kwargs) + + filters = self.request.GET.copy() + filters.pop("page", None) + if filters: + data["filters"] = True + data["filter_form"] = TransactionFiltersForm( + initial=filters, user=self.request.user + ) + return data + class TransactionACMixin: model = Transaction From ccbf433ec0705b33f128c285e45dfd82530ee41b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 18:33:04 +0100 Subject: [PATCH 303/325] Add filter icon --- nummi/main/static/main/css/main.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index a3f9ed5..43baf37 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -547,6 +547,12 @@ details { font-weight: 650; cursor: pointer; padding: var(--gap); + + &::marker { + content: "\ed27\2002"; + font-family: remixicon; + font-weight: initial; + } } &[open] summary { From a238401f13451f5285da56c82a93b41427bf5fde Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 18:45:36 +0100 Subject: [PATCH 304/325] Add AccountSelect widget and update account field in forms - Introduced AccountSelect widget for improved account selection in forms. - Updated AccountForm, StatementForm, and TransactionFiltersForm to use AccountSelect. - Modified account model options to include 'archived' in ordering. - Added new migration for account model options change. --- nummi/account/forms.py | 5 +++++ .../migrations/0004_alter_account_options.py | 20 +++++++++++++++++++ nummi/account/models.py | 2 +- .../account/forms/widgets/account.html | 5 +++++ nummi/main/static/main/js/base.js | 15 ++++++++++++++ nummi/statement/forms.py | 14 +++++++++++++ nummi/transaction/forms.py | 14 ++++++++++++- 7 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 nummi/account/migrations/0004_alter_account_options.py create mode 100644 nummi/account/templates/account/forms/widgets/account.html diff --git a/nummi/account/forms.py b/nummi/account/forms.py index cde1416..8dc869a 100644 --- a/nummi/account/forms.py +++ b/nummi/account/forms.py @@ -1,3 +1,4 @@ +from django.forms.widgets import Select from main.forms import IconInput, NummiForm from .models import Account @@ -15,3 +16,7 @@ class AccountForm(NummiForm): widgets = { "icon": IconInput(), } + + +class AccountSelect(Select): + template_name = "account/forms/widgets/account.html" diff --git a/nummi/account/migrations/0004_alter_account_options.py b/nummi/account/migrations/0004_alter_account_options.py new file mode 100644 index 0000000..7c4fd2d --- /dev/null +++ b/nummi/account/migrations/0004_alter_account_options.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.7 on 2025-01-04 17:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("account", "0003_account_archived"), + ] + + operations = [ + migrations.AlterModelOptions( + name="account", + options={ + "ordering": ["-default", "archived", "name"], + "verbose_name": "Account", + "verbose_name_plural": "Accounts", + }, + ), + ] diff --git a/nummi/account/models.py b/nummi/account/models.py index 16aca17..07ee795 100644 --- a/nummi/account/models.py +++ b/nummi/account/models.py @@ -41,7 +41,7 @@ class Account(UserModel): ) class Meta: - ordering = ["-default", "name"] + ordering = ["-default", "archived", "name"] verbose_name = _("Account") verbose_name_plural = _("Accounts") diff --git a/nummi/account/templates/account/forms/widgets/account.html b/nummi/account/templates/account/forms/widgets/account.html new file mode 100644 index 0000000..0641a32 --- /dev/null +++ b/nummi/account/templates/account/forms/widgets/account.html @@ -0,0 +1,5 @@ +{% load main_extras %} + diff --git a/nummi/main/static/main/js/base.js b/nummi/main/static/main/js/base.js index 3cf2d92..2b41ee2 100644 --- a/nummi/main/static/main/js/base.js +++ b/nummi/main/static/main/js/base.js @@ -34,6 +34,21 @@ for (let form of forms) { setTimeout(setIcon, 0); }); } + let accountSelect = form.querySelector(".account-select"); + if (accountSelect) { + let input = accountSelect.querySelector("select"); + let icon = accountSelect.querySelector("span"); + let icons = JSON.parse(input.dataset.icons); + + function setIcon(event) { + icon.className = `ri-${icons[input.value] || "bank"}-line`; + } + setIcon(); + input.addEventListener("input", setIcon); + form.addEventListener("reset", (event) => { + setTimeout(setIcon, 0); + }); + } let iconSelect = form.querySelector(".icon-select"); if (iconSelect) { diff --git a/nummi/statement/forms.py b/nummi/statement/forms.py index 353ebe7..e2b9f46 100644 --- a/nummi/statement/forms.py +++ b/nummi/statement/forms.py @@ -1,3 +1,6 @@ +import json + +from account.forms import AccountSelect from django import forms from django.forms.widgets import Select from django.utils.translation import gettext_lazy as _ @@ -13,6 +16,7 @@ class StatementForm(NummiForm): fields = ["account", "start_date", "date", "start_value", "value", "file"] widgets = { "file": NummiFileInput, + "account": AccountSelect, } meta_fieldsets = [ @@ -40,6 +44,16 @@ class StatementForm(NummiForm): required=False, ) + self.fields["account"].widget.attrs |= { + "class": "account", + "data-icons": json.dumps( + { + str(acc.id): acc.icon + for acc in self.fields["account"].queryset.only("id", "icon") + } + ), + } + if _disable_account: self.fields["account"].disabled = True diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index 15d3dbd..16110a0 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -1,5 +1,6 @@ import json +from account.forms import AccountSelect from category.forms import CategorySelect from django import forms from django.forms import formset_factory @@ -168,7 +169,9 @@ class TransactionFiltersForm(forms.Form): category = forms.ModelChoiceField( queryset=None, required=False, widget=CategorySelect() ) - account = forms.ModelChoiceField(queryset=None, required=False) + account = forms.ModelChoiceField( + queryset=None, required=False, widget=AccountSelect() + ) search = forms.CharField(label=_("Search"), required=False) sort_by = forms.ChoiceField( label=_("Sort by"), @@ -198,3 +201,12 @@ class TransactionFiltersForm(forms.Form): } ), } + self.fields["account"].widget.attrs |= { + "class": "account", + "data-icons": json.dumps( + { + str(acc.id): acc.icon + for acc in self.fields["account"].queryset.only("id", "icon") + } + ), + } From e42410863d201871be4f890913ac954c00c8a518 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 18:57:13 +0100 Subject: [PATCH 305/325] Update French translations --- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 810 -> 905 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 39 +++-- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 968 -> 1080 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 24 ++- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 490 -> 534 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 20 ++- nummi/main/locale/fr_FR/LC_MESSAGES/django.mo | Bin 1687 -> 1484 bytes nummi/main/locale/fr_FR/LC_MESSAGES/django.po | 101 ++++++------ .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 1305 -> 1401 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 93 ++++++----- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 1577 -> 2196 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 146 ++++++++++++------ 12 files changed, 254 insertions(+), 169 deletions(-) diff --git a/nummi/account/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/account/locale/fr_FR/LC_MESSAGES/django.mo index d0a97cf8b790f1183168d8f4c957f9c7d6f0f41f..52b402c1ae272c1bd4e329b0227fb4dc104ce126 100644 GIT binary patch delta 479 zcmXw#Jxjw-6ozkNZT&`C5QGfboE#OLB)HVk)~SOK;;ktpO-a%^)=d<**3HF5sH6S? zv7`UNKj9`0J}0?7gT{snGwT)nYaBk=+E&=;!Wf9$g0FWko4OXUC!DBgk6 za39v;0hBsTDEHftM|F6~fEQ5C#ZW%v23qu2%NPks%48DsEJ7xiViMAfiHoB02Ud|3 zmZn8`L68jlxiZX@7e%gLCNXIc_D1?rout~&wb_^)TZcPga_#zy?RL&?to!-(>m oo+rc1jdbP)Nj%8)qRxCa`LtV(*X{O#P^aele3q$}j`TiKh*%28abQ62Ah)IpTrH#%e}uN$rL?k7 zDftWOt$gO(6d8E;*>`qlc6RQqi|J1-zZ9Z^n&<=FpaaslGa??mhb8z3tMJL?yHIO; z&JST8KXMG<6@Cmap@n)sg%3tj`D9ZhvAsAy^aZc+JLkV)4S#^z;1}xsBg{d=q5~D6 z4p@i!Knv133$|IU<|)fUhb&3kIZ#Fgq|J3=4gI-vgCL$o_K(RTHqQ;~+i)62Hj%-b dA0>WZU*l-AHyyKR=3Z|5;Us+Dt^S=Rasr#HEQA06 diff --git a/nummi/account/locale/fr_FR/LC_MESSAGES/django.po b/nummi/account/locale/fr_FR/LC_MESSAGES/django.po index 14f7e1a..30ea931 100644 --- a/nummi/account/locale/fr_FR/LC_MESSAGES/django.po +++ b/nummi/account/locale/fr_FR/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-04 16:18+0100\n" +"POT-Creation-Date: 2025-01-04 18:51+0100\n" "PO-Revision-Date: 2023-04-22 15:17+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -17,46 +17,59 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.2.2\n" -#: .\account\models.py:11 .\account\models.py:37 .\account\models.py:45 +#: .\account\models.py:12 .\account\models.py:45 .\account\models.py:53 +#: .\account\templates\account\account_list.html:9 msgid "Account" msgstr "Compte" -#: .\account\models.py:11 +#: .\account\models.py:12 msgid "Name" msgstr "Nom" -#: .\account\models.py:15 +#: .\account\models.py:16 msgid "Icon" msgstr "Icône" -#: .\account\models.py:17 +#: .\account\models.py:18 msgid "Default" msgstr "Défaut" -#: .\account\models.py:38 +#: .\account\models.py:19 +msgid "Archived" +msgstr "Archivé" + +#: .\account\models.py:46 .\account\templates\account\account_list.html:12 msgid "Accounts" msgstr "Comptes" -#: .\account\templates\account\account_detail.html:13 +#: .\account\templates\account\account_detail.html:15 msgid "Edit account" msgstr "Modifier le compte" -#: .\account\templates\account\account_detail.html:16 +#: .\account\templates\account\account_detail.html:18 msgid "Statements" msgstr "Relevés" -#: .\account\templates\account\account_detail.html:20 -msgid "Transactions" -msgstr "Transactions" - -#: .\account\templates\account\account_detail.html:25 +#: .\account\templates\account\account_detail.html:24 msgid "History" msgstr "Historique" #: .\account\templates\account\account_form.html:5 +#: .\account\templates\account\account_table.html:35 msgid "Create account" msgstr "Créer un compte" #: .\account\templates\account\account_form.html:8 msgid "New account" msgstr "Nouveau compte" + +#: .\account\templates\account\account_table.html:16 +msgid "All accounts" +msgstr "Tous les comptes" + +#: .\account\templates\account\account_table.html:26 +msgid "Show archived" +msgstr "Afficher archivés" + +#~ msgid "Transactions" +#~ msgstr "Transactions" diff --git a/nummi/category/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/category/locale/fr_FR/LC_MESSAGES/django.mo index 53d58b5e6341c6055989605e6b469824c309374e..3beb30965957e002abe1d70e0a3327a808f11a53 100644 GIT binary patch delta 480 zcmXxgF-yZx5Ww+^X>E)))k<|xA-K5s0bKnGBApHCLjoaz<`p-y>Lx@dC!w384!Y{% zBsjPT3c7T25qJL=>cRDw^u2fQa!;*0ul(pXE`(Sj*U2%tN^U#+5GOc`r|9DudU%N) z9Ag`AF~EoE`U|#rf5QfTq1Ks9-@kD|q?8{X^q|a$G@*kmNDq6siCgU1K`n6Lip=97 zYC{F;zGGa$bJT{eaSrcM_dTMXe@1QW6&W=tT{#i delta 405 zcmYk&u};EJ6vpvWN(B`m!6<`FEQG}wR+$OUAh9A7jm4=$6T{@-(mJxRG7=MvgA0Qz zgR5ck1$Y7fA8CvyJ@!EStr)VdC*(n|=7fCb=V3c;T8IV*}N|C7xji&#{k( zc$<9>P<0Wikw@gQCx%me#wyOyV}E;PqK@8?J^w<*I)OqP=%+fdn#hr=`5%<;Z$`~3 qINknWn(Kz_>fvVSPpo@A@rUF4ksmTmW=<>la(c^lE_9MNckm0N$t4~D diff --git a/nummi/category/locale/fr_FR/LC_MESSAGES/django.po b/nummi/category/locale/fr_FR/LC_MESSAGES/django.po index d66c030..8e0da23 100644 --- a/nummi/category/locale/fr_FR/LC_MESSAGES/django.po +++ b/nummi/category/locale/fr_FR/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-04 16:18+0100\n" +"POT-Creation-Date: 2025-01-04 18:51+0100\n" "PO-Revision-Date: 2023-04-22 15:18+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -18,7 +18,7 @@ msgstr "" "X-Generator: Poedit 3.2.2\n" #: .\category\models.py:12 .\category\models.py:32 -#: .\category\templates\category\category_plot.html:14 +#: .\category\templates\category\category_plot.html:13 msgid "Category" msgstr "Catégorie" @@ -38,15 +38,19 @@ msgstr "Budget" msgid "Categories" msgstr "Catégories" -#: .\category\templates\category\category_detail.html:12 +#: .\category\templates\category\category_detail.html:14 msgid "Edit category" msgstr "Modifier la catégorie" -#: .\category\templates\category\category_detail.html:15 +#: .\category\templates\category\category_detail.html:17 msgid "Transactions" msgstr "Transactions" #: .\category\templates\category\category_detail.html:20 +msgid "View all transactions" +msgstr "Voir toutes les transactions" + +#: .\category\templates\category\category_detail.html:25 msgid "History" msgstr "Historique" @@ -58,18 +62,22 @@ msgstr "Créer une catégorie" msgid "New category" msgstr "Nouvelle catégorie" -#: .\category\templates\category\category_plot.html:15 +#: .\category\templates\category\category_plot.html:14 msgid "Expenses" msgstr "Dépenses" -#: .\category\templates\category\category_plot.html:16 +#: .\category\templates\category\category_plot.html:15 msgid "Income" msgstr "Revenus" -#: .\category\templates\category\category_plot.html:62 +#: .\category\templates\category\category_plot.html:58 msgid "No transaction" msgstr "Aucune transaction" -#: .\category\templates\category\category_plot.html:70 +#: .\category\templates\category\category_plot.html:66 msgid "Total" msgstr "Total" + +#: .\category\templates\category\category_plot.html:89 +msgid "Expected total" +msgstr "Total attendu" diff --git a/nummi/history/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/history/locale/fr_FR/LC_MESSAGES/django.mo index cdfe26fffdcf15c1ca75914eaed79c8fc4615fe2..4c77aead32e5fea8924d904e3b8926f1bc4f81b4 100644 GIT binary patch delta 194 zcmaFGJdLIPo)F7a1|VPqVi_Rz0b*_-t^r~YSOLTwK)e!&L25PuF)I-7gwp$gGz$

      $+Iv)^l<`dkU9K78Yra%WI_PQP>=)&fHW|JSquzJ d6C34NL-I=!b2iR%W#k337##ER4zEmQ003vA76)k1``OeOk60p@u&+UyJKG7;gzWj E0JmEaxBvhE diff --git a/nummi/history/locale/fr_FR/LC_MESSAGES/django.po b/nummi/history/locale/fr_FR/LC_MESSAGES/django.po index c0e51e2..ea07229 100644 --- a/nummi/history/locale/fr_FR/LC_MESSAGES/django.po +++ b/nummi/history/locale/fr_FR/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-03 15:51+0100\n" +"POT-Creation-Date: 2025-01-04 18:51+0100\n" "PO-Revision-Date: 2023-04-22 15:18+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -17,18 +17,22 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.2.2\n" -#: .\history\templates\history\plot.html:17 +#: .\history\templates\history\plot.html:10 +msgid "Year" +msgstr "Année" + +#: .\history\templates\history\plot.html:13 +msgid "Total" +msgstr "Total" + +#: .\history\templates\history\plot.html:56 msgid "Month" msgstr "Mois" -#: .\history\templates\history\plot.html:18 +#: .\history\templates\history\plot.html:57 msgid "Expenses" msgstr "Dépenses" -#: .\history\templates\history\plot.html:19 +#: .\history\templates\history\plot.html:58 msgid "Income" msgstr "Revenus" - -#: .\history\templates\history\plot.html:55 -msgid "Year" -msgstr "Année" diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo index 258a1ed85a97ab8bc46848e7e6b7e75135ee0dd7..78a2ba7ff6196d0d429a791a51e55bb200368672 100644 GIT binary patch delta 582 zcmXxhy)Q#i7{~F`+um}uRoc?VOKe#17g(ec28l&149HCzkqA04-82@7m*u2sVj>J| z5@wskU@(Y9Vvrck*nEGt*ONT`+^6T9`N8B?6?!Hx=8gw7xW^uR2nT40TJVW#_!ZSZ zsK(l*PMX099K=qXLp4}Jt*c@m?x1e+1hu}2Df*jB9{TYHb?bTZKlB0)5{6E%2%Dpt zup|sFBMfaoo63=D)=lcu>L3dCZhA?N>>|@TxlWxS?XELpADz+c+Dgr@Y;NzFvcKWi ee4CHY-d5s=G5edCwq!R delta 751 zcmYk)JxCj29LMn|=Os~-G}hXhXoqA_aS(^%sE}X=A+>`YDs+jLv;;L5B^k6sii?Yg zaFkLP2f@X$;8;3jD728hl@8eoZZ1L{{Qlxo=pX*~x##`m?nmrnqV;>&{a}cvHg9o) zybSRm>W(o{+`@j`!Fk-X^(VW(2iR z+Lp7Zi6?FS3{Fy>LoM_g!}u1N$ZVqeZ{rB=p!ywR96zD@onZ>kaVpd@rp=26UeT%z zTw@;pq83=-q54IPVG#%L6>8iX4q+7^KyRI&UT6hXI!807g1=IqosQzoHem;)lJJeUym`9{S^YI0G?JBqEV@630ROpH{ zxk9Jma(buJiFIvh@(HO8{~y|ju3aZpXhWQ%_g@WTf_zB!BIkL2v(_**g)<#3-8ThwC*>B()kd)IEDbLE8C(Ck;~a#7mu@k<;%}5I!HGK?&3OK1y-^9; aZZ`7VFIV4GgRZ+C&o#Yf?O$6l{QeGEWKeDZ diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.po b/nummi/main/locale/fr_FR/LC_MESSAGES/django.po index 4ff140a..e97a01e 100644 --- a/nummi/main/locale/fr_FR/LC_MESSAGES/django.po +++ b/nummi/main/locale/fr_FR/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-04 16:18+0100\n" +"POT-Creation-Date: 2025-01-04 18:51+0100\n" "PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -25,110 +25,105 @@ msgstr "Utilisateur" msgid "Skip to main content" msgstr "Aller au contenu principal" -#: .\main\templates\main\base.html:34 +#: .\main\templates\main\base.html:35 msgid "Home" msgstr "Accueil" -#: .\main\templates\main\base.html:39 +#: .\main\templates\main\base.html:42 .\main\templates\main\index.html:17 msgid "Statements" msgstr "Relevés" -#: .\main\templates\main\base.html:45 +#: .\main\templates\main\base.html:49 msgid "Transactions" msgstr "Transactions" -#: .\main\templates\main\base.html:51 .\main\templates\main\list.html:10 -#: .\main\templates\main\list.html:34 +#: .\main\templates\main\base.html:57 .\main\templates\main\list.html:10 +#: .\main\templates\main\list.html:37 msgid "Search" msgstr "Rechercher" -#: .\main\templates\main\base.html:54 -msgid "Log out" -msgstr "Se déconnecter" - -#: .\main\templates\main\base.html:59 .\main\templates\main\form\login.html:6 -#: .\main\templates\main\login.html:11 -msgid "Log in" -msgstr "Se connecter" - -#: .\main\templates\main\base.html:65 +#: .\main\templates\main\base.html:62 #, python-format msgid "Logged in as %(user)s" msgstr "Connecté en tant que %(user)s" +#: .\main\templates\main\base.html:66 +msgid "Log out" +msgstr "Se déconnecter" + +#: .\main\templates\main\base.html:74 .\main\templates\main\form\login.html:5 +#: .\main\templates\main\login.html:11 +msgid "Log in" +msgstr "Se connecter" + #: .\main\templates\main\confirm_delete.html:15 #, python-format msgid "Are you sure you want do delete %(object)s ?" msgstr "Êtes-vous sûr de vouloir supprimer %(object)s ?" -#: .\main\templates\main\confirm_delete.html:19 +#: .\main\templates\main\confirm_delete.html:20 msgid "Cancel" msgstr "Annuler" -#: .\main\templates\main\confirm_delete.html:20 +#: .\main\templates\main\confirm_delete.html:21 msgid "Confirm" msgstr "Confirmer" -#: .\main\templates\main\form\fileinput.html:8 +#: .\main\templates\main\form\fileinput.html:6 msgid "File" msgstr "Fichier" -#: .\main\templates\main\form\form_base.html:30 -msgid "Delete" -msgstr "Supprimer" - -#: .\main\templates\main\form\form_base.html:32 -msgid "Reset" -msgstr "Réinitialiser" - -#: .\main\templates\main\form\form_base.html:34 +#: .\main\templates\main\form\form_base.html:46 msgid "Create" msgstr "Créer" -#: .\main\templates\main\form\form_base.html:36 +#: .\main\templates\main\form\form_base.html:48 msgid "Save" msgstr "Enregistrer" +#: .\main\templates\main\form\form_base.html:50 +msgid "Reset" +msgstr "Réinitialiser" + +#: .\main\templates\main\form\form_base.html:52 +msgid "Delete" +msgstr "Supprimer" + #: .\main\templates\main\index.html:13 msgid "Accounts" msgstr "Comptes" -#: .\main\templates\main\index.html:17 -msgid "Account" -msgstr "Compte" - -#: .\main\templates\main\index.html:18 -msgid "Balance" -msgstr "Solde" - -#: .\main\templates\main\index.html:19 .\main\templates\main\index.html:31 -msgid "Edit" -msgstr "Modifier" - -#: .\main\templates\main\index.html:36 -msgid "No account" -msgstr "Aucun compte" - -#: .\main\templates\main\index.html:43 -msgid "Create account" -msgstr "Créer un compte" - -#: .\main\templates\main\index.html:50 +#: .\main\templates\main\index.html:23 msgid "Categories" msgstr "Catégories" -#: .\main\templates\main\index.html:56 +#: .\main\templates\main\index.html:29 msgid "Create category" msgstr "Créer une catégorie" -#: .\main\templates\main\index.html:63 +#: .\main\templates\main\index.html:34 msgid "History" msgstr "Historique" -#: .\main\views.py:69 +#: .\main\views.py:54 msgid "was created successfully" msgstr "a été créé avec succès" +#~ msgid "Account" +#~ msgstr "Compte" + +#~ msgid "Balance" +#~ msgstr "Solde" + +#~ msgid "Edit" +#~ msgstr "Modifier" + +#~ msgid "No account" +#~ msgstr "Aucun compte" + +#~ msgid "Create account" +#~ msgstr "Créer un compte" + #~ msgid "Create statement" #~ msgstr "Créer un relevé" diff --git a/nummi/statement/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.mo index d4b610942111d253874c027edf8d6c8edff418c8..860f60ee66ba1c98b795a814d88d00adf088d797 100644 GIT binary patch literal 1401 zcmZ{iJ#W)M7{?E2c`@&W0umtI!kduO)NQ3IKt)>8Qi+r(gsKuq;M!;RTGx*5&M8Q& zh)=-42Vls|NViOg1y&@Om>7YLh5zF?X;j2X&hPHY^YVY5yKf^yuNl^9d>8O-WqNLDlP6-?aGE{tN29L4NsS2xL({iI!uz48?wj{O&ydD@= znXX@V+x5gg*VW%?)>xC8Z^RP}_|J=GuL`E?SbD>^sn1;!e?-u#1zn zn-O&(+4x^hB;nREbyo&{e!v;IpaPSffGQga|*)Zs4|{f16S$B?^I> zL3kXdZYb=Aa5qu5cP{ZjJ(;gt)x~OMN9=f^Xw4>kV=;I=eY-F8pj{)0Nm50RtJChku1-5k!Bw{ct3pwLY4Aw&Z8U8>1|GYJ>Q}fo~%$Ab0CEWC%2*1-_O-(OI#?nX%%iHM@uUvQ%ub1nz*;H~9LNjL6MSRjAPM7m%H_4!}-?Q-E%;c zsi_bWEu|~?02ByQ(9%#M_ySaPAjE&p=ZBJ!*T0>)*?H}LA0K_qAm_0zVSUEBj1?Wg zALIu(4*mp>fWN`h;9oEYj*WEn32+qpBuMr=coe)19s}pI?KY>?K%ePGbcaX8eu#cmd3GgCFab55XI1A#(%9(vV zqX$x54UqbL0iFVPGy5y>B=onL{XIy2A3^H#3rO?(3R0cFK&s;pNcE4PXq1`Gnf$4C z5{f}Wxk%ui&I9eFbyH20mu5mjcR_+S`$BoBH@X*^4do{}M}JqY1)dd?h8qhuL~Pld z@2i9u1l(#bHlA;#iVa(UN2s(E*lGb~=+GimwkYd$p+)QqR*C~ok<(4fixR<>WF**{ z*cm#triPTq*3-6osJrbR!#*plzcZ{lsEY@KtV3qQYH8M*L`E-)28j$SYqc$W>nsOO zO=yGWiab{lO{J}~YC;(}^GRr&s)D)v(5Z-)BqxWOEONKtPCJF0j_dNNTSeEsUT_PA z+=^$clje^+t8|f0osXlL_C@Y@X8_md*@g43Xn-TWjdUeT}8Tir2 za6L{b_EjL`u*hdNrOiEY9*9`zZqa2W0%`el-p#u?RuPeCb=o{gc<-IC1=VQcTIaS^ zVuj{`F#8@!r`>-goi==unCosN)_*3QcK4l-0YP|O#&lsK(X_<|&&A)ueAt>wTEa{E zA?6Z)Pe3{!blRJV?S+uBw332k9rS3@Cz|EnU;EzYgKAh!NzJWF43E%i)`wyL0hAy? A82|tP diff --git a/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po index f969e8b..6068d95 100644 --- a/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po +++ b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-03 15:51+0100\n" +"POT-Creation-Date: 2025-01-04 18:51+0100\n" "PO-Revision-Date: 2023-04-22 15:22+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.2.2\n" -#: .\statement\forms.py:28 +#: .\statement\forms.py:43 msgid "Add transactions" msgstr "Ajouter des transactions" @@ -37,36 +37,53 @@ msgstr "Valeur finale" msgid "Start value" msgstr "Valeur initiale" -#: .\statement\models.py:29 -#: .\statement\templates\statement\statement_table.html:30 -msgid "Difference" -msgstr "Différence" - -#: .\statement\models.py:36 -msgid "Transaction difference" -msgstr "Différence des transactions" - -#: .\statement\models.py:42 +#: .\statement\models.py:28 msgid "File" msgstr "Fichier" -#: .\statement\models.py:49 +#: .\statement\models.py:35 #, python-format msgid "%(date)s statement" msgstr "Relevé du %(date)s" -#: .\statement\models.py:89 +#: .\statement\models.py:68 msgid "Statement" msgstr "Relevé" -#: .\statement\models.py:90 +#: .\statement\models.py:69 #: .\statement\templates\statement\statement_list.html:4 #: .\statement\templates\statement\statement_list.html:7 msgid "Statements" msgstr "Relevés" +#: .\statement\templates\statement\confirm_delete.html:5 +msgid "This will delete all transactions in this statement." +msgstr "Ceci va supprimer toutes les transactions de ce relevé." + +#: .\statement\templates\statement\statement_detail.html:19 +#: .\statement\templates\statement\statement_detail.html:30 +msgid "Edit statement" +msgstr "Modifier le relevé" + +#: .\statement\templates\statement\statement_detail.html:31 +#: .\statement\templates\statement\statement_detail.html:61 +msgid "Add transaction" +msgstr "Ajouter une transaction" + +#: .\statement\templates\statement\statement_detail.html:57 +msgid "Transactions" +msgstr "Transactions" + +#: .\statement\templates\statement\statement_detail.html:62 +msgid "View all transactions" +msgstr "Afficher toutes les transactions" + +#: .\statement\templates\statement\statement_detail.html:67 +msgid "Categories" +msgstr "Catégories" + #: .\statement\templates\statement\statement_form.html:4 -#: .\statement\templates\statement\statement_table.html:18 +#: .\statement\templates\statement\statement_table.html:7 msgid "Create statement" msgstr "Créer un relevé" @@ -74,34 +91,28 @@ msgstr "Créer un relevé" msgid "New statement" msgstr "Nouveau relevé" -#: .\statement\templates\statement\statement_form.html:23 -msgid "Categories" -msgstr "Catégories" +#: .\statement\templates\statement\statement_form.html:12 +msgid "Back" +msgstr "Retour" -#: .\statement\templates\statement\statement_form.html:27 -#: .\statement\templates\statement\statement_table.html:31 -msgid "Transactions" -msgstr "Transactions" - -#: .\statement\templates\statement\statement_table.html:25 -msgid "Date" -msgstr "Date" - -#: .\statement\templates\statement\statement_table.html:27 -msgid "Account" -msgstr "Compte" - -#: .\statement\templates\statement\statement_table.html:29 -msgid "Value" -msgstr "Valeur" - -#: .\statement\templates\statement\statement_table.html:62 -msgid "No statement" -msgstr "Aucun relevé" - -#: .\statement\templates\statement\statement_table.html:70 +#: .\statement\templates\statement\statement_table.html:28 msgid "View all statements" msgstr "Voir tous les relevés" +#~ msgid "Difference" +#~ msgstr "Différence" + +#~ msgid "Transaction difference" +#~ msgstr "Différence des transactions" + +#~ msgid "Date" +#~ msgstr "Date" + +#~ msgid "Account" +#~ msgstr "Compte" + +#~ msgid "Value" +#~ msgstr "Valeur" + #~ msgid "No transaction" #~ msgstr "Aucune transaction" diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo index cf6c6811b2a425ceddb1e39654b793780693ed22..1a322027d0cdc9900c9d58538267e3772346df61 100644 GIT binary patch literal 2196 zcmZvcO>7%Q7={OE3pkWQfI^{^X$u4zt?M{|QkNnU$AKUcjAJ6CN}%a_>`c4fb!TVO z1_^OMa6p1vxq-MKo)LVyGa`Gf@Og(LKU5H|#;asi3=U9X)DV&re0`PupA`x{@G z+;vHyO+yYs+%-a+1i!x)9opyD32{C61vmkI3+@Ad0C#~ug9Y#x@CNXAa5s1ryczr( zoCGKEF~2{AZVz}g<1vu>XF%#d0pcfW=xza@0q+1$gVcW>#813}j(T1LdH&6e?_}%m zX7l$zp8F8QPkfAy_k0S{k8eQQc^RahE8vabZy^2n6XZQtv;BXv{oUB)`aY2Q_JcQp zM?n0`yEJqKZ1eQ96XTsJOl ze*z-K;!BYE`Wn0)ybRLLUqPPx3uJsaVZ8T()O!%*{Zk-55i=m|J&~~n^4#+v{d)<- zPw0#x$hy4%BCKh92>k&F<3n56Xd@f%VrfeV^|9RtVGP;sf!v#phTS3HF35wBW5Xr! za7J92<`k(EM<9$R8{@#*VmmxU>MQe1Ul^CY5ZZk=DXBn#>#gl3dMr*&xxvXx)WKWO%@B*HP3$7OC6+@!>S|!fpR#ziP4Xpy%qB9L0 zYbVy=zP};XBJ1RtEwSdXm44W?ss($1aS(IkSgflc(Ze1gNoUd%>jq}1Ajsbxi@J@@ zYTtRwEpJ`h7zwG!LPPg(skah$Ov_tHIW( zT%|NST`HGKg=balywsvVAyO4tYju>B^`cxz?70mEe;#-6AxR71NzL7!(bno>+dhHg zVJA@??KQRP;@Z)?BHQ-mrAA?8d8M{(>`bv#s79ef1ij`~4@NnC(H-vv%7iDSkJ}K- zQ!g}^yt(b~7`e8#UJWU1A?p=6cgDEFi{4W@)OKieJ<=?e*?IAPsXQKqQ zAd^tbHVQbgJJ;aWI42G5q67u3+@QZ*+u2yu+BlCaR_&nwABDN4yuSad^FbelzO&9( z;87dV6r+I*R6gedC9e}JgUH6Rm2k@p3yZV1@i%yQanK(>f#C{YfyK^{qi0~wqtqJO z*Us^|WFg6jw-9w9$)*m1?SPFJ^4`rSK7*QHsw*=r`1ElzPS7G6gT4ukGb%7K%)#9o z8t(+dCjLM0mhq?2(1G3@^qIA;wu6rpo>w!Mn%p>jE{F%;3B!^;7!L4aB4Z_T2+w2y J$=Mc*e*wFy?lu4b delta 811 zcmY+?PiPZC6vy#tvYSoYR89Z*r++Mn5K0fC_SS<4X~3gM=|vB+jCCPR;wCAtQc&=2Jqn%_6hbNV8t^1YLGb&Fi_`~`&+P8Zd-Ha)o$ut@$D`hkA!ZoU z3~#`gIqae0b;_6kx6s2q9KwCfVh1naTO7p?sC8d3gWqu&Pf+W7$@8B`-nKboqJ^pS z-$H|^g?Ut>NmPPayomFt11zEnmrw^>#zB0NxQfdA0=2(|IedjaZs7#=%`TGy-5#Je ze5I=FqBfqQ3jRQKc!rm8ge*+WO;q7I)cU*0^*z+V9-#I=L^bv}xo>q%eY3`d+GZUu z;Y%FH4OEA3P=yas364;md_ooYhAMoL*h`-OLN(&^qfeVdeO0Z^Gjyo-BtJ^7;7jy3 zT)_$5Ak?Ws3G^RPVZ~L34y^;~uzC~cgWCO2Lu1KYhgaw`>WvXI{SKy>jWDk1mg0UG zR-2WE>!zpN?{s0-mTkkDqOFHc F=|6TUUR3}9 diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po index 2266d5a..15336a6 100644 --- a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po +++ b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-03 15:51+0100\n" +"POT-Creation-Date: 2025-01-04 18:51+0100\n" "PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -17,103 +17,119 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.2.2\n" -#: .\transaction\models.py:19 .\transaction\models.py:82 +#: .\transaction\forms.py:175 +msgid "Search" +msgstr "Rechercher" + +#: .\transaction\forms.py:177 +msgid "Sort by" +msgstr "Trier par" + +#: .\transaction\forms.py:179 +msgid "Default" +msgstr "Défaut" + +#: .\transaction\forms.py:180 +msgid "Date +" +msgstr "Date +" + +#: .\transaction\forms.py:181 +msgid "Date -" +msgstr "Date -" + +#: .\transaction\forms.py:182 +msgid "Value +" +msgstr "Valeur +" + +#: .\transaction\forms.py:183 +msgid "Value -" +msgstr "Valeur -" + +#: .\transaction\models.py:18 .\transaction\models.py:63 msgid "Transaction" msgstr "Transaction" -#: .\transaction\models.py:19 .\transaction\models.py:89 -#: .\transaction\templates\transaction\invoice_table.html:10 -#: .\transaction\templates\transaction\transaction_table.html:32 +#: .\transaction\models.py:18 .\transaction\models.py:70 +#: .\transaction\templates\transaction\transaction_table.html:19 msgid "Name" msgstr "Nom" -#: .\transaction\models.py:21 +#: .\transaction\models.py:20 msgid "Description" msgstr "Description" -#: .\transaction\models.py:23 +#: .\transaction\models.py:22 msgid "Value" msgstr "Valeur" -#: .\transaction\models.py:25 -#: .\transaction\templates\transaction\transaction_table.html:31 +#: .\transaction\models.py:24 +#: .\transaction\templates\transaction\transaction_table.html:18 msgid "Date" msgstr "Date" -#: .\transaction\models.py:26 +#: .\transaction\models.py:25 msgid "Real date" msgstr "Date réelle" -#: .\transaction\models.py:28 -#: .\transaction\templates\transaction\transaction_table.html:35 +#: .\transaction\models.py:27 +#: .\transaction\templates\transaction\transaction_table.html:22 msgid "Trader" msgstr "Commerçant" -#: .\transaction\models.py:31 +#: .\transaction\models.py:30 msgid "Payment" msgstr "Paiement" -#: .\transaction\models.py:38 -#: .\transaction\templates\transaction\transaction_table.html:37 +#: .\transaction\models.py:37 +#: .\transaction\templates\transaction\transaction_table.html:24 msgid "Category" msgstr "Catégorie" -#: .\transaction\models.py:43 +#: .\transaction\models.py:44 msgid "Statement" msgstr "Relevé" -#: .\transaction\models.py:48 -#: .\transaction\templates\transaction\transaction_table.html:40 -msgid "Account" -msgstr "Compte" - -#: .\transaction\models.py:83 -#: .\transaction\templates\transaction\transaction_archive_month.html:27 +#: .\transaction\models.py:64 +#: .\transaction\templates\transaction\transaction_archive_month.html:25 #: .\transaction\templates\transaction\transaction_archive_year.html:31 #: .\transaction\templates\transaction\transaction_list.html:4 -#: .\transaction\templates\transaction\transaction_list.html:7 +#: .\transaction\templates\transaction\transaction_list.html:11 msgid "Transactions" msgstr "Transactions" -#: .\transaction\models.py:89 .\transaction\models.py:122 +#: .\transaction\models.py:70 .\transaction\models.py:103 msgid "Invoice" msgstr "Facture" -#: .\transaction\models.py:94 -#: .\transaction\templates\transaction\invoice_table.html:11 -#: .\transaction\templates\transaction\invoice_table.html:22 +#: .\transaction\models.py:75 msgid "File" msgstr "Fichier" -#: .\transaction\models.py:123 -#: .\transaction\templates\transaction\transaction_form.html:20 +#: .\transaction\models.py:104 +#: .\transaction\templates\transaction\transaction_detail.html:46 msgid "Invoices" msgstr "Factures" #: .\transaction\templates\transaction\invoice_form.html:4 -#: .\transaction\templates\transaction\invoice_table.html:37 msgid "Create invoice" msgstr "Créer une facture" #: .\transaction\templates\transaction\invoice_form.html:7 +#: .\transaction\templates\transaction\invoice_table.html:12 msgid "New invoice" msgstr "Nouvelle facture" -#: .\transaction\templates\transaction\invoice_table.html:12 -#: .\transaction\templates\transaction\invoice_table.html:25 -msgid "Delete" -msgstr "Supprimer" +#: .\transaction\templates\transaction\invoice_table.html:7 +msgid "Edit" +msgstr "Modifier" -#: .\transaction\templates\transaction\invoice_table.html:30 -msgid "No invoice" -msgstr "Aucune facture" - -#: .\transaction\templates\transaction\transaction_archive_month.html:14 +#: .\transaction\templates\transaction\transaction_archive_month.html:13 #: .\transaction\templates\transaction\transaction_archive_year.html:13 +#: .\transaction\templates\transaction\transaction_form.html:18 msgid "Back" msgstr "Retour" -#: .\transaction\templates\transaction\transaction_archive_month.html:22 +#: .\transaction\templates\transaction\transaction_archive_month.html:20 #: .\transaction\templates\transaction\transaction_archive_year.html:26 msgid "Categories" msgstr "Catégories" @@ -122,8 +138,31 @@ msgstr "Catégories" msgid "History" msgstr "Historique" +#: .\transaction\templates\transaction\transaction_detail.html:39 +msgid "Edit transaction" +msgstr "Modifier la transaction" + +#: .\transaction\templates\transaction\transaction_detail.html:57 +msgid "Add invoice" +msgstr "Ajouter une facture" + +#: .\transaction\templates\transaction\transaction_filters.html:3 +msgid "Filters" +msgstr "Filtres" + +#: .\transaction\templates\transaction\transaction_filters.html:12 +msgid "Filter" +msgstr "Filtrer" + +#: .\transaction\templates\transaction\transaction_filters.html:13 +msgid "Reset" +msgstr "Réinitialiser" + +#: .\transaction\templates\transaction\transaction_filters.html:15 +msgid "Clear" +msgstr "Effacer" + #: .\transaction\templates\transaction\transaction_form.html:5 -#: .\transaction\templates\transaction\transaction_table.html:25 msgid "Create transaction" msgstr "Créer une transaction" @@ -131,18 +170,33 @@ msgstr "Créer une transaction" msgid "New transaction" msgstr "Nouvelle transaction" -#: .\transaction\templates\transaction\transaction_table.html:33 +#: .\transaction\templates\transaction\transaction_list.html:15 +msgid "Add transaction" +msgstr "Ajouter une transaction" + +#: .\transaction\templates\transaction\transaction_table.html:20 msgid "Expenses" msgstr "Dépenses" -#: .\transaction\templates\transaction\transaction_table.html:34 +#: .\transaction\templates\transaction\transaction_table.html:21 msgid "Income" msgstr "Recettes" -#: .\transaction\templates\transaction\transaction_table.html:87 +#: .\transaction\templates\transaction\transaction_table.html:27 +msgid "Account" +msgstr "Compte" + +#: .\transaction\templates\transaction\transaction_table.html:74 msgid "No transaction" msgstr "Aucune transaction" -#: .\transaction\templates\transaction\transaction_table.html:95 +#: .\transaction\templates\transaction\transaction_table.html:82 msgid "View all transactions" msgstr "Voir toutes les transactions" + +#: .\transaction\views.py:101 +msgid "Error processing file" +msgstr "Erreur lors du traitement du fichier" + +#~ msgid "Delete" +#~ msgstr "Supprimer" From 02c53c7daba566d4d87b5bbf77311906c6e81972 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 20:28:32 +0100 Subject: [PATCH 306/325] Fix template variable references in transaction detail view --- .../templates/transaction/transaction_detail.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nummi/transaction/templates/transaction/transaction_detail.html b/nummi/transaction/templates/transaction/transaction_detail.html index 7e58c0f..065a7bc 100644 --- a/nummi/transaction/templates/transaction/transaction_detail.html +++ b/nummi/transaction/templates/transaction/transaction_detail.html @@ -16,17 +16,17 @@

      {{ transaction }}

        - {% if statement %} + {% if transaction.statement %}
      • - {% with statement.account as account %} + {% with transaction.statement.account as account %} {{ account.icon|remix }}{{ account }} – {% endwith %} - {{ statement }} + {{ transaction.statement }}
      • {% endif %} - {% if category %} + {% if transaction.category %}
      • - {{ category.icon|remix }}{{ category }} + {{ transaction.category.icon|remix }}{{ transaction.category }}
      • {% endif %} {% if transaction.trader %} From d44407d9ab64e49464ec0641451b8148af24f7b9 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 21:14:46 +0100 Subject: [PATCH 307/325] Refactor transaction URL handling and enhance filter form functionality Close #43 --- .../templates/category/category_detail.html | 2 +- .../templates/category/category_plot.html | 4 +-- nummi/main/static/main/js/base.js | 20 ++++++++++- nummi/main/templatetags/main_extras.py | 19 ++++++++++ .../templates/statement/statement_detail.html | 2 +- nummi/transaction/forms.py | 9 +++++ .../transaction/transaction_filters.html | 36 ++++++++++--------- nummi/transaction/views.py | 2 ++ pkgbuild/PKGBUILD | 1 + 9 files changed, 73 insertions(+), 22 deletions(-) diff --git a/nummi/category/templates/category/category_detail.html b/nummi/category/templates/category/category_detail.html index 10d3a80..7768101 100644 --- a/nummi/category/templates/category/category_detail.html +++ b/nummi/category/templates/category/category_detail.html @@ -15,7 +15,7 @@

        {% translate "Transactions" %}

        - {% url "category_transactions" category.id as t_url %} + {% url_get "transactions" category=category.id as t_url %}

        {{ "list-check"|remixnl }}{% translate "View all transactions" %}

        diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index fd80fc3..1ffa4f7 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -22,9 +22,9 @@ {% if cat.category %} {% if month %} - {{ cat.category__icon|remix }}{{ cat.category__name }} + {{ cat.category__icon|remix }}{{ cat.category__name }} {% elif year %} - {{ cat.category__icon|remix }}{{ cat.category__name }} + {{ cat.category__icon|remix }}{{ cat.category__name }} {% else %} {{ cat.category__icon|remix }}{{ cat.category__name }} {% endif %} diff --git a/nummi/main/static/main/js/base.js b/nummi/main/static/main/js/base.js index 2b41ee2..2f760d6 100644 --- a/nummi/main/static/main/js/base.js +++ b/nummi/main/static/main/js/base.js @@ -153,9 +153,27 @@ if (accounts) { const filterForm = document.querySelector("form.filter"); if (filterForm) { + const accountSelect = filterForm.querySelector("[name='account']"); + const statementSelect = filterForm.querySelector("[name='statement']"); + if (!statementSelect.disabled) { + accountSelect.addEventListener("input", (event) => { + statementSelect.value = ""; + statementSelect.disabled = true; + }); + filterForm.addEventListener("reset", (event) => { + statementSelect.disabled = false; + }); + } + let disableStatement = false; filterForm.addEventListener("submit", (event) => { for (element of filterForm.elements) { - if (element.value == "") { + if ( + element.value == "" || + (disableStatement && element.name == "statement") + ) { + if (element.name == "account") { + disableStatement = true; + } element.disabled = true; } } diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 1b1e5dc..34b5182 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -1,5 +1,9 @@ +from urllib import parse + +from dateutil.relativedelta import relativedelta from django import template from django.templatetags.static import static +from django.urls import reverse from django.utils import formats from django.utils.safestring import mark_safe @@ -118,3 +122,18 @@ def page_url(context, page): query = context["request"].GET.copy() query["page"] = page return query.urlencode() + + +@register.simple_tag +def url_get(name, **kwargs): + return f"{reverse(name)}?{parse.urlencode(kwargs)}" + + +@register.filter +def end_of_month(month): + return month + relativedelta(months=1, days=-1) + + +@register.filter +def end_of_year(year): + return year + relativedelta(years=1, days=-1) diff --git a/nummi/statement/templates/statement/statement_detail.html b/nummi/statement/templates/statement/statement_detail.html index 232b22f..fa963e1 100644 --- a/nummi/statement/templates/statement/statement_detail.html +++ b/nummi/statement/templates/statement/statement_detail.html @@ -55,7 +55,7 @@

{% translate "Transactions" %}

- {% url "statement_transactions" statement.id as t_url %} + {% url_get "transactions" account=account.id statement=statement.id as t_url %}

{{ "add-circle"|remix }}{% translate "Add transaction" %} diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index 16110a0..a093bbc 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -172,6 +172,7 @@ class TransactionFiltersForm(forms.Form): account = forms.ModelChoiceField( queryset=None, required=False, widget=AccountSelect() ) + statement = forms.ModelChoiceField(queryset=None, required=False) search = forms.CharField(label=_("Search"), required=False) sort_by = forms.ChoiceField( label=_("Sort by"), @@ -191,6 +192,14 @@ class TransactionFiltersForm(forms.Form): self.fields["category"].queryset = _user.category_set self.fields["account"].queryset = _user.account_set + print(kwargs.get("initial")) + if acc_id := kwargs.get("initial", {}).get("account"): + self.fields["statement"].queryset = ( + self.fields["account"].queryset.get(id=acc_id).statement_set + ) + else: + self.fields["statement"].queryset = _user.statement_set.none() + self.fields["statement"].disabled = True self.fields["category"].widget.attrs |= { "class": "category", diff --git a/nummi/transaction/templates/transaction/transaction_filters.html b/nummi/transaction/templates/transaction/transaction_filters.html index 907a14f..f29766d 100644 --- a/nummi/transaction/templates/transaction/transaction_filters.html +++ b/nummi/transaction/templates/transaction/transaction_filters.html @@ -1,19 +1,21 @@ {% load i18n %} -

- {% translate "Filters" %} -
- {% for field in form %} -
- - {{ field }} +{% if form %} +
+ {% translate "Filters" %} + + {% for field in form %} +
+ + {{ field }} +
+ {% endfor %} +
+ + + {% if filters %} + {% translate "Clear" %} + {% endif %}
- {% endfor %} -
- - - {% if filters %} - {% translate "Clear" %} - {% endif %} -
- -
+ +
+{% endif %} diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 41fd184..a928f01 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -190,6 +190,8 @@ class TransactionListView(NummiListView): queryset = queryset.filter(category=category) if account := self.request.GET.get("account"): queryset = queryset.filter(statement__account=account) + if statement := self.request.GET.get("statement"): + queryset = queryset.filter(statement=statement) if search := self.request.GET.get("search"): queryset = ( queryset.annotate( diff --git a/pkgbuild/PKGBUILD b/pkgbuild/PKGBUILD index 16ac39e..88e06a0 100644 --- a/pkgbuild/PKGBUILD +++ b/pkgbuild/PKGBUILD @@ -10,6 +10,7 @@ depends=( "python-django" "python-toml" "python-psycopg" + "python-dateutil" ) makedepends=("git") optdepends=("postgresql: database") From d5292911c23bd1e2b8666100324d2e5fc43f5b38 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 21:57:21 +0100 Subject: [PATCH 308/325] Refactor models to inherit from NummiModel and implement custom query sets for enhanced search functionality --- nummi/account/models.py | 6 +-- .../templates/account/account_table.html | 4 +- nummi/category/models.py | 4 +- nummi/main/models.py | 33 ++++++++++++- .../search/templates/search/search_form.html | 5 +- .../templates/search/search_results.html | 47 +++++++++++++++++++ nummi/search/views.py | 40 +++++----------- nummi/transaction/models.py | 16 +++++-- nummi/transaction/views.py | 22 +-------- 9 files changed, 116 insertions(+), 61 deletions(-) create mode 100644 nummi/search/templates/search/search_results.html diff --git a/nummi/account/models.py b/nummi/account/models.py index 07ee795..c1203de 100644 --- a/nummi/account/models.py +++ b/nummi/account/models.py @@ -4,10 +4,10 @@ from django.apps import apps from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from main.models import UserModel +from main.models import NummiModel -class Account(UserModel): +class Account(NummiModel): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name")) icon = models.SlugField( @@ -46,7 +46,7 @@ class Account(UserModel): verbose_name_plural = _("Accounts") -class AccountModel(UserModel): +class AccountModel(NummiModel): account = models.ForeignKey( Account, on_delete=models.CASCADE, diff --git a/nummi/account/templates/account/account_table.html b/nummi/account/templates/account/account_table.html index 0de11e9..f938ad1 100644 --- a/nummi/account/templates/account/account_table.html +++ b/nummi/account/templates/account/account_table.html @@ -1,7 +1,7 @@ {% load i18n main_extras %}
{% for acc in accounts %} -