From 79f87779dd416859e8e50e40a19595ddf7077f77 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 1 Jan 2025 19:05:34 +0100 Subject: [PATCH 01/60] Fix display on index page --- nummi/main/static/main/css/main.css | 1 - 1 file changed, 1 deletion(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index dd87b3d..85fad38 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -338,7 +338,6 @@ ul.messages { .accounts { display: grid; - grid-row-gap: 0.5rem; grid-auto-rows: min-content; dl { From 8575f43475398744be2f104d6865e1ed38436081 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 09:37:47 +0100 Subject: [PATCH 02/60] Fix transaction table with no account --- .../templates/transaction/transaction_table.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index 9364ec1..396c511 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -62,17 +62,17 @@ {% if trans.value < 0 %}{% endif %} {{ trans.trader|default_if_none:"" }} {% if not category %} - {% if trans.category %} - + + {% if trans.category %} {{ trans.category.icon|remix }}{{ trans.category }} - - {% else %} - - {% endif %} + {% endif %} + {% endif %} {% if not account %} - {{ trans.account.icon|remix }}{{ trans.account }} + {% if trans.account %} + {{ trans.account.icon|remix }}{{ trans.account|default_if_none:"" }} + {% endif %} {% endif %} From 57d5330d75619b664236dfaffbe72f095dbeed9b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 10:07:01 +0100 Subject: [PATCH 03/60] Create account list view --- .../templates/account/account_list.html | 17 ++++++++ .../templates/account/account_table.html | 41 ++++++++++++++++++ nummi/account/templatetags/__init__.py | 0 nummi/account/templatetags/account_extras.py | 10 +++++ nummi/account/urls.py | 1 + nummi/account/views.py | 6 +++ nummi/main/static/main/css/main.css | 42 +++++++++---------- nummi/main/static/main/js/index.js | 7 ++-- nummi/main/templates/main/index.html | 36 +++------------- nummi/main/views.py | 2 +- nummi/statement/views.py | 1 + nummi/transaction/views.py | 1 + 12 files changed, 105 insertions(+), 59 deletions(-) create mode 100644 nummi/account/templates/account/account_list.html create mode 100644 nummi/account/templates/account/account_table.html create mode 100644 nummi/account/templatetags/__init__.py create mode 100644 nummi/account/templatetags/account_extras.py diff --git a/nummi/account/templates/account/account_list.html b/nummi/account/templates/account/account_list.html new file mode 100644 index 0000000..9193e05 --- /dev/null +++ b/nummi/account/templates/account/account_list.html @@ -0,0 +1,17 @@ +{% extends "main/list.html" %} +{% load i18n main_extras account_extras %} +{% block link %} + {{ block.super }} + {% css "main/css/table.css" %} + {% css "main/css/plot.css" %} + {% js "main/js/index.js" %} +{% endblock link %} +{% block name %} + {% translate "Account" %} +{% endblock name %} +{% block h2 %} + {% translate "Accounts" %} +{% endblock h2 %} +{% block table %} +
{% account_table accounts %}
+{% endblock table %} diff --git a/nummi/account/templates/account/account_table.html b/nummi/account/templates/account/account_table.html new file mode 100644 index 0000000..25655b7 --- /dev/null +++ b/nummi/account/templates/account/account_table.html @@ -0,0 +1,41 @@ +{% load i18n main_extras %} +
+ {% for acc in accounts %} + + {% endfor %} + {% if index %} + + {% else %} + + + {% endif %} +
diff --git a/nummi/account/templatetags/__init__.py b/nummi/account/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/account/templatetags/account_extras.py b/nummi/account/templatetags/account_extras.py new file mode 100644 index 0000000..5e6c478 --- /dev/null +++ b/nummi/account/templatetags/account_extras.py @@ -0,0 +1,10 @@ +from django import template + +register = template.Library() + + +@register.inclusion_tag("account/account_table.html") +def account_table(accounts, **kwargs): + return kwargs | { + "accounts": accounts, + } diff --git a/nummi/account/urls.py b/nummi/account/urls.py index f6b4f2b..93713af 100644 --- a/nummi/account/urls.py +++ b/nummi/account/urls.py @@ -5,6 +5,7 @@ from transaction.views import TransactionMonthView, TransactionYearView from . import views urlpatterns = [ + path("list", views.AccountListView.as_view(), name="accounts"), path("new", views.AccountCreateView.as_view(), name="new_account"), path("", views.AccountDetailView.as_view(), name="account"), path("/edit", views.AccountUpdateView.as_view(), name="edit_account"), diff --git a/nummi/account/views.py b/nummi/account/views.py index 103832b..c3cbc36 100644 --- a/nummi/account/views.py +++ b/nummi/account/views.py @@ -5,6 +5,7 @@ from main.views import ( NummiCreateView, NummiDeleteView, NummiDetailView, + NummiListView, NummiUpdateView, ) from statement.views import StatementListView @@ -66,6 +67,11 @@ class AccountMixin: return super().get_context_data(**kwargs) | {"account": self.account} +class AccountListView(NummiListView): + model = Account + context_object_name = "accounts" + + class AccountTListView(AccountMixin, TransactionListView): pass diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 85fad38..b1b383d 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -336,35 +336,31 @@ ul.messages { } } -.accounts { - display: grid; - grid-auto-rows: min-content; +dl.accounts { + margin: 0; - dl { + dt, + dd { margin: 0; - dt, - dd { - margin: 0; - } - .account { - padding: 0.5rem; - margin-bottom: 0.5rem; - border: 1px solid var(--gray); + } + .account { + padding: 0.5rem; + margin-bottom: 0.5rem; + border: 1px solid var(--gray); - display: grid; - grid-template-columns: 1fr min-content; - &.new, - &.more { - border-style: dashed; - } - &.more label { - display: block; - } + display: grid; + grid-template-columns: 1fr min-content; + &.new, + &.more { + border-style: dashed; } - &:not(.show-archive) .account.archived { - display: none; + &.more label { + display: block; } } + &:not(.show-archive) .account.archived { + display: none; + } } ul.statements, diff --git a/nummi/main/static/main/js/index.js b/nummi/main/static/main/js/index.js index 88438b2..550651b 100644 --- a/nummi/main/static/main/js/index.js +++ b/nummi/main/static/main/js/index.js @@ -1,10 +1,9 @@ -let accounts = document.querySelector(".accounts"); +let accounts = document.querySelector("dl.accounts"); let toggle = accounts.querySelector("input#show-archived-accounts"); -let dl = accounts.querySelector("dl"); -dl.classList.toggle("show-archive", toggle.checked); +accounts.classList.toggle("show-archive", toggle.checked); if (toggle) { toggle.addEventListener("change", (event) => { - dl.classList.toggle("show-archive", toggle.checked); + accounts.classList.toggle("show-archive", toggle.checked); }); } diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 0a11f61..3b82d2f 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -1,6 +1,6 @@ {% extends "main/base.html" %} {% load static %} -{% load main_extras %} +{% load main_extras account_extras %} {% load i18n %} {% block link %} {{ block.super }} @@ -10,42 +10,16 @@ {% endblock link %} {% block body %}
-
+

{% translate "Accounts" %}

-
- {% for acc in accounts %} - - {% endfor %} - - -
+ {% account_table accounts index=True total=accounts|balance %}
-
+

{% translate "Statements" %}

{% include "statement/statement_table.html" %}
-
+

{% translate "Categories" %}

{% spaceless %}

diff --git a/nummi/main/views.py b/nummi/main/views.py index 2786ed9..0dc8b3f 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -105,4 +105,4 @@ class LogoutView(auth_views.LogoutView): class NummiListView(UserMixin, ListView): - paginate_by = 96 + pass diff --git a/nummi/statement/views.py b/nummi/statement/views.py index 426436a..238d296 100644 --- a/nummi/statement/views.py +++ b/nummi/statement/views.py @@ -84,6 +84,7 @@ class StatementDetailView(NummiDetailView): class StatementListView(NummiListView): model = Statement context_object_name = "statements" + paginate_by = 32 class StatementMixin: diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 433177e..90e681d 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -124,6 +124,7 @@ class InvoiceDeleteView(NummiDeleteView): class TransactionListView(NummiListView): model = Transaction context_object_name = "transactions" + paginate_by = 50 class TransactionACMixin: From e8050fadb9600fc8f720038a7af7a05961855bca Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 10:16:05 +0100 Subject: [PATCH 04/60] Fix statement list, gaps --- nummi/main/static/main/css/main.css | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index b1b383d..e0e880c 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -81,7 +81,10 @@ a { main, nav, footer { - padding: 2rem; + padding: 2rem 1rem; + @media (width > 720px) { + padding: 2rem; + } } main { position: relative; @@ -94,8 +97,7 @@ main { .split { display: grid; - column-gap: var(--gap); - row-gap: var(--gap); + gap: var(--gap); grid-template-columns: 100%; @media (width > 720px) { grid-template-columns: minmax(20rem, max-content) 1fr; @@ -366,15 +368,15 @@ dl.accounts { ul.statements, ul.invoices { display: grid; - grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); grid-auto-rows: 1fr; - grid-gap: var(--gap); + gap: 0.5rem; list-style: none; padding: 0; li { display: grid; - grid-row-gap: var(--gap); + gap: 0.5rem; padding: var(--gap); border: var(--gray) 1px solid; text-align: right; @@ -423,7 +425,7 @@ ul.statements { overflow-x: auto; display: grid; grid-template-columns: repeat(4, min-content); - grid-gap: var(--gap); + gap: var(--gap); align-items: center; > span:nth-child(2) { @@ -483,8 +485,8 @@ ul.statements { .category { padding: 0 var(--gap); border: var(--gray) 1px solid; - margin-right: var(--gap); - margin-bottom: var(--gap); + margin-right: 0.5rem; + margin-bottom: 0.5rem; &.add { border-style: dashed; From cb939df81eb4ab7b661211d58e40e1b9c58a39bb Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 13:58:10 +0100 Subject: [PATCH 05/60] Fix #23, Fix #25 --- nummi/main/static/main/css/form.css | 22 ++++++++++++++++++---- nummi/main/static/main/css/main.css | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 440f13d..c39223c 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -7,7 +7,7 @@ form ul.errorlist { form { display: grid; - grid-gap: var(--gap); + gap: 0.5rem; grid-template-columns: repeat(auto-fill, 32rem); @media (width < 1024px) { grid-template-columns: 1fr; @@ -15,7 +15,7 @@ form { .column { display: grid; - grid-gap: var(--gap); + gap: 0.5rem; border: 1px solid var(--gray); padding: var(--gap); @@ -23,7 +23,7 @@ form { display: grid; grid-auto-columns: 1fr; grid-auto-flow: column; - grid-gap: inherit; + gap: inherit; padding: 0; margin: 0; border: none; @@ -32,11 +32,25 @@ form { .field { display: grid; grid-auto-rows: min-content; + align-items: center; overflow: hidden; + column-gap: 0.5rem; &:has(> textarea) { grid-template-rows: min-content 1fr; } + &:has(input[type="checkbox"]) { + grid-template-columns: min-content 1fr; + > label { + font-size: inherit; + grid-row: 1; + grid-column: 2; + } + > input { + grid-row: 1; + grid-column: 1; + } + } > label { font-size: 0.8rem; @@ -91,7 +105,7 @@ form { display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); grid-auto-rows: 1fr; - grid-gap: var(--gap); + gap: 0.5rem; align-items: center; input { diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index e0e880c..9181c72 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -373,6 +373,7 @@ ul.invoices { gap: 0.5rem; list-style: none; padding: 0; + margin: 0; li { display: grid; From bac3b593580b5ca2fcabbfaacb7940c8e1d04319 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 14:02:01 +0100 Subject: [PATCH 06/60] Fix #22 --- nummi/main/static/main/css/form.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index c39223c..a4412a2 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -68,6 +68,10 @@ form { background: none; z-index: 1; + &:not([type="checkbox"]) { + width: 100%; + } + &[name*="value"] { text-align: right; font-feature-settings: var(--num); From 090f1a3a5c716d3752a7d428add49f8061eff64f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 14:05:08 +0100 Subject: [PATCH 07/60] Fix #21 --- .../0004_remove_transaction_account.py | 16 ++++++++++++++++ nummi/transaction/models.py | 16 +++------------- 2 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 nummi/transaction/migrations/0004_remove_transaction_account.py diff --git a/nummi/transaction/migrations/0004_remove_transaction_account.py b/nummi/transaction/migrations/0004_remove_transaction_account.py new file mode 100644 index 0000000..0e6a7b9 --- /dev/null +++ b/nummi/transaction/migrations/0004_remove_transaction_account.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2 on 2025-01-02 13:03 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("transaction", "0003_alter_transaction_account_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="transaction", + name="account", + ), + ] diff --git a/nummi/transaction/models.py b/nummi/transaction/models.py index f831f22..0fb8dd8 100644 --- a/nummi/transaction/models.py +++ b/nummi/transaction/models.py @@ -2,7 +2,6 @@ import datetime from pathlib import Path from uuid import uuid4 -from account.models import Account from category.models import Category from django.core.validators import FileExtensionValidator from django.db import models @@ -44,22 +43,12 @@ class Transaction(UserModel): null=True, verbose_name=_("Statement"), ) - account = models.ForeignKey( - Account, - on_delete=models.CASCADE, - blank=True, - null=True, - verbose_name=_("Account"), - editable=False, - ) def save(self, *args, **kwargs): if Transaction.objects.filter(pk=self.pk): prev_self = Transaction.objects.get(pk=self.pk) else: prev_self = None - if self.statement: - self.account = self.statement.account super().save(*args, **kwargs) if prev_self is not None and prev_self.statement: prev_self.statement.update_sum() @@ -76,8 +65,9 @@ class Transaction(UserModel): return reverse("del_transaction", args=(self.pk,)) @property - def invoices(self): - return Invoice.objects.filter(transaction=self) + def account(self): + if self.statement: + return self.statement.account @property def has_invoice(self): From 786d7c213061356864baa5074288a4fb1cff018e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 14:19:06 +0100 Subject: [PATCH 08/60] Use templatetags for statement_table --- nummi/account/templates/account/account_detail.html | 12 +++++++----- nummi/account/views.py | 3 ++- nummi/main/templates/main/index.html | 4 ++-- .../templates/statement/statement_list.html | 10 +++++----- nummi/statement/templatetags/statement_extras.py | 7 +++++++ 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/nummi/account/templates/account/account_detail.html b/nummi/account/templates/account/account_detail.html index 183a2f1..fcd9321 100644 --- a/nummi/account/templates/account/account_detail.html +++ b/nummi/account/templates/account/account_detail.html @@ -1,12 +1,14 @@ {% extends "main/base.html" %} -{% load main_extras %} +{% load main_extras statement_extras %} {% load i18n %} -{% block title %}{{ object }} – {{ block.super }}{% endblock %} +{% block title %} + {{ object }} – {{ block.super }} +{% endblock title %} {% block link %} {{ block.super }} {% css "main/css/table.css" %} {% css "main/css/plot.css" %} -{% endblock %} +{% endblock link %} {% block body %}

{{ object.icon|remix }}{{ object }}

@@ -14,7 +16,7 @@

{% translate "Statements" %}

- {% include "statement/statement_table.html" %} + {% statement_table statements statements_url=statements_url new_statement_url=new_statement_url %}
{% if history %}
@@ -22,4 +24,4 @@ {% include "history/plot.html" %}
{% endif %} -{% endblock %} +{% endblock body %} diff --git a/nummi/account/views.py b/nummi/account/views.py index c3cbc36..92066cd 100644 --- a/nummi/account/views.py +++ b/nummi/account/views.py @@ -9,6 +9,7 @@ from main.views import ( NummiUpdateView, ) from statement.views import StatementListView +from transaction.models import Transaction from transaction.views import TransactionListView from .forms import AccountForm @@ -51,7 +52,7 @@ class AccountDetailView(NummiDetailView): "new_statement", kwargs={"account": account.pk} ), "statements": _statements[:_max], - "history": history(account.transaction_set), + "history": history(Transaction.objects.filter(statement__account=account)), } diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 3b82d2f..d601937 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -1,6 +1,6 @@ {% extends "main/base.html" %} {% load static %} -{% load main_extras account_extras %} +{% load main_extras account_extras statement_extras %} {% load i18n %} {% block link %} {{ block.super }} @@ -16,7 +16,7 @@

{% translate "Statements" %}

- {% include "statement/statement_table.html" %} + {% statement_table statements statements_url=statements_url %}
diff --git a/nummi/statement/templates/statement/statement_list.html b/nummi/statement/templates/statement/statement_list.html index 9d3ea04..6970a63 100644 --- a/nummi/statement/templates/statement/statement_list.html +++ b/nummi/statement/templates/statement/statement_list.html @@ -1,12 +1,12 @@ {% extends "main/list.html" %} -{% load i18n %} +{% load i18n statement_extras %} {% block name %} {% translate "Statements" %} -{% endblock %} +{% endblock name %} {% block h2 %} {% translate "Statements" %} -{% endblock %} +{% endblock h2 %} {% block table %} {% url "new_statement" as new_statement_url %} - {% include "statement/statement_table.html" %} -{% endblock %} + {% statement_table statements new_statement_url=new_statement_url %} +{% endblock table %} diff --git a/nummi/statement/templatetags/statement_extras.py b/nummi/statement/templatetags/statement_extras.py index a8631d1..54eef80 100644 --- a/nummi/statement/templatetags/statement_extras.py +++ b/nummi/statement/templatetags/statement_extras.py @@ -12,3 +12,10 @@ def check(s, diff): return remix("check", "green") else: return remix("close", "red") + + +@register.inclusion_tag("statement/statement_table.html") +def statement_table(statements, **kwargs): + return kwargs | { + "statements": statements, + } From b1fddd0dd6bc95a879450acc70df4d44f251b3ff Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 2 Jan 2025 14:41:53 +0100 Subject: [PATCH 09/60] Use templatetag for statement_table --- .../templates/account/account_detail.html | 4 +++- .../templates/account/account_table.html | 8 +++----- nummi/account/views.py | 12 ------------ nummi/main/templates/main/index.html | 7 ++++--- nummi/main/views.py | 17 +++-------------- .../templates/statement/statement_list.html | 4 ++-- .../statement/templatetags/statement_extras.py | 5 +++++ 7 files changed, 20 insertions(+), 37 deletions(-) diff --git a/nummi/account/templates/account/account_detail.html b/nummi/account/templates/account/account_detail.html index fcd9321..a4b6676 100644 --- a/nummi/account/templates/account/account_detail.html +++ b/nummi/account/templates/account/account_detail.html @@ -16,7 +16,9 @@

{% translate "Statements" %}

- {% statement_table statements statements_url=statements_url new_statement_url=new_statement_url %} + {% url "new_statement" account=account.pk as ns_url %} + {% url "account_statements" account=account.pk as s_url %} + {% statement_table account.statement_set.all statements_url=s_url new_statement_url=ns_url n_max=6 %}
{% if history %}
diff --git a/nummi/account/templates/account/account_table.html b/nummi/account/templates/account/account_table.html index 25655b7..0de11e9 100644 --- a/nummi/account/templates/account/account_table.html +++ b/nummi/account/templates/account/account_table.html @@ -15,11 +15,9 @@
{{ "gallery-view"|remixnl }}{% translate "All accounts" %}
- {% if total %} -
- {{ total|value }} -
- {% endif %} +
+ {{ accounts|balance|value }} +
{% 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 12/60] 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 13/60] 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 14/60] 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 15/60] 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 16/60] 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 17/60] 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 18/60] 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 19/60] 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 20/60] 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 21/60] 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 22/60] 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 23/60] 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 24/60] 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 25/60] 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 26/60] 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 27/60] 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 28/60] 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 29/60] 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 30/60] 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 31/60] 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 32/60] 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 33/60] 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 34/60] 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 35/60] 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 36/60] 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 37/60] 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 38/60] 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 39/60] 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 40/60] 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 41/60] 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 42/60] 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 43/60] 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 %} -