From 805c7d3dc0f95b487a78f859ffa6296c61cd2283 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 4 Jan 2025 12:37:09 +0100 Subject: [PATCH 01/28] 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 02/28] 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 03/28] 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 04/28] 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 05/28] 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 06/28] 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 07/28] 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 08/28] 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 09/28] 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 10/28] 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 11/28] 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 %} -