From 787427beec760c90cefecc6be87b6616253ce1df Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 15:32:01 +0200 Subject: [PATCH 001/276] Switch to remixicon --- nummi/main/forms.py | 4 +- ...nt_uicon_remove_category_uicon_and_more.py | 32 + nummi/main/models.py | 12 +- nummi/main/static/main/css/index.css | 6 - nummi/main/static/main/css/main.css | 9 +- .../main/static/main/remixicon/remixicon.css | 7419 ++++++++++++++++- .../static/main/remixicon/remixicon.woff2 | Bin 984 -> 139320 bytes nummi/main/templates/main/form/account.html | 2 +- nummi/main/templates/main/form/category.html | 2 +- nummi/main/templates/main/icons/checkmark.svg | 11 - nummi/main/templates/main/icons/error.svg | 1 - nummi/main/templates/main/index.html | 7 +- nummi/main/templates/main/plot/category.html | 2 +- nummi/main/templates/main/table/snapshot.html | 12 +- .../templates/main/table/transaction.html | 12 +- nummi/main/templatetags/main_extras.py | 5 + nummi/main/views.py | 2 +- 17 files changed, 7480 insertions(+), 58 deletions(-) create mode 100644 nummi/main/migrations/0027_remove_account_uicon_remove_category_uicon_and_more.py delete mode 100644 nummi/main/static/main/css/index.css delete mode 100644 nummi/main/templates/main/icons/checkmark.svg delete mode 100644 nummi/main/templates/main/icons/error.svg diff --git a/nummi/main/forms.py b/nummi/main/forms.py index 6047bda..d1e5de1 100644 --- a/nummi/main/forms.py +++ b/nummi/main/forms.py @@ -21,7 +21,7 @@ class AccountForm(NummiForm): model = Account fields = [ "name", - "uicon", + "icon", "default", ] @@ -31,7 +31,7 @@ class CategoryForm(NummiForm): model = Category fields = [ "name", - "uicon", + "icon", "budget", ] diff --git a/nummi/main/migrations/0027_remove_account_uicon_remove_category_uicon_and_more.py b/nummi/main/migrations/0027_remove_account_uicon_remove_category_uicon_and_more.py new file mode 100644 index 0000000..d0f386f --- /dev/null +++ b/nummi/main/migrations/0027_remove_account_uicon_remove_category_uicon_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.1.4 on 2023-04-19 13:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("main", "0026_alter_account_uicon_alter_category_uicon"), + ] + + operations = [ + migrations.RemoveField( + model_name="account", + name="uicon", + ), + migrations.RemoveField( + model_name="category", + name="uicon", + ), + migrations.AddField( + model_name="account", + name="icon", + field=models.SlugField(default="bank", max_length=16, verbose_name="Icon"), + ), + migrations.AddField( + model_name="category", + name="icon", + field=models.SlugField( + default="folder", max_length=16, verbose_name="Icon" + ), + ), + ] diff --git a/nummi/main/models.py b/nummi/main/models.py index a1eb97e..db6144f 100644 --- a/nummi/main/models.py +++ b/nummi/main/models.py @@ -43,9 +43,9 @@ def get_path(instance, filename): class Account(CustomModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name")) - uicon = models.CharField( - max_length=2, - default="🏦", + icon = models.SlugField( + max_length=16, + default="bank", verbose_name=_("Icon"), ) default = models.BooleanField(default=False, verbose_name=_("Default")) @@ -96,9 +96,9 @@ class Category(CustomModel): name = models.CharField( max_length=64, default=_("Category"), verbose_name=_("Name") ) - uicon = models.CharField( - max_length=2, - default="📂", + icon = models.SlugField( + max_length=16, + default="folder", verbose_name=_("Icon"), ) budget = models.BooleanField(default=True, verbose_name=_("Budget")) diff --git a/nummi/main/static/main/css/index.css b/nummi/main/static/main/css/index.css deleted file mode 100644 index 4df25da..0000000 --- a/nummi/main/static/main/css/index.css +++ /dev/null @@ -1,6 +0,0 @@ -a.big-link { - margin-right: 1em; -} -a.big-link > i { - margin-right: 0.5rem; -} diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index ff39283..22ea06a 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -159,6 +159,11 @@ footer { height: initial; } } -svg { - height: 1em; + +a.big-link { + margin-right: 1em; +} +[class^="ri-"] { + margin-right: 0.5em; + font-weight: normal; } diff --git a/nummi/main/static/main/remixicon/remixicon.css b/nummi/main/static/main/remixicon/remixicon.css index a58e163..be59fed 100644 --- a/nummi/main/static/main/remixicon/remixicon.css +++ b/nummi/main/static/main/remixicon/remixicon.css @@ -1,31 +1,7432 @@ /* +* Remix Icon v3.2.0 * https://remixicon.com * https://github.com/Remix-Design/RemixIcon +* * Copyright RemixIcon.com * Released under the Apache License Version 2.0 +* +* Date: 2023-04-15 */ - @font-face { font-family: "remixicon"; - src: url("remixicon.woff2?t=1681908066607") format("woff2"); + src: url("remixicon.woff2?t=1681573354175") format("woff2"); font-display: swap; } -[class^="ri-"], -[class*="ri-"] { - font-family: "remixicon" !important; +[class^="ri-"] { + font-family: "remixicon"; + display: inline-block; font-style: normal; text-align: center; width: 1.25em; - font-weight: normal; + text-decoration: none; } -.ri-attachement:before { +.ri-24-hours-fill:before { + content: "\ea01"; +} +.ri-24-hours-line:before { + content: "\ea02"; +} +.ri-4k-fill:before { + content: "\ea03"; +} +.ri-4k-line:before { + content: "\ea04"; +} +.ri-a-b:before { + content: "\ea05"; +} +.ri-account-box-fill:before { + content: "\ea06"; +} +.ri-account-box-line:before { + content: "\ea07"; +} +.ri-account-circle-fill:before { + content: "\ea08"; +} +.ri-account-circle-line:before { + content: "\ea09"; +} +.ri-account-pin-box-fill:before { + content: "\ea0a"; +} +.ri-account-pin-box-line:before { + content: "\ea0b"; +} +.ri-account-pin-circle-fill:before { + content: "\ea0c"; +} +.ri-account-pin-circle-line:before { + content: "\ea0d"; +} +.ri-add-box-fill:before { + content: "\ea0e"; +} +.ri-add-box-line:before { + content: "\ea0f"; +} +.ri-add-circle-fill:before { + content: "\ea10"; +} +.ri-add-circle-line:before { + content: "\ea11"; +} +.ri-add-fill:before { + content: "\ea12"; +} +.ri-add-line:before { + content: "\ea13"; +} +.ri-admin-fill:before { + content: "\ea14"; +} +.ri-admin-line:before { + content: "\ea15"; +} +.ri-advertisement-fill:before { + content: "\ea16"; +} +.ri-advertisement-line:before { + content: "\ea17"; +} +.ri-airplay-fill:before { + content: "\ea18"; +} +.ri-airplay-line:before { + content: "\ea19"; +} +.ri-alarm-fill:before { + content: "\ea1a"; +} +.ri-alarm-line:before { + content: "\ea1b"; +} +.ri-alarm-warning-fill:before { + content: "\ea1c"; +} +.ri-alarm-warning-line:before { + content: "\ea1d"; +} +.ri-album-fill:before { + content: "\ea1e"; +} +.ri-album-line:before { + content: "\ea1f"; +} +.ri-alert-fill:before { + content: "\ea20"; +} +.ri-alert-line:before { + content: "\ea21"; +} +.ri-aliens-fill:before { + content: "\ea22"; +} +.ri-aliens-line:before { + content: "\ea23"; +} +.ri-align-bottom:before { + content: "\ea24"; +} +.ri-align-center:before { + content: "\ea25"; +} +.ri-align-justify:before { + content: "\ea26"; +} +.ri-align-left:before { + content: "\ea27"; +} +.ri-align-right:before { + content: "\ea28"; +} +.ri-align-top:before { + content: "\ea29"; +} +.ri-align-vertically:before { + content: "\ea2a"; +} +.ri-alipay-fill:before { + content: "\ea2b"; +} +.ri-alipay-line:before { + content: "\ea2c"; +} +.ri-amazon-fill:before { + content: "\ea2d"; +} +.ri-amazon-line:before { + content: "\ea2e"; +} +.ri-anchor-fill:before { + content: "\ea2f"; +} +.ri-anchor-line:before { + content: "\ea30"; +} +.ri-ancient-gate-fill:before { + content: "\ea31"; +} +.ri-ancient-gate-line:before { + content: "\ea32"; +} +.ri-ancient-pavilion-fill:before { + content: "\ea33"; +} +.ri-ancient-pavilion-line:before { + content: "\ea34"; +} +.ri-android-fill:before { + content: "\ea35"; +} +.ri-android-line:before { + content: "\ea36"; +} +.ri-angularjs-fill:before { + content: "\ea37"; +} +.ri-angularjs-line:before { + content: "\ea38"; +} +.ri-anticlockwise-2-fill:before { + content: "\ea39"; +} +.ri-anticlockwise-2-line:before { + content: "\ea3a"; +} +.ri-anticlockwise-fill:before { + content: "\ea3b"; +} +.ri-anticlockwise-line:before { + content: "\ea3c"; +} +.ri-app-store-fill:before { + content: "\ea3d"; +} +.ri-app-store-line:before { + content: "\ea3e"; +} +.ri-apple-fill:before { + content: "\ea3f"; +} +.ri-apple-line:before { + content: "\ea40"; +} +.ri-apps-2-fill:before { + content: "\ea41"; +} +.ri-apps-2-line:before { + content: "\ea42"; +} +.ri-apps-fill:before { + content: "\ea43"; +} +.ri-apps-line:before { + content: "\ea44"; +} +.ri-archive-drawer-fill:before { + content: "\ea45"; +} +.ri-archive-drawer-line:before { + content: "\ea46"; +} +.ri-archive-fill:before { + content: "\ea47"; +} +.ri-archive-line:before { + content: "\ea48"; +} +.ri-arrow-down-circle-fill:before { + content: "\ea49"; +} +.ri-arrow-down-circle-line:before { + content: "\ea4a"; +} +.ri-arrow-down-fill:before { + content: "\ea4b"; +} +.ri-arrow-down-line:before { + content: "\ea4c"; +} +.ri-arrow-down-s-fill:before { + content: "\ea4d"; +} +.ri-arrow-down-s-line:before { + content: "\ea4e"; +} +.ri-arrow-drop-down-fill:before { + content: "\ea4f"; +} +.ri-arrow-drop-down-line:before { + content: "\ea50"; +} +.ri-arrow-drop-left-fill:before { + content: "\ea51"; +} +.ri-arrow-drop-left-line:before { + content: "\ea52"; +} +.ri-arrow-drop-right-fill:before { + content: "\ea53"; +} +.ri-arrow-drop-right-line:before { + content: "\ea54"; +} +.ri-arrow-drop-up-fill:before { + content: "\ea55"; +} +.ri-arrow-drop-up-line:before { + content: "\ea56"; +} +.ri-arrow-go-back-fill:before { + content: "\ea57"; +} +.ri-arrow-go-back-line:before { + content: "\ea58"; +} +.ri-arrow-go-forward-fill:before { + content: "\ea59"; +} +.ri-arrow-go-forward-line:before { + content: "\ea5a"; +} +.ri-arrow-left-circle-fill:before { + content: "\ea5b"; +} +.ri-arrow-left-circle-line:before { + content: "\ea5c"; +} +.ri-arrow-left-down-fill:before { + content: "\ea5d"; +} +.ri-arrow-left-down-line:before { + content: "\ea5e"; +} +.ri-arrow-left-fill:before { + content: "\ea5f"; +} +.ri-arrow-left-line:before { + content: "\ea60"; +} +.ri-arrow-left-right-fill:before { + content: "\ea61"; +} +.ri-arrow-left-right-line:before { + content: "\ea62"; +} +.ri-arrow-left-s-fill:before { + content: "\ea63"; +} +.ri-arrow-left-s-line:before { + content: "\ea64"; +} +.ri-arrow-left-up-fill:before { + content: "\ea65"; +} +.ri-arrow-left-up-line:before { + content: "\ea66"; +} +.ri-arrow-right-circle-fill:before { + content: "\ea67"; +} +.ri-arrow-right-circle-line:before { + content: "\ea68"; +} +.ri-arrow-right-down-fill:before { + content: "\ea69"; +} +.ri-arrow-right-down-line:before { + content: "\ea6a"; +} +.ri-arrow-right-fill:before { + content: "\ea6b"; +} +.ri-arrow-right-line:before { + content: "\ea6c"; +} +.ri-arrow-right-s-fill:before { + content: "\ea6d"; +} +.ri-arrow-right-s-line:before { + content: "\ea6e"; +} +.ri-arrow-right-up-fill:before { + content: "\ea6f"; +} +.ri-arrow-right-up-line:before { + content: "\ea70"; +} +.ri-arrow-up-circle-fill:before { + content: "\ea71"; +} +.ri-arrow-up-circle-line:before { + content: "\ea72"; +} +.ri-arrow-up-down-fill:before { + content: "\ea73"; +} +.ri-arrow-up-down-line:before { + content: "\ea74"; +} +.ri-arrow-up-fill:before { + content: "\ea75"; +} +.ri-arrow-up-line:before { + content: "\ea76"; +} +.ri-arrow-up-s-fill:before { + content: "\ea77"; +} +.ri-arrow-up-s-line:before { + content: "\ea78"; +} +.ri-artboard-2-fill:before { + content: "\ea79"; +} +.ri-artboard-2-line:before { + content: "\ea7a"; +} +.ri-artboard-fill:before { + content: "\ea7b"; +} +.ri-artboard-line:before { + content: "\ea7c"; +} +.ri-article-fill:before { + content: "\ea7d"; +} +.ri-article-line:before { + content: "\ea7e"; +} +.ri-aspect-ratio-fill:before { + content: "\ea7f"; +} +.ri-aspect-ratio-line:before { + content: "\ea80"; +} +.ri-asterisk:before { + content: "\ea81"; +} +.ri-at-fill:before { + content: "\ea82"; +} +.ri-at-line:before { + content: "\ea83"; +} +.ri-attachment-2:before { + content: "\ea84"; +} +.ri-attachment-fill:before { + content: "\ea85"; +} +.ri-attachment-line:before { content: "\ea86"; } -.ri-check:before { +.ri-auction-fill:before { + content: "\ea87"; +} +.ri-auction-line:before { + content: "\ea88"; +} +.ri-award-fill:before { + content: "\ea89"; +} +.ri-award-line:before { + content: "\ea8a"; +} +.ri-baidu-fill:before { + content: "\ea8b"; +} +.ri-baidu-line:before { + content: "\ea8c"; +} +.ri-ball-pen-fill:before { + content: "\ea8d"; +} +.ri-ball-pen-line:before { + content: "\ea8e"; +} +.ri-bank-card-2-fill:before { + content: "\ea8f"; +} +.ri-bank-card-2-line:before { + content: "\ea90"; +} +.ri-bank-card-fill:before { + content: "\ea91"; +} +.ri-bank-card-line:before { + content: "\ea92"; +} +.ri-bank-fill:before { + content: "\ea93"; +} +.ri-bank-line:before { + content: "\ea94"; +} +.ri-bar-chart-2-fill:before { + content: "\ea95"; +} +.ri-bar-chart-2-line:before { + content: "\ea96"; +} +.ri-bar-chart-box-fill:before { + content: "\ea97"; +} +.ri-bar-chart-box-line:before { + content: "\ea98"; +} +.ri-bar-chart-fill:before { + content: "\ea99"; +} +.ri-bar-chart-grouped-fill:before { + content: "\ea9a"; +} +.ri-bar-chart-grouped-line:before { + content: "\ea9b"; +} +.ri-bar-chart-horizontal-fill:before { + content: "\ea9c"; +} +.ri-bar-chart-horizontal-line:before { + content: "\ea9d"; +} +.ri-bar-chart-line:before { + content: "\ea9e"; +} +.ri-barcode-box-fill:before { + content: "\ea9f"; +} +.ri-barcode-box-line:before { + content: "\eaa0"; +} +.ri-barcode-fill:before { + content: "\eaa1"; +} +.ri-barcode-line:before { + content: "\eaa2"; +} +.ri-barricade-fill:before { + content: "\eaa3"; +} +.ri-barricade-line:before { + content: "\eaa4"; +} +.ri-base-station-fill:before { + content: "\eaa5"; +} +.ri-base-station-line:before { + content: "\eaa6"; +} +.ri-basketball-fill:before { + content: "\eaa7"; +} +.ri-basketball-line:before { + content: "\eaa8"; +} +.ri-battery-2-charge-fill:before { + content: "\eaa9"; +} +.ri-battery-2-charge-line:before { + content: "\eaaa"; +} +.ri-battery-2-fill:before { + content: "\eaab"; +} +.ri-battery-2-line:before { + content: "\eaac"; +} +.ri-battery-charge-fill:before { + content: "\eaad"; +} +.ri-battery-charge-line:before { + content: "\eaae"; +} +.ri-battery-fill:before { + content: "\eaaf"; +} +.ri-battery-line:before { + content: "\eab0"; +} +.ri-battery-low-fill:before { + content: "\eab1"; +} +.ri-battery-low-line:before { + content: "\eab2"; +} +.ri-battery-saver-fill:before { + content: "\eab3"; +} +.ri-battery-saver-line:before { + content: "\eab4"; +} +.ri-battery-share-fill:before { + content: "\eab5"; +} +.ri-battery-share-line:before { + content: "\eab6"; +} +.ri-bear-smile-fill:before { + content: "\eab7"; +} +.ri-bear-smile-line:before { + content: "\eab8"; +} +.ri-behance-fill:before { + content: "\eab9"; +} +.ri-behance-line:before { + content: "\eaba"; +} +.ri-bell-fill:before { + content: "\eabb"; +} +.ri-bell-line:before { + content: "\eabc"; +} +.ri-bike-fill:before { + content: "\eabd"; +} +.ri-bike-line:before { + content: "\eabe"; +} +.ri-bilibili-fill:before { + content: "\eabf"; +} +.ri-bilibili-line:before { + content: "\eac0"; +} +.ri-bill-fill:before { + content: "\eac1"; +} +.ri-bill-line:before { + content: "\eac2"; +} +.ri-billiards-fill:before { + content: "\eac3"; +} +.ri-billiards-line:before { + content: "\eac4"; +} +.ri-bit-coin-fill:before { + content: "\eac5"; +} +.ri-bit-coin-line:before { + content: "\eac6"; +} +.ri-blaze-fill:before { + content: "\eac7"; +} +.ri-blaze-line:before { + content: "\eac8"; +} +.ri-bluetooth-connect-fill:before { + content: "\eac9"; +} +.ri-bluetooth-connect-line:before { + content: "\eaca"; +} +.ri-bluetooth-fill:before { + content: "\eacb"; +} +.ri-bluetooth-line:before { + content: "\eacc"; +} +.ri-blur-off-fill:before { + content: "\eacd"; +} +.ri-blur-off-line:before { + content: "\eace"; +} +.ri-body-scan-fill:before { + content: "\eacf"; +} +.ri-body-scan-line:before { + content: "\ead0"; +} +.ri-bold:before { + content: "\ead1"; +} +.ri-book-2-fill:before { + content: "\ead2"; +} +.ri-book-2-line:before { + content: "\ead3"; +} +.ri-book-3-fill:before { + content: "\ead4"; +} +.ri-book-3-line:before { + content: "\ead5"; +} +.ri-book-fill:before { + content: "\ead6"; +} +.ri-book-line:before { + content: "\ead7"; +} +.ri-book-mark-fill:before { + content: "\ead8"; +} +.ri-book-mark-line:before { + content: "\ead9"; +} +.ri-book-open-fill:before { + content: "\eada"; +} +.ri-book-open-line:before { + content: "\eadb"; +} +.ri-book-read-fill:before { + content: "\eadc"; +} +.ri-book-read-line:before { + content: "\eadd"; +} +.ri-booklet-fill:before { + content: "\eade"; +} +.ri-booklet-line:before { + content: "\eadf"; +} +.ri-bookmark-2-fill:before { + content: "\eae0"; +} +.ri-bookmark-2-line:before { + content: "\eae1"; +} +.ri-bookmark-3-fill:before { + content: "\eae2"; +} +.ri-bookmark-3-line:before { + content: "\eae3"; +} +.ri-bookmark-fill:before { + content: "\eae4"; +} +.ri-bookmark-line:before { + content: "\eae5"; +} +.ri-boxing-fill:before { + content: "\eae6"; +} +.ri-boxing-line:before { + content: "\eae7"; +} +.ri-braces-fill:before { + content: "\eae8"; +} +.ri-braces-line:before { + content: "\eae9"; +} +.ri-brackets-fill:before { + content: "\eaea"; +} +.ri-brackets-line:before { + content: "\eaeb"; +} +.ri-briefcase-2-fill:before { + content: "\eaec"; +} +.ri-briefcase-2-line:before { + content: "\eaed"; +} +.ri-briefcase-3-fill:before { + content: "\eaee"; +} +.ri-briefcase-3-line:before { + content: "\eaef"; +} +.ri-briefcase-4-fill:before { + content: "\eaf0"; +} +.ri-briefcase-4-line:before { + content: "\eaf1"; +} +.ri-briefcase-5-fill:before { + content: "\eaf2"; +} +.ri-briefcase-5-line:before { + content: "\eaf3"; +} +.ri-briefcase-fill:before { + content: "\eaf4"; +} +.ri-briefcase-line:before { + content: "\eaf5"; +} +.ri-bring-forward:before { + content: "\eaf6"; +} +.ri-bring-to-front:before { + content: "\eaf7"; +} +.ri-broadcast-fill:before { + content: "\eaf8"; +} +.ri-broadcast-line:before { + content: "\eaf9"; +} +.ri-brush-2-fill:before { + content: "\eafa"; +} +.ri-brush-2-line:before { + content: "\eafb"; +} +.ri-brush-3-fill:before { + content: "\eafc"; +} +.ri-brush-3-line:before { + content: "\eafd"; +} +.ri-brush-4-fill:before { + content: "\eafe"; +} +.ri-brush-4-line:before { + content: "\eaff"; +} +.ri-brush-fill:before { + content: "\eb00"; +} +.ri-brush-line:before { + content: "\eb01"; +} +.ri-bubble-chart-fill:before { + content: "\eb02"; +} +.ri-bubble-chart-line:before { + content: "\eb03"; +} +.ri-bug-2-fill:before { + content: "\eb04"; +} +.ri-bug-2-line:before { + content: "\eb05"; +} +.ri-bug-fill:before { + content: "\eb06"; +} +.ri-bug-line:before { + content: "\eb07"; +} +.ri-building-2-fill:before { + content: "\eb08"; +} +.ri-building-2-line:before { + content: "\eb09"; +} +.ri-building-3-fill:before { + content: "\eb0a"; +} +.ri-building-3-line:before { + content: "\eb0b"; +} +.ri-building-4-fill:before { + content: "\eb0c"; +} +.ri-building-4-line:before { + content: "\eb0d"; +} +.ri-building-fill:before { + content: "\eb0e"; +} +.ri-building-line:before { + content: "\eb0f"; +} +.ri-bus-2-fill:before { + content: "\eb10"; +} +.ri-bus-2-line:before { + content: "\eb11"; +} +.ri-bus-fill:before { + content: "\eb12"; +} +.ri-bus-line:before { + content: "\eb13"; +} +.ri-bus-wifi-fill:before { + content: "\eb14"; +} +.ri-bus-wifi-line:before { + content: "\eb15"; +} +.ri-cactus-fill:before { + content: "\eb16"; +} +.ri-cactus-line:before { + content: "\eb17"; +} +.ri-cake-2-fill:before { + content: "\eb18"; +} +.ri-cake-2-line:before { + content: "\eb19"; +} +.ri-cake-3-fill:before { + content: "\eb1a"; +} +.ri-cake-3-line:before { + content: "\eb1b"; +} +.ri-cake-fill:before { + content: "\eb1c"; +} +.ri-cake-line:before { + content: "\eb1d"; +} +.ri-calculator-fill:before { + content: "\eb1e"; +} +.ri-calculator-line:before { + content: "\eb1f"; +} +.ri-calendar-2-fill:before { + content: "\eb20"; +} +.ri-calendar-2-line:before { + content: "\eb21"; +} +.ri-calendar-check-fill:before { + content: "\eb22"; +} +.ri-calendar-check-line:before { + content: "\eb23"; +} +.ri-calendar-event-fill:before { + content: "\eb24"; +} +.ri-calendar-event-line:before { + content: "\eb25"; +} +.ri-calendar-fill:before { + content: "\eb26"; +} +.ri-calendar-line:before { + content: "\eb27"; +} +.ri-calendar-todo-fill:before { + content: "\eb28"; +} +.ri-calendar-todo-line:before { + content: "\eb29"; +} +.ri-camera-2-fill:before { + content: "\eb2a"; +} +.ri-camera-2-line:before { + content: "\eb2b"; +} +.ri-camera-3-fill:before { + content: "\eb2c"; +} +.ri-camera-3-line:before { + content: "\eb2d"; +} +.ri-camera-fill:before { + content: "\eb2e"; +} +.ri-camera-lens-fill:before { + content: "\eb2f"; +} +.ri-camera-lens-line:before { + content: "\eb30"; +} +.ri-camera-line:before { + content: "\eb31"; +} +.ri-camera-off-fill:before { + content: "\eb32"; +} +.ri-camera-off-line:before { + content: "\eb33"; +} +.ri-camera-switch-fill:before { + content: "\eb34"; +} +.ri-camera-switch-line:before { + content: "\eb35"; +} +.ri-capsule-fill:before { + content: "\eb36"; +} +.ri-capsule-line:before { + content: "\eb37"; +} +.ri-car-fill:before { + content: "\eb38"; +} +.ri-car-line:before { + content: "\eb39"; +} +.ri-car-washing-fill:before { + content: "\eb3a"; +} +.ri-car-washing-line:before { + content: "\eb3b"; +} +.ri-caravan-fill:before { + content: "\eb3c"; +} +.ri-caravan-line:before { + content: "\eb3d"; +} +.ri-cast-fill:before { + content: "\eb3e"; +} +.ri-cast-line:before { + content: "\eb3f"; +} +.ri-cellphone-fill:before { + content: "\eb40"; +} +.ri-cellphone-line:before { + content: "\eb41"; +} +.ri-celsius-fill:before { + content: "\eb42"; +} +.ri-celsius-line:before { + content: "\eb43"; +} +.ri-centos-fill:before { + content: "\eb44"; +} +.ri-centos-line:before { + content: "\eb45"; +} +.ri-character-recognition-fill:before { + content: "\eb46"; +} +.ri-character-recognition-line:before { + content: "\eb47"; +} +.ri-charging-pile-2-fill:before { + content: "\eb48"; +} +.ri-charging-pile-2-line:before { + content: "\eb49"; +} +.ri-charging-pile-fill:before { + content: "\eb4a"; +} +.ri-charging-pile-line:before { + content: "\eb4b"; +} +.ri-chat-1-fill:before { + content: "\eb4c"; +} +.ri-chat-1-line:before { + content: "\eb4d"; +} +.ri-chat-2-fill:before { + content: "\eb4e"; +} +.ri-chat-2-line:before { + content: "\eb4f"; +} +.ri-chat-3-fill:before { + content: "\eb50"; +} +.ri-chat-3-line:before { + content: "\eb51"; +} +.ri-chat-4-fill:before { + content: "\eb52"; +} +.ri-chat-4-line:before { + content: "\eb53"; +} +.ri-chat-check-fill:before { + content: "\eb54"; +} +.ri-chat-check-line:before { + content: "\eb55"; +} +.ri-chat-delete-fill:before { + content: "\eb56"; +} +.ri-chat-delete-line:before { + content: "\eb57"; +} +.ri-chat-download-fill:before { + content: "\eb58"; +} +.ri-chat-download-line:before { + content: "\eb59"; +} +.ri-chat-follow-up-fill:before { + content: "\eb5a"; +} +.ri-chat-follow-up-line:before { + content: "\eb5b"; +} +.ri-chat-forward-fill:before { + content: "\eb5c"; +} +.ri-chat-forward-line:before { + content: "\eb5d"; +} +.ri-chat-heart-fill:before { + content: "\eb5e"; +} +.ri-chat-heart-line:before { + content: "\eb5f"; +} +.ri-chat-history-fill:before { + content: "\eb60"; +} +.ri-chat-history-line:before { + content: "\eb61"; +} +.ri-chat-new-fill:before { + content: "\eb62"; +} +.ri-chat-new-line:before { + content: "\eb63"; +} +.ri-chat-off-fill:before { + content: "\eb64"; +} +.ri-chat-off-line:before { + content: "\eb65"; +} +.ri-chat-poll-fill:before { + content: "\eb66"; +} +.ri-chat-poll-line:before { + content: "\eb67"; +} +.ri-chat-private-fill:before { + content: "\eb68"; +} +.ri-chat-private-line:before { + content: "\eb69"; +} +.ri-chat-quote-fill:before { + content: "\eb6a"; +} +.ri-chat-quote-line:before { + content: "\eb6b"; +} +.ri-chat-settings-fill:before { + content: "\eb6c"; +} +.ri-chat-settings-line:before { + content: "\eb6d"; +} +.ri-chat-smile-2-fill:before { + content: "\eb6e"; +} +.ri-chat-smile-2-line:before { + content: "\eb6f"; +} +.ri-chat-smile-3-fill:before { + content: "\eb70"; +} +.ri-chat-smile-3-line:before { + content: "\eb71"; +} +.ri-chat-smile-fill:before { + content: "\eb72"; +} +.ri-chat-smile-line:before { + content: "\eb73"; +} +.ri-chat-upload-fill:before { + content: "\eb74"; +} +.ri-chat-upload-line:before { + content: "\eb75"; +} +.ri-chat-voice-fill:before { + content: "\eb76"; +} +.ri-chat-voice-line:before { + content: "\eb77"; +} +.ri-check-double-fill:before { + content: "\eb78"; +} +.ri-check-double-line:before { + content: "\eb79"; +} +.ri-check-fill:before { + content: "\eb7a"; +} +.ri-check-line:before { content: "\eb7b"; } -.ri-close:before { +.ri-checkbox-blank-circle-fill:before { + content: "\eb7c"; +} +.ri-checkbox-blank-circle-line:before { + content: "\eb7d"; +} +.ri-checkbox-blank-fill:before { + content: "\eb7e"; +} +.ri-checkbox-blank-line:before { + content: "\eb7f"; +} +.ri-checkbox-circle-fill:before { + content: "\eb80"; +} +.ri-checkbox-circle-line:before { + content: "\eb81"; +} +.ri-checkbox-fill:before { + content: "\eb82"; +} +.ri-checkbox-indeterminate-fill:before { + content: "\eb83"; +} +.ri-checkbox-indeterminate-line:before { + content: "\eb84"; +} +.ri-checkbox-line:before { + content: "\eb85"; +} +.ri-checkbox-multiple-blank-fill:before { + content: "\eb86"; +} +.ri-checkbox-multiple-blank-line:before { + content: "\eb87"; +} +.ri-checkbox-multiple-fill:before { + content: "\eb88"; +} +.ri-checkbox-multiple-line:before { + content: "\eb89"; +} +.ri-china-railway-fill:before { + content: "\eb8a"; +} +.ri-china-railway-line:before { + content: "\eb8b"; +} +.ri-chrome-fill:before { + content: "\eb8c"; +} +.ri-chrome-line:before { + content: "\eb8d"; +} +.ri-clapperboard-fill:before { + content: "\eb8e"; +} +.ri-clapperboard-line:before { + content: "\eb8f"; +} +.ri-clipboard-fill:before { + content: "\eb90"; +} +.ri-clipboard-line:before { + content: "\eb91"; +} +.ri-clockwise-2-fill:before { + content: "\eb92"; +} +.ri-clockwise-2-line:before { + content: "\eb93"; +} +.ri-clockwise-fill:before { + content: "\eb94"; +} +.ri-clockwise-line:before { + content: "\eb95"; +} +.ri-close-circle-fill:before { + content: "\eb96"; +} +.ri-close-circle-line:before { + content: "\eb97"; +} +.ri-close-fill:before { + content: "\eb98"; +} +.ri-close-line:before { content: "\eb99"; } +.ri-closed-captioning-fill:before { + content: "\eb9a"; +} +.ri-closed-captioning-line:before { + content: "\eb9b"; +} +.ri-cloud-fill:before { + content: "\eb9c"; +} +.ri-cloud-line:before { + content: "\eb9d"; +} +.ri-cloud-off-fill:before { + content: "\eb9e"; +} +.ri-cloud-off-line:before { + content: "\eb9f"; +} +.ri-cloud-windy-fill:before { + content: "\eba0"; +} +.ri-cloud-windy-line:before { + content: "\eba1"; +} +.ri-cloudy-2-fill:before { + content: "\eba2"; +} +.ri-cloudy-2-line:before { + content: "\eba3"; +} +.ri-cloudy-fill:before { + content: "\eba4"; +} +.ri-cloudy-line:before { + content: "\eba5"; +} +.ri-code-box-fill:before { + content: "\eba6"; +} +.ri-code-box-line:before { + content: "\eba7"; +} +.ri-code-fill:before { + content: "\eba8"; +} +.ri-code-line:before { + content: "\eba9"; +} +.ri-code-s-fill:before { + content: "\ebaa"; +} +.ri-code-s-line:before { + content: "\ebab"; +} +.ri-code-s-slash-fill:before { + content: "\ebac"; +} +.ri-code-s-slash-line:before { + content: "\ebad"; +} +.ri-code-view:before { + content: "\ebae"; +} +.ri-codepen-fill:before { + content: "\ebaf"; +} +.ri-codepen-line:before { + content: "\ebb0"; +} +.ri-coin-fill:before { + content: "\ebb1"; +} +.ri-coin-line:before { + content: "\ebb2"; +} +.ri-coins-fill:before { + content: "\ebb3"; +} +.ri-coins-line:before { + content: "\ebb4"; +} +.ri-collage-fill:before { + content: "\ebb5"; +} +.ri-collage-line:before { + content: "\ebb6"; +} +.ri-command-fill:before { + content: "\ebb7"; +} +.ri-command-line:before { + content: "\ebb8"; +} +.ri-community-fill:before { + content: "\ebb9"; +} +.ri-community-line:before { + content: "\ebba"; +} +.ri-compass-2-fill:before { + content: "\ebbb"; +} +.ri-compass-2-line:before { + content: "\ebbc"; +} +.ri-compass-3-fill:before { + content: "\ebbd"; +} +.ri-compass-3-line:before { + content: "\ebbe"; +} +.ri-compass-4-fill:before { + content: "\ebbf"; +} +.ri-compass-4-line:before { + content: "\ebc0"; +} +.ri-compass-discover-fill:before { + content: "\ebc1"; +} +.ri-compass-discover-line:before { + content: "\ebc2"; +} +.ri-compass-fill:before { + content: "\ebc3"; +} +.ri-compass-line:before { + content: "\ebc4"; +} +.ri-compasses-2-fill:before { + content: "\ebc5"; +} +.ri-compasses-2-line:before { + content: "\ebc6"; +} +.ri-compasses-fill:before { + content: "\ebc7"; +} +.ri-compasses-line:before { + content: "\ebc8"; +} +.ri-computer-fill:before { + content: "\ebc9"; +} +.ri-computer-line:before { + content: "\ebca"; +} +.ri-contacts-book-2-fill:before { + content: "\ebcb"; +} +.ri-contacts-book-2-line:before { + content: "\ebcc"; +} +.ri-contacts-book-fill:before { + content: "\ebcd"; +} +.ri-contacts-book-line:before { + content: "\ebce"; +} +.ri-contacts-book-upload-fill:before { + content: "\ebcf"; +} +.ri-contacts-book-upload-line:before { + content: "\ebd0"; +} +.ri-contacts-fill:before { + content: "\ebd1"; +} +.ri-contacts-line:before { + content: "\ebd2"; +} +.ri-contrast-2-fill:before { + content: "\ebd3"; +} +.ri-contrast-2-line:before { + content: "\ebd4"; +} +.ri-contrast-drop-2-fill:before { + content: "\ebd5"; +} +.ri-contrast-drop-2-line:before { + content: "\ebd6"; +} +.ri-contrast-drop-fill:before { + content: "\ebd7"; +} +.ri-contrast-drop-line:before { + content: "\ebd8"; +} +.ri-contrast-fill:before { + content: "\ebd9"; +} +.ri-contrast-line:before { + content: "\ebda"; +} +.ri-copper-coin-fill:before { + content: "\ebdb"; +} +.ri-copper-coin-line:before { + content: "\ebdc"; +} +.ri-copper-diamond-fill:before { + content: "\ebdd"; +} +.ri-copper-diamond-line:before { + content: "\ebde"; +} +.ri-copyleft-fill:before { + content: "\ebdf"; +} +.ri-copyleft-line:before { + content: "\ebe0"; +} +.ri-copyright-fill:before { + content: "\ebe1"; +} +.ri-copyright-line:before { + content: "\ebe2"; +} +.ri-coreos-fill:before { + content: "\ebe3"; +} +.ri-coreos-line:before { + content: "\ebe4"; +} +.ri-coupon-2-fill:before { + content: "\ebe5"; +} +.ri-coupon-2-line:before { + content: "\ebe6"; +} +.ri-coupon-3-fill:before { + content: "\ebe7"; +} +.ri-coupon-3-line:before { + content: "\ebe8"; +} +.ri-coupon-4-fill:before { + content: "\ebe9"; +} +.ri-coupon-4-line:before { + content: "\ebea"; +} +.ri-coupon-5-fill:before { + content: "\ebeb"; +} +.ri-coupon-5-line:before { + content: "\ebec"; +} +.ri-coupon-fill:before { + content: "\ebed"; +} +.ri-coupon-line:before { + content: "\ebee"; +} +.ri-cpu-fill:before { + content: "\ebef"; +} +.ri-cpu-line:before { + content: "\ebf0"; +} +.ri-creative-commons-by-fill:before { + content: "\ebf1"; +} +.ri-creative-commons-by-line:before { + content: "\ebf2"; +} +.ri-creative-commons-fill:before { + content: "\ebf3"; +} +.ri-creative-commons-line:before { + content: "\ebf4"; +} +.ri-creative-commons-nc-fill:before { + content: "\ebf5"; +} +.ri-creative-commons-nc-line:before { + content: "\ebf6"; +} +.ri-creative-commons-nd-fill:before { + content: "\ebf7"; +} +.ri-creative-commons-nd-line:before { + content: "\ebf8"; +} +.ri-creative-commons-sa-fill:before { + content: "\ebf9"; +} +.ri-creative-commons-sa-line:before { + content: "\ebfa"; +} +.ri-creative-commons-zero-fill:before { + content: "\ebfb"; +} +.ri-creative-commons-zero-line:before { + content: "\ebfc"; +} +.ri-criminal-fill:before { + content: "\ebfd"; +} +.ri-criminal-line:before { + content: "\ebfe"; +} +.ri-crop-2-fill:before { + content: "\ebff"; +} +.ri-crop-2-line:before { + content: "\ec00"; +} +.ri-crop-fill:before { + content: "\ec01"; +} +.ri-crop-line:before { + content: "\ec02"; +} +.ri-css3-fill:before { + content: "\ec03"; +} +.ri-css3-line:before { + content: "\ec04"; +} +.ri-cup-fill:before { + content: "\ec05"; +} +.ri-cup-line:before { + content: "\ec06"; +} +.ri-currency-fill:before { + content: "\ec07"; +} +.ri-currency-line:before { + content: "\ec08"; +} +.ri-cursor-fill:before { + content: "\ec09"; +} +.ri-cursor-line:before { + content: "\ec0a"; +} +.ri-customer-service-2-fill:before { + content: "\ec0b"; +} +.ri-customer-service-2-line:before { + content: "\ec0c"; +} +.ri-customer-service-fill:before { + content: "\ec0d"; +} +.ri-customer-service-line:before { + content: "\ec0e"; +} +.ri-dashboard-2-fill:before { + content: "\ec0f"; +} +.ri-dashboard-2-line:before { + content: "\ec10"; +} +.ri-dashboard-3-fill:before { + content: "\ec11"; +} +.ri-dashboard-3-line:before { + content: "\ec12"; +} +.ri-dashboard-fill:before { + content: "\ec13"; +} +.ri-dashboard-line:before { + content: "\ec14"; +} +.ri-database-2-fill:before { + content: "\ec15"; +} +.ri-database-2-line:before { + content: "\ec16"; +} +.ri-database-fill:before { + content: "\ec17"; +} +.ri-database-line:before { + content: "\ec18"; +} +.ri-delete-back-2-fill:before { + content: "\ec19"; +} +.ri-delete-back-2-line:before { + content: "\ec1a"; +} +.ri-delete-back-fill:before { + content: "\ec1b"; +} +.ri-delete-back-line:before { + content: "\ec1c"; +} +.ri-delete-bin-2-fill:before { + content: "\ec1d"; +} +.ri-delete-bin-2-line:before { + content: "\ec1e"; +} +.ri-delete-bin-3-fill:before { + content: "\ec1f"; +} +.ri-delete-bin-3-line:before { + content: "\ec20"; +} +.ri-delete-bin-4-fill:before { + content: "\ec21"; +} +.ri-delete-bin-4-line:before { + content: "\ec22"; +} +.ri-delete-bin-5-fill:before { + content: "\ec23"; +} +.ri-delete-bin-5-line:before { + content: "\ec24"; +} +.ri-delete-bin-6-fill:before { + content: "\ec25"; +} +.ri-delete-bin-6-line:before { + content: "\ec26"; +} +.ri-delete-bin-7-fill:before { + content: "\ec27"; +} +.ri-delete-bin-7-line:before { + content: "\ec28"; +} +.ri-delete-bin-fill:before { + content: "\ec29"; +} +.ri-delete-bin-line:before { + content: "\ec2a"; +} +.ri-delete-column:before { + content: "\ec2b"; +} +.ri-delete-row:before { + content: "\ec2c"; +} +.ri-device-fill:before { + content: "\ec2d"; +} +.ri-device-line:before { + content: "\ec2e"; +} +.ri-device-recover-fill:before { + content: "\ec2f"; +} +.ri-device-recover-line:before { + content: "\ec30"; +} +.ri-dingding-fill:before { + content: "\ec31"; +} +.ri-dingding-line:before { + content: "\ec32"; +} +.ri-direction-fill:before { + content: "\ec33"; +} +.ri-direction-line:before { + content: "\ec34"; +} +.ri-disc-fill:before { + content: "\ec35"; +} +.ri-disc-line:before { + content: "\ec36"; +} +.ri-discord-fill:before { + content: "\ec37"; +} +.ri-discord-line:before { + content: "\ec38"; +} +.ri-discuss-fill:before { + content: "\ec39"; +} +.ri-discuss-line:before { + content: "\ec3a"; +} +.ri-dislike-fill:before { + content: "\ec3b"; +} +.ri-dislike-line:before { + content: "\ec3c"; +} +.ri-disqus-fill:before { + content: "\ec3d"; +} +.ri-disqus-line:before { + content: "\ec3e"; +} +.ri-divide-fill:before { + content: "\ec3f"; +} +.ri-divide-line:before { + content: "\ec40"; +} +.ri-donut-chart-fill:before { + content: "\ec41"; +} +.ri-donut-chart-line:before { + content: "\ec42"; +} +.ri-door-closed-fill:before { + content: "\ec43"; +} +.ri-door-closed-line:before { + content: "\ec44"; +} +.ri-door-fill:before { + content: "\ec45"; +} +.ri-door-line:before { + content: "\ec46"; +} +.ri-door-lock-box-fill:before { + content: "\ec47"; +} +.ri-door-lock-box-line:before { + content: "\ec48"; +} +.ri-door-lock-fill:before { + content: "\ec49"; +} +.ri-door-lock-line:before { + content: "\ec4a"; +} +.ri-door-open-fill:before { + content: "\ec4b"; +} +.ri-door-open-line:before { + content: "\ec4c"; +} +.ri-dossier-fill:before { + content: "\ec4d"; +} +.ri-dossier-line:before { + content: "\ec4e"; +} +.ri-douban-fill:before { + content: "\ec4f"; +} +.ri-douban-line:before { + content: "\ec50"; +} +.ri-double-quotes-l:before { + content: "\ec51"; +} +.ri-double-quotes-r:before { + content: "\ec52"; +} +.ri-download-2-fill:before { + content: "\ec53"; +} +.ri-download-2-line:before { + content: "\ec54"; +} +.ri-download-cloud-2-fill:before { + content: "\ec55"; +} +.ri-download-cloud-2-line:before { + content: "\ec56"; +} +.ri-download-cloud-fill:before { + content: "\ec57"; +} +.ri-download-cloud-line:before { + content: "\ec58"; +} +.ri-download-fill:before { + content: "\ec59"; +} +.ri-download-line:before { + content: "\ec5a"; +} +.ri-draft-fill:before { + content: "\ec5b"; +} +.ri-draft-line:before { + content: "\ec5c"; +} +.ri-drag-drop-fill:before { + content: "\ec5d"; +} +.ri-drag-drop-line:before { + content: "\ec5e"; +} +.ri-drag-move-2-fill:before { + content: "\ec5f"; +} +.ri-drag-move-2-line:before { + content: "\ec60"; +} +.ri-drag-move-fill:before { + content: "\ec61"; +} +.ri-drag-move-line:before { + content: "\ec62"; +} +.ri-dribbble-fill:before { + content: "\ec63"; +} +.ri-dribbble-line:before { + content: "\ec64"; +} +.ri-drive-fill:before { + content: "\ec65"; +} +.ri-drive-line:before { + content: "\ec66"; +} +.ri-drizzle-fill:before { + content: "\ec67"; +} +.ri-drizzle-line:before { + content: "\ec68"; +} +.ri-drop-fill:before { + content: "\ec69"; +} +.ri-drop-line:before { + content: "\ec6a"; +} +.ri-dropbox-fill:before { + content: "\ec6b"; +} +.ri-dropbox-line:before { + content: "\ec6c"; +} +.ri-dual-sim-1-fill:before { + content: "\ec6d"; +} +.ri-dual-sim-1-line:before { + content: "\ec6e"; +} +.ri-dual-sim-2-fill:before { + content: "\ec6f"; +} +.ri-dual-sim-2-line:before { + content: "\ec70"; +} +.ri-dv-fill:before { + content: "\ec71"; +} +.ri-dv-line:before { + content: "\ec72"; +} +.ri-dvd-fill:before { + content: "\ec73"; +} +.ri-dvd-line:before { + content: "\ec74"; +} +.ri-e-bike-2-fill:before { + content: "\ec75"; +} +.ri-e-bike-2-line:before { + content: "\ec76"; +} +.ri-e-bike-fill:before { + content: "\ec77"; +} +.ri-e-bike-line:before { + content: "\ec78"; +} +.ri-earth-fill:before { + content: "\ec79"; +} +.ri-earth-line:before { + content: "\ec7a"; +} +.ri-earthquake-fill:before { + content: "\ec7b"; +} +.ri-earthquake-line:before { + content: "\ec7c"; +} +.ri-edge-fill:before { + content: "\ec7d"; +} +.ri-edge-line:before { + content: "\ec7e"; +} +.ri-edit-2-fill:before { + content: "\ec7f"; +} +.ri-edit-2-line:before { + content: "\ec80"; +} +.ri-edit-box-fill:before { + content: "\ec81"; +} +.ri-edit-box-line:before { + content: "\ec82"; +} +.ri-edit-circle-fill:before { + content: "\ec83"; +} +.ri-edit-circle-line:before { + content: "\ec84"; +} +.ri-edit-fill:before { + content: "\ec85"; +} +.ri-edit-line:before { + content: "\ec86"; +} +.ri-eject-fill:before { + content: "\ec87"; +} +.ri-eject-line:before { + content: "\ec88"; +} +.ri-emotion-2-fill:before { + content: "\ec89"; +} +.ri-emotion-2-line:before { + content: "\ec8a"; +} +.ri-emotion-fill:before { + content: "\ec8b"; +} +.ri-emotion-happy-fill:before { + content: "\ec8c"; +} +.ri-emotion-happy-line:before { + content: "\ec8d"; +} +.ri-emotion-laugh-fill:before { + content: "\ec8e"; +} +.ri-emotion-laugh-line:before { + content: "\ec8f"; +} +.ri-emotion-line:before { + content: "\ec90"; +} +.ri-emotion-normal-fill:before { + content: "\ec91"; +} +.ri-emotion-normal-line:before { + content: "\ec92"; +} +.ri-emotion-sad-fill:before { + content: "\ec93"; +} +.ri-emotion-sad-line:before { + content: "\ec94"; +} +.ri-emotion-unhappy-fill:before { + content: "\ec95"; +} +.ri-emotion-unhappy-line:before { + content: "\ec96"; +} +.ri-empathize-fill:before { + content: "\ec97"; +} +.ri-empathize-line:before { + content: "\ec98"; +} +.ri-emphasis-cn:before { + content: "\ec99"; +} +.ri-emphasis:before { + content: "\ec9a"; +} +.ri-english-input:before { + content: "\ec9b"; +} +.ri-equalizer-fill:before { + content: "\ec9c"; +} +.ri-equalizer-line:before { + content: "\ec9d"; +} +.ri-eraser-fill:before { + content: "\ec9e"; +} +.ri-eraser-line:before { + content: "\ec9f"; +} +.ri-error-warning-fill:before { + content: "\eca0"; +} +.ri-error-warning-line:before { + content: "\eca1"; +} +.ri-evernote-fill:before { + content: "\eca2"; +} +.ri-evernote-line:before { + content: "\eca3"; +} +.ri-exchange-box-fill:before { + content: "\eca4"; +} +.ri-exchange-box-line:before { + content: "\eca5"; +} +.ri-exchange-cny-fill:before { + content: "\eca6"; +} +.ri-exchange-cny-line:before { + content: "\eca7"; +} +.ri-exchange-dollar-fill:before { + content: "\eca8"; +} +.ri-exchange-dollar-line:before { + content: "\eca9"; +} +.ri-exchange-fill:before { + content: "\ecaa"; +} +.ri-exchange-funds-fill:before { + content: "\ecab"; +} +.ri-exchange-funds-line:before { + content: "\ecac"; +} +.ri-exchange-line:before { + content: "\ecad"; +} +.ri-external-link-fill:before { + content: "\ecae"; +} +.ri-external-link-line:before { + content: "\ecaf"; +} +.ri-eye-2-fill:before { + content: "\ecb0"; +} +.ri-eye-2-line:before { + content: "\ecb1"; +} +.ri-eye-close-fill:before { + content: "\ecb2"; +} +.ri-eye-close-line:before { + content: "\ecb3"; +} +.ri-eye-fill:before { + content: "\ecb4"; +} +.ri-eye-line:before { + content: "\ecb5"; +} +.ri-eye-off-fill:before { + content: "\ecb6"; +} +.ri-eye-off-line:before { + content: "\ecb7"; +} +.ri-facebook-box-fill:before { + content: "\ecb8"; +} +.ri-facebook-box-line:before { + content: "\ecb9"; +} +.ri-facebook-circle-fill:before { + content: "\ecba"; +} +.ri-facebook-circle-line:before { + content: "\ecbb"; +} +.ri-facebook-fill:before { + content: "\ecbc"; +} +.ri-facebook-line:before { + content: "\ecbd"; +} +.ri-fahrenheit-fill:before { + content: "\ecbe"; +} +.ri-fahrenheit-line:before { + content: "\ecbf"; +} +.ri-feedback-fill:before { + content: "\ecc0"; +} +.ri-feedback-line:before { + content: "\ecc1"; +} +.ri-file-2-fill:before { + content: "\ecc2"; +} +.ri-file-2-line:before { + content: "\ecc3"; +} +.ri-file-3-fill:before { + content: "\ecc4"; +} +.ri-file-3-line:before { + content: "\ecc5"; +} +.ri-file-4-fill:before { + content: "\ecc6"; +} +.ri-file-4-line:before { + content: "\ecc7"; +} +.ri-file-add-fill:before { + content: "\ecc8"; +} +.ri-file-add-line:before { + content: "\ecc9"; +} +.ri-file-chart-2-fill:before { + content: "\ecca"; +} +.ri-file-chart-2-line:before { + content: "\eccb"; +} +.ri-file-chart-fill:before { + content: "\eccc"; +} +.ri-file-chart-line:before { + content: "\eccd"; +} +.ri-file-cloud-fill:before { + content: "\ecce"; +} +.ri-file-cloud-line:before { + content: "\eccf"; +} +.ri-file-code-fill:before { + content: "\ecd0"; +} +.ri-file-code-line:before { + content: "\ecd1"; +} +.ri-file-copy-2-fill:before { + content: "\ecd2"; +} +.ri-file-copy-2-line:before { + content: "\ecd3"; +} +.ri-file-copy-fill:before { + content: "\ecd4"; +} +.ri-file-copy-line:before { + content: "\ecd5"; +} +.ri-file-damage-fill:before { + content: "\ecd6"; +} +.ri-file-damage-line:before { + content: "\ecd7"; +} +.ri-file-download-fill:before { + content: "\ecd8"; +} +.ri-file-download-line:before { + content: "\ecd9"; +} +.ri-file-edit-fill:before { + content: "\ecda"; +} +.ri-file-edit-line:before { + content: "\ecdb"; +} +.ri-file-excel-2-fill:before { + content: "\ecdc"; +} +.ri-file-excel-2-line:before { + content: "\ecdd"; +} +.ri-file-excel-fill:before { + content: "\ecde"; +} +.ri-file-excel-line:before { + content: "\ecdf"; +} +.ri-file-fill:before { + content: "\ece0"; +} +.ri-file-forbid-fill:before { + content: "\ece1"; +} +.ri-file-forbid-line:before { + content: "\ece2"; +} +.ri-file-gif-fill:before { + content: "\ece3"; +} +.ri-file-gif-line:before { + content: "\ece4"; +} +.ri-file-history-fill:before { + content: "\ece5"; +} +.ri-file-history-line:before { + content: "\ece6"; +} +.ri-file-hwp-fill:before { + content: "\ece7"; +} +.ri-file-hwp-line:before { + content: "\ece8"; +} +.ri-file-info-fill:before { + content: "\ece9"; +} +.ri-file-info-line:before { + content: "\ecea"; +} +.ri-file-line:before { + content: "\eceb"; +} +.ri-file-list-2-fill:before { + content: "\ecec"; +} +.ri-file-list-2-line:before { + content: "\eced"; +} +.ri-file-list-3-fill:before { + content: "\ecee"; +} +.ri-file-list-3-line:before { + content: "\ecef"; +} +.ri-file-list-fill:before { + content: "\ecf0"; +} +.ri-file-list-line:before { + content: "\ecf1"; +} +.ri-file-lock-fill:before { + content: "\ecf2"; +} +.ri-file-lock-line:before { + content: "\ecf3"; +} +.ri-file-mark-fill:before { + content: "\ecf4"; +} +.ri-file-mark-line:before { + content: "\ecf5"; +} +.ri-file-music-fill:before { + content: "\ecf6"; +} +.ri-file-music-line:before { + content: "\ecf7"; +} +.ri-file-paper-2-fill:before { + content: "\ecf8"; +} +.ri-file-paper-2-line:before { + content: "\ecf9"; +} +.ri-file-paper-fill:before { + content: "\ecfa"; +} +.ri-file-paper-line:before { + content: "\ecfb"; +} +.ri-file-pdf-fill:before { + content: "\ecfc"; +} +.ri-file-pdf-line:before { + content: "\ecfd"; +} +.ri-file-ppt-2-fill:before { + content: "\ecfe"; +} +.ri-file-ppt-2-line:before { + content: "\ecff"; +} +.ri-file-ppt-fill:before { + content: "\ed00"; +} +.ri-file-ppt-line:before { + content: "\ed01"; +} +.ri-file-reduce-fill:before { + content: "\ed02"; +} +.ri-file-reduce-line:before { + content: "\ed03"; +} +.ri-file-search-fill:before { + content: "\ed04"; +} +.ri-file-search-line:before { + content: "\ed05"; +} +.ri-file-settings-fill:before { + content: "\ed06"; +} +.ri-file-settings-line:before { + content: "\ed07"; +} +.ri-file-shield-2-fill:before { + content: "\ed08"; +} +.ri-file-shield-2-line:before { + content: "\ed09"; +} +.ri-file-shield-fill:before { + content: "\ed0a"; +} +.ri-file-shield-line:before { + content: "\ed0b"; +} +.ri-file-shred-fill:before { + content: "\ed0c"; +} +.ri-file-shred-line:before { + content: "\ed0d"; +} +.ri-file-text-fill:before { + content: "\ed0e"; +} +.ri-file-text-line:before { + content: "\ed0f"; +} +.ri-file-transfer-fill:before { + content: "\ed10"; +} +.ri-file-transfer-line:before { + content: "\ed11"; +} +.ri-file-unknow-fill:before { + content: "\ed12"; +} +.ri-file-unknow-line:before { + content: "\ed13"; +} +.ri-file-upload-fill:before { + content: "\ed14"; +} +.ri-file-upload-line:before { + content: "\ed15"; +} +.ri-file-user-fill:before { + content: "\ed16"; +} +.ri-file-user-line:before { + content: "\ed17"; +} +.ri-file-warning-fill:before { + content: "\ed18"; +} +.ri-file-warning-line:before { + content: "\ed19"; +} +.ri-file-word-2-fill:before { + content: "\ed1a"; +} +.ri-file-word-2-line:before { + content: "\ed1b"; +} +.ri-file-word-fill:before { + content: "\ed1c"; +} +.ri-file-word-line:before { + content: "\ed1d"; +} +.ri-file-zip-fill:before { + content: "\ed1e"; +} +.ri-file-zip-line:before { + content: "\ed1f"; +} +.ri-film-fill:before { + content: "\ed20"; +} +.ri-film-line:before { + content: "\ed21"; +} +.ri-filter-2-fill:before { + content: "\ed22"; +} +.ri-filter-2-line:before { + content: "\ed23"; +} +.ri-filter-3-fill:before { + content: "\ed24"; +} +.ri-filter-3-line:before { + content: "\ed25"; +} +.ri-filter-fill:before { + content: "\ed26"; +} +.ri-filter-line:before { + content: "\ed27"; +} +.ri-filter-off-fill:before { + content: "\ed28"; +} +.ri-filter-off-line:before { + content: "\ed29"; +} +.ri-find-replace-fill:before { + content: "\ed2a"; +} +.ri-find-replace-line:before { + content: "\ed2b"; +} +.ri-finder-fill:before { + content: "\ed2c"; +} +.ri-finder-line:before { + content: "\ed2d"; +} +.ri-fingerprint-2-fill:before { + content: "\ed2e"; +} +.ri-fingerprint-2-line:before { + content: "\ed2f"; +} +.ri-fingerprint-fill:before { + content: "\ed30"; +} +.ri-fingerprint-line:before { + content: "\ed31"; +} +.ri-fire-fill:before { + content: "\ed32"; +} +.ri-fire-line:before { + content: "\ed33"; +} +.ri-firefox-fill:before { + content: "\ed34"; +} +.ri-firefox-line:before { + content: "\ed35"; +} +.ri-first-aid-kit-fill:before { + content: "\ed36"; +} +.ri-first-aid-kit-line:before { + content: "\ed37"; +} +.ri-flag-2-fill:before { + content: "\ed38"; +} +.ri-flag-2-line:before { + content: "\ed39"; +} +.ri-flag-fill:before { + content: "\ed3a"; +} +.ri-flag-line:before { + content: "\ed3b"; +} +.ri-flashlight-fill:before { + content: "\ed3c"; +} +.ri-flashlight-line:before { + content: "\ed3d"; +} +.ri-flask-fill:before { + content: "\ed3e"; +} +.ri-flask-line:before { + content: "\ed3f"; +} +.ri-flight-land-fill:before { + content: "\ed40"; +} +.ri-flight-land-line:before { + content: "\ed41"; +} +.ri-flight-takeoff-fill:before { + content: "\ed42"; +} +.ri-flight-takeoff-line:before { + content: "\ed43"; +} +.ri-flood-fill:before { + content: "\ed44"; +} +.ri-flood-line:before { + content: "\ed45"; +} +.ri-flow-chart:before { + content: "\ed46"; +} +.ri-flutter-fill:before { + content: "\ed47"; +} +.ri-flutter-line:before { + content: "\ed48"; +} +.ri-focus-2-fill:before { + content: "\ed49"; +} +.ri-focus-2-line:before { + content: "\ed4a"; +} +.ri-focus-3-fill:before { + content: "\ed4b"; +} +.ri-focus-3-line:before { + content: "\ed4c"; +} +.ri-focus-fill:before { + content: "\ed4d"; +} +.ri-focus-line:before { + content: "\ed4e"; +} +.ri-foggy-fill:before { + content: "\ed4f"; +} +.ri-foggy-line:before { + content: "\ed50"; +} +.ri-folder-2-fill:before { + content: "\ed51"; +} +.ri-folder-2-line:before { + content: "\ed52"; +} +.ri-folder-3-fill:before { + content: "\ed53"; +} +.ri-folder-3-line:before { + content: "\ed54"; +} +.ri-folder-4-fill:before { + content: "\ed55"; +} +.ri-folder-4-line:before { + content: "\ed56"; +} +.ri-folder-5-fill:before { + content: "\ed57"; +} +.ri-folder-5-line:before { + content: "\ed58"; +} +.ri-folder-add-fill:before { + content: "\ed59"; +} +.ri-folder-add-line:before { + content: "\ed5a"; +} +.ri-folder-chart-2-fill:before { + content: "\ed5b"; +} +.ri-folder-chart-2-line:before { + content: "\ed5c"; +} +.ri-folder-chart-fill:before { + content: "\ed5d"; +} +.ri-folder-chart-line:before { + content: "\ed5e"; +} +.ri-folder-download-fill:before { + content: "\ed5f"; +} +.ri-folder-download-line:before { + content: "\ed60"; +} +.ri-folder-fill:before { + content: "\ed61"; +} +.ri-folder-forbid-fill:before { + content: "\ed62"; +} +.ri-folder-forbid-line:before { + content: "\ed63"; +} +.ri-folder-history-fill:before { + content: "\ed64"; +} +.ri-folder-history-line:before { + content: "\ed65"; +} +.ri-folder-info-fill:before { + content: "\ed66"; +} +.ri-folder-info-line:before { + content: "\ed67"; +} +.ri-folder-keyhole-fill:before { + content: "\ed68"; +} +.ri-folder-keyhole-line:before { + content: "\ed69"; +} +.ri-folder-line:before { + content: "\ed6a"; +} +.ri-folder-lock-fill:before { + content: "\ed6b"; +} +.ri-folder-lock-line:before { + content: "\ed6c"; +} +.ri-folder-music-fill:before { + content: "\ed6d"; +} +.ri-folder-music-line:before { + content: "\ed6e"; +} +.ri-folder-open-fill:before { + content: "\ed6f"; +} +.ri-folder-open-line:before { + content: "\ed70"; +} +.ri-folder-received-fill:before { + content: "\ed71"; +} +.ri-folder-received-line:before { + content: "\ed72"; +} +.ri-folder-reduce-fill:before { + content: "\ed73"; +} +.ri-folder-reduce-line:before { + content: "\ed74"; +} +.ri-folder-settings-fill:before { + content: "\ed75"; +} +.ri-folder-settings-line:before { + content: "\ed76"; +} +.ri-folder-shared-fill:before { + content: "\ed77"; +} +.ri-folder-shared-line:before { + content: "\ed78"; +} +.ri-folder-shield-2-fill:before { + content: "\ed79"; +} +.ri-folder-shield-2-line:before { + content: "\ed7a"; +} +.ri-folder-shield-fill:before { + content: "\ed7b"; +} +.ri-folder-shield-line:before { + content: "\ed7c"; +} +.ri-folder-transfer-fill:before { + content: "\ed7d"; +} +.ri-folder-transfer-line:before { + content: "\ed7e"; +} +.ri-folder-unknow-fill:before { + content: "\ed7f"; +} +.ri-folder-unknow-line:before { + content: "\ed80"; +} +.ri-folder-upload-fill:before { + content: "\ed81"; +} +.ri-folder-upload-line:before { + content: "\ed82"; +} +.ri-folder-user-fill:before { + content: "\ed83"; +} +.ri-folder-user-line:before { + content: "\ed84"; +} +.ri-folder-warning-fill:before { + content: "\ed85"; +} +.ri-folder-warning-line:before { + content: "\ed86"; +} +.ri-folder-zip-fill:before { + content: "\ed87"; +} +.ri-folder-zip-line:before { + content: "\ed88"; +} +.ri-folders-fill:before { + content: "\ed89"; +} +.ri-folders-line:before { + content: "\ed8a"; +} +.ri-font-color:before { + content: "\ed8b"; +} +.ri-font-size-2:before { + content: "\ed8c"; +} +.ri-font-size:before { + content: "\ed8d"; +} +.ri-football-fill:before { + content: "\ed8e"; +} +.ri-football-line:before { + content: "\ed8f"; +} +.ri-footprint-fill:before { + content: "\ed90"; +} +.ri-footprint-line:before { + content: "\ed91"; +} +.ri-forbid-2-fill:before { + content: "\ed92"; +} +.ri-forbid-2-line:before { + content: "\ed93"; +} +.ri-forbid-fill:before { + content: "\ed94"; +} +.ri-forbid-line:before { + content: "\ed95"; +} +.ri-format-clear:before { + content: "\ed96"; +} +.ri-fridge-fill:before { + content: "\ed97"; +} +.ri-fridge-line:before { + content: "\ed98"; +} +.ri-fullscreen-exit-fill:before { + content: "\ed99"; +} +.ri-fullscreen-exit-line:before { + content: "\ed9a"; +} +.ri-fullscreen-fill:before { + content: "\ed9b"; +} +.ri-fullscreen-line:before { + content: "\ed9c"; +} +.ri-function-fill:before { + content: "\ed9d"; +} +.ri-function-line:before { + content: "\ed9e"; +} +.ri-functions:before { + content: "\ed9f"; +} +.ri-funds-box-fill:before { + content: "\eda0"; +} +.ri-funds-box-line:before { + content: "\eda1"; +} +.ri-funds-fill:before { + content: "\eda2"; +} +.ri-funds-line:before { + content: "\eda3"; +} +.ri-gallery-fill:before { + content: "\eda4"; +} +.ri-gallery-line:before { + content: "\eda5"; +} +.ri-gallery-upload-fill:before { + content: "\eda6"; +} +.ri-gallery-upload-line:before { + content: "\eda7"; +} +.ri-game-fill:before { + content: "\eda8"; +} +.ri-game-line:before { + content: "\eda9"; +} +.ri-gamepad-fill:before { + content: "\edaa"; +} +.ri-gamepad-line:before { + content: "\edab"; +} +.ri-gas-station-fill:before { + content: "\edac"; +} +.ri-gas-station-line:before { + content: "\edad"; +} +.ri-gatsby-fill:before { + content: "\edae"; +} +.ri-gatsby-line:before { + content: "\edaf"; +} +.ri-genderless-fill:before { + content: "\edb0"; +} +.ri-genderless-line:before { + content: "\edb1"; +} +.ri-ghost-2-fill:before { + content: "\edb2"; +} +.ri-ghost-2-line:before { + content: "\edb3"; +} +.ri-ghost-fill:before { + content: "\edb4"; +} +.ri-ghost-line:before { + content: "\edb5"; +} +.ri-ghost-smile-fill:before { + content: "\edb6"; +} +.ri-ghost-smile-line:before { + content: "\edb7"; +} +.ri-gift-2-fill:before { + content: "\edb8"; +} +.ri-gift-2-line:before { + content: "\edb9"; +} +.ri-gift-fill:before { + content: "\edba"; +} +.ri-gift-line:before { + content: "\edbb"; +} +.ri-git-branch-fill:before { + content: "\edbc"; +} +.ri-git-branch-line:before { + content: "\edbd"; +} +.ri-git-commit-fill:before { + content: "\edbe"; +} +.ri-git-commit-line:before { + content: "\edbf"; +} +.ri-git-merge-fill:before { + content: "\edc0"; +} +.ri-git-merge-line:before { + content: "\edc1"; +} +.ri-git-pull-request-fill:before { + content: "\edc2"; +} +.ri-git-pull-request-line:before { + content: "\edc3"; +} +.ri-git-repository-commits-fill:before { + content: "\edc4"; +} +.ri-git-repository-commits-line:before { + content: "\edc5"; +} +.ri-git-repository-fill:before { + content: "\edc6"; +} +.ri-git-repository-line:before { + content: "\edc7"; +} +.ri-git-repository-private-fill:before { + content: "\edc8"; +} +.ri-git-repository-private-line:before { + content: "\edc9"; +} +.ri-github-fill:before { + content: "\edca"; +} +.ri-github-line:before { + content: "\edcb"; +} +.ri-gitlab-fill:before { + content: "\edcc"; +} +.ri-gitlab-line:before { + content: "\edcd"; +} +.ri-global-fill:before { + content: "\edce"; +} +.ri-global-line:before { + content: "\edcf"; +} +.ri-globe-fill:before { + content: "\edd0"; +} +.ri-globe-line:before { + content: "\edd1"; +} +.ri-goblet-fill:before { + content: "\edd2"; +} +.ri-goblet-line:before { + content: "\edd3"; +} +.ri-google-fill:before { + content: "\edd4"; +} +.ri-google-line:before { + content: "\edd5"; +} +.ri-google-play-fill:before { + content: "\edd6"; +} +.ri-google-play-line:before { + content: "\edd7"; +} +.ri-government-fill:before { + content: "\edd8"; +} +.ri-government-line:before { + content: "\edd9"; +} +.ri-gps-fill:before { + content: "\edda"; +} +.ri-gps-line:before { + content: "\eddb"; +} +.ri-gradienter-fill:before { + content: "\eddc"; +} +.ri-gradienter-line:before { + content: "\eddd"; +} +.ri-grid-fill:before { + content: "\edde"; +} +.ri-grid-line:before { + content: "\eddf"; +} +.ri-group-2-fill:before { + content: "\ede0"; +} +.ri-group-2-line:before { + content: "\ede1"; +} +.ri-group-fill:before { + content: "\ede2"; +} +.ri-group-line:before { + content: "\ede3"; +} +.ri-guide-fill:before { + content: "\ede4"; +} +.ri-guide-line:before { + content: "\ede5"; +} +.ri-h-1:before { + content: "\ede6"; +} +.ri-h-2:before { + content: "\ede7"; +} +.ri-h-3:before { + content: "\ede8"; +} +.ri-h-4:before { + content: "\ede9"; +} +.ri-h-5:before { + content: "\edea"; +} +.ri-h-6:before { + content: "\edeb"; +} +.ri-hail-fill:before { + content: "\edec"; +} +.ri-hail-line:before { + content: "\eded"; +} +.ri-hammer-fill:before { + content: "\edee"; +} +.ri-hammer-line:before { + content: "\edef"; +} +.ri-hand-coin-fill:before { + content: "\edf0"; +} +.ri-hand-coin-line:before { + content: "\edf1"; +} +.ri-hand-heart-fill:before { + content: "\edf2"; +} +.ri-hand-heart-line:before { + content: "\edf3"; +} +.ri-hand-sanitizer-fill:before { + content: "\edf4"; +} +.ri-hand-sanitizer-line:before { + content: "\edf5"; +} +.ri-handbag-fill:before { + content: "\edf6"; +} +.ri-handbag-line:before { + content: "\edf7"; +} +.ri-hard-drive-2-fill:before { + content: "\edf8"; +} +.ri-hard-drive-2-line:before { + content: "\edf9"; +} +.ri-hard-drive-fill:before { + content: "\edfa"; +} +.ri-hard-drive-line:before { + content: "\edfb"; +} +.ri-hashtag:before { + content: "\edfc"; +} +.ri-haze-2-fill:before { + content: "\edfd"; +} +.ri-haze-2-line:before { + content: "\edfe"; +} +.ri-haze-fill:before { + content: "\edff"; +} +.ri-haze-line:before { + content: "\ee00"; +} +.ri-hd-fill:before { + content: "\ee01"; +} +.ri-hd-line:before { + content: "\ee02"; +} +.ri-heading:before { + content: "\ee03"; +} +.ri-headphone-fill:before { + content: "\ee04"; +} +.ri-headphone-line:before { + content: "\ee05"; +} +.ri-health-book-fill:before { + content: "\ee06"; +} +.ri-health-book-line:before { + content: "\ee07"; +} +.ri-heart-2-fill:before { + content: "\ee08"; +} +.ri-heart-2-line:before { + content: "\ee09"; +} +.ri-heart-3-fill:before { + content: "\ee0a"; +} +.ri-heart-3-line:before { + content: "\ee0b"; +} +.ri-heart-add-fill:before { + content: "\ee0c"; +} +.ri-heart-add-line:before { + content: "\ee0d"; +} +.ri-heart-fill:before { + content: "\ee0e"; +} +.ri-heart-line:before { + content: "\ee0f"; +} +.ri-heart-pulse-fill:before { + content: "\ee10"; +} +.ri-heart-pulse-line:before { + content: "\ee11"; +} +.ri-hearts-fill:before { + content: "\ee12"; +} +.ri-hearts-line:before { + content: "\ee13"; +} +.ri-heavy-showers-fill:before { + content: "\ee14"; +} +.ri-heavy-showers-line:before { + content: "\ee15"; +} +.ri-history-fill:before { + content: "\ee16"; +} +.ri-history-line:before { + content: "\ee17"; +} +.ri-home-2-fill:before { + content: "\ee18"; +} +.ri-home-2-line:before { + content: "\ee19"; +} +.ri-home-3-fill:before { + content: "\ee1a"; +} +.ri-home-3-line:before { + content: "\ee1b"; +} +.ri-home-4-fill:before { + content: "\ee1c"; +} +.ri-home-4-line:before { + content: "\ee1d"; +} +.ri-home-5-fill:before { + content: "\ee1e"; +} +.ri-home-5-line:before { + content: "\ee1f"; +} +.ri-home-6-fill:before { + content: "\ee20"; +} +.ri-home-6-line:before { + content: "\ee21"; +} +.ri-home-7-fill:before { + content: "\ee22"; +} +.ri-home-7-line:before { + content: "\ee23"; +} +.ri-home-8-fill:before { + content: "\ee24"; +} +.ri-home-8-line:before { + content: "\ee25"; +} +.ri-home-fill:before { + content: "\ee26"; +} +.ri-home-gear-fill:before { + content: "\ee27"; +} +.ri-home-gear-line:before { + content: "\ee28"; +} +.ri-home-heart-fill:before { + content: "\ee29"; +} +.ri-home-heart-line:before { + content: "\ee2a"; +} +.ri-home-line:before { + content: "\ee2b"; +} +.ri-home-smile-2-fill:before { + content: "\ee2c"; +} +.ri-home-smile-2-line:before { + content: "\ee2d"; +} +.ri-home-smile-fill:before { + content: "\ee2e"; +} +.ri-home-smile-line:before { + content: "\ee2f"; +} +.ri-home-wifi-fill:before { + content: "\ee30"; +} +.ri-home-wifi-line:before { + content: "\ee31"; +} +.ri-honor-of-kings-fill:before { + content: "\ee32"; +} +.ri-honor-of-kings-line:before { + content: "\ee33"; +} +.ri-honour-fill:before { + content: "\ee34"; +} +.ri-honour-line:before { + content: "\ee35"; +} +.ri-hospital-fill:before { + content: "\ee36"; +} +.ri-hospital-line:before { + content: "\ee37"; +} +.ri-hotel-bed-fill:before { + content: "\ee38"; +} +.ri-hotel-bed-line:before { + content: "\ee39"; +} +.ri-hotel-fill:before { + content: "\ee3a"; +} +.ri-hotel-line:before { + content: "\ee3b"; +} +.ri-hotspot-fill:before { + content: "\ee3c"; +} +.ri-hotspot-line:before { + content: "\ee3d"; +} +.ri-hq-fill:before { + content: "\ee3e"; +} +.ri-hq-line:before { + content: "\ee3f"; +} +.ri-html5-fill:before { + content: "\ee40"; +} +.ri-html5-line:before { + content: "\ee41"; +} +.ri-ie-fill:before { + content: "\ee42"; +} +.ri-ie-line:before { + content: "\ee43"; +} +.ri-image-2-fill:before { + content: "\ee44"; +} +.ri-image-2-line:before { + content: "\ee45"; +} +.ri-image-add-fill:before { + content: "\ee46"; +} +.ri-image-add-line:before { + content: "\ee47"; +} +.ri-image-edit-fill:before { + content: "\ee48"; +} +.ri-image-edit-line:before { + content: "\ee49"; +} +.ri-image-fill:before { + content: "\ee4a"; +} +.ri-image-line:before { + content: "\ee4b"; +} +.ri-inbox-archive-fill:before { + content: "\ee4c"; +} +.ri-inbox-archive-line:before { + content: "\ee4d"; +} +.ri-inbox-fill:before { + content: "\ee4e"; +} +.ri-inbox-line:before { + content: "\ee4f"; +} +.ri-inbox-unarchive-fill:before { + content: "\ee50"; +} +.ri-inbox-unarchive-line:before { + content: "\ee51"; +} +.ri-increase-decrease-fill:before { + content: "\ee52"; +} +.ri-increase-decrease-line:before { + content: "\ee53"; +} +.ri-indent-decrease:before { + content: "\ee54"; +} +.ri-indent-increase:before { + content: "\ee55"; +} +.ri-indeterminate-circle-fill:before { + content: "\ee56"; +} +.ri-indeterminate-circle-line:before { + content: "\ee57"; +} +.ri-information-fill:before { + content: "\ee58"; +} +.ri-information-line:before { + content: "\ee59"; +} +.ri-infrared-thermometer-fill:before { + content: "\ee5a"; +} +.ri-infrared-thermometer-line:before { + content: "\ee5b"; +} +.ri-ink-bottle-fill:before { + content: "\ee5c"; +} +.ri-ink-bottle-line:before { + content: "\ee5d"; +} +.ri-input-cursor-move:before { + content: "\ee5e"; +} +.ri-input-method-fill:before { + content: "\ee5f"; +} +.ri-input-method-line:before { + content: "\ee60"; +} +.ri-insert-column-left:before { + content: "\ee61"; +} +.ri-insert-column-right:before { + content: "\ee62"; +} +.ri-insert-row-bottom:before { + content: "\ee63"; +} +.ri-insert-row-top:before { + content: "\ee64"; +} +.ri-instagram-fill:before { + content: "\ee65"; +} +.ri-instagram-line:before { + content: "\ee66"; +} +.ri-install-fill:before { + content: "\ee67"; +} +.ri-install-line:before { + content: "\ee68"; +} +.ri-invision-fill:before { + content: "\ee69"; +} +.ri-invision-line:before { + content: "\ee6a"; +} +.ri-italic:before { + content: "\ee6b"; +} +.ri-kakao-talk-fill:before { + content: "\ee6c"; +} +.ri-kakao-talk-line:before { + content: "\ee6d"; +} +.ri-key-2-fill:before { + content: "\ee6e"; +} +.ri-key-2-line:before { + content: "\ee6f"; +} +.ri-key-fill:before { + content: "\ee70"; +} +.ri-key-line:before { + content: "\ee71"; +} +.ri-keyboard-box-fill:before { + content: "\ee72"; +} +.ri-keyboard-box-line:before { + content: "\ee73"; +} +.ri-keyboard-fill:before { + content: "\ee74"; +} +.ri-keyboard-line:before { + content: "\ee75"; +} +.ri-keynote-fill:before { + content: "\ee76"; +} +.ri-keynote-line:before { + content: "\ee77"; +} +.ri-knife-blood-fill:before { + content: "\ee78"; +} +.ri-knife-blood-line:before { + content: "\ee79"; +} +.ri-knife-fill:before { + content: "\ee7a"; +} +.ri-knife-line:before { + content: "\ee7b"; +} +.ri-landscape-fill:before { + content: "\ee7c"; +} +.ri-landscape-line:before { + content: "\ee7d"; +} +.ri-layout-2-fill:before { + content: "\ee7e"; +} +.ri-layout-2-line:before { + content: "\ee7f"; +} +.ri-layout-3-fill:before { + content: "\ee80"; +} +.ri-layout-3-line:before { + content: "\ee81"; +} +.ri-layout-4-fill:before { + content: "\ee82"; +} +.ri-layout-4-line:before { + content: "\ee83"; +} +.ri-layout-5-fill:before { + content: "\ee84"; +} +.ri-layout-5-line:before { + content: "\ee85"; +} +.ri-layout-6-fill:before { + content: "\ee86"; +} +.ri-layout-6-line:before { + content: "\ee87"; +} +.ri-layout-bottom-2-fill:before { + content: "\ee88"; +} +.ri-layout-bottom-2-line:before { + content: "\ee89"; +} +.ri-layout-bottom-fill:before { + content: "\ee8a"; +} +.ri-layout-bottom-line:before { + content: "\ee8b"; +} +.ri-layout-column-fill:before { + content: "\ee8c"; +} +.ri-layout-column-line:before { + content: "\ee8d"; +} +.ri-layout-fill:before { + content: "\ee8e"; +} +.ri-layout-grid-fill:before { + content: "\ee8f"; +} +.ri-layout-grid-line:before { + content: "\ee90"; +} +.ri-layout-left-2-fill:before { + content: "\ee91"; +} +.ri-layout-left-2-line:before { + content: "\ee92"; +} +.ri-layout-left-fill:before { + content: "\ee93"; +} +.ri-layout-left-line:before { + content: "\ee94"; +} +.ri-layout-line:before { + content: "\ee95"; +} +.ri-layout-masonry-fill:before { + content: "\ee96"; +} +.ri-layout-masonry-line:before { + content: "\ee97"; +} +.ri-layout-right-2-fill:before { + content: "\ee98"; +} +.ri-layout-right-2-line:before { + content: "\ee99"; +} +.ri-layout-right-fill:before { + content: "\ee9a"; +} +.ri-layout-right-line:before { + content: "\ee9b"; +} +.ri-layout-row-fill:before { + content: "\ee9c"; +} +.ri-layout-row-line:before { + content: "\ee9d"; +} +.ri-layout-top-2-fill:before { + content: "\ee9e"; +} +.ri-layout-top-2-line:before { + content: "\ee9f"; +} +.ri-layout-top-fill:before { + content: "\eea0"; +} +.ri-layout-top-line:before { + content: "\eea1"; +} +.ri-leaf-fill:before { + content: "\eea2"; +} +.ri-leaf-line:before { + content: "\eea3"; +} +.ri-lifebuoy-fill:before { + content: "\eea4"; +} +.ri-lifebuoy-line:before { + content: "\eea5"; +} +.ri-lightbulb-fill:before { + content: "\eea6"; +} +.ri-lightbulb-flash-fill:before { + content: "\eea7"; +} +.ri-lightbulb-flash-line:before { + content: "\eea8"; +} +.ri-lightbulb-line:before { + content: "\eea9"; +} +.ri-line-chart-fill:before { + content: "\eeaa"; +} +.ri-line-chart-line:before { + content: "\eeab"; +} +.ri-line-fill:before { + content: "\eeac"; +} +.ri-line-height:before { + content: "\eead"; +} +.ri-line-line:before { + content: "\eeae"; +} +.ri-link-m:before { + content: "\eeaf"; +} +.ri-link-unlink-m:before { + content: "\eeb0"; +} +.ri-link-unlink:before { + content: "\eeb1"; +} +.ri-link:before { + content: "\eeb2"; +} +.ri-linkedin-box-fill:before { + content: "\eeb3"; +} +.ri-linkedin-box-line:before { + content: "\eeb4"; +} +.ri-linkedin-fill:before { + content: "\eeb5"; +} +.ri-linkedin-line:before { + content: "\eeb6"; +} +.ri-links-fill:before { + content: "\eeb7"; +} +.ri-links-line:before { + content: "\eeb8"; +} +.ri-list-check-2:before { + content: "\eeb9"; +} +.ri-list-check:before { + content: "\eeba"; +} +.ri-list-ordered:before { + content: "\eebb"; +} +.ri-list-settings-fill:before { + content: "\eebc"; +} +.ri-list-settings-line:before { + content: "\eebd"; +} +.ri-list-unordered:before { + content: "\eebe"; +} +.ri-live-fill:before { + content: "\eebf"; +} +.ri-live-line:before { + content: "\eec0"; +} +.ri-loader-2-fill:before { + content: "\eec1"; +} +.ri-loader-2-line:before { + content: "\eec2"; +} +.ri-loader-3-fill:before { + content: "\eec3"; +} +.ri-loader-3-line:before { + content: "\eec4"; +} +.ri-loader-4-fill:before { + content: "\eec5"; +} +.ri-loader-4-line:before { + content: "\eec6"; +} +.ri-loader-5-fill:before { + content: "\eec7"; +} +.ri-loader-5-line:before { + content: "\eec8"; +} +.ri-loader-fill:before { + content: "\eec9"; +} +.ri-loader-line:before { + content: "\eeca"; +} +.ri-lock-2-fill:before { + content: "\eecb"; +} +.ri-lock-2-line:before { + content: "\eecc"; +} +.ri-lock-fill:before { + content: "\eecd"; +} +.ri-lock-line:before { + content: "\eece"; +} +.ri-lock-password-fill:before { + content: "\eecf"; +} +.ri-lock-password-line:before { + content: "\eed0"; +} +.ri-lock-unlock-fill:before { + content: "\eed1"; +} +.ri-lock-unlock-line:before { + content: "\eed2"; +} +.ri-login-box-fill:before { + content: "\eed3"; +} +.ri-login-box-line:before { + content: "\eed4"; +} +.ri-login-circle-fill:before { + content: "\eed5"; +} +.ri-login-circle-line:before { + content: "\eed6"; +} +.ri-logout-box-fill:before { + content: "\eed7"; +} +.ri-logout-box-line:before { + content: "\eed8"; +} +.ri-logout-box-r-fill:before { + content: "\eed9"; +} +.ri-logout-box-r-line:before { + content: "\eeda"; +} +.ri-logout-circle-fill:before { + content: "\eedb"; +} +.ri-logout-circle-line:before { + content: "\eedc"; +} +.ri-logout-circle-r-fill:before { + content: "\eedd"; +} +.ri-logout-circle-r-line:before { + content: "\eede"; +} +.ri-luggage-cart-fill:before { + content: "\eedf"; +} +.ri-luggage-cart-line:before { + content: "\eee0"; +} +.ri-luggage-deposit-fill:before { + content: "\eee1"; +} +.ri-luggage-deposit-line:before { + content: "\eee2"; +} +.ri-lungs-fill:before { + content: "\eee3"; +} +.ri-lungs-line:before { + content: "\eee4"; +} +.ri-mac-fill:before { + content: "\eee5"; +} +.ri-mac-line:before { + content: "\eee6"; +} +.ri-macbook-fill:before { + content: "\eee7"; +} +.ri-macbook-line:before { + content: "\eee8"; +} +.ri-magic-fill:before { + content: "\eee9"; +} +.ri-magic-line:before { + content: "\eeea"; +} +.ri-mail-add-fill:before { + content: "\eeeb"; +} +.ri-mail-add-line:before { + content: "\eeec"; +} +.ri-mail-check-fill:before { + content: "\eeed"; +} +.ri-mail-check-line:before { + content: "\eeee"; +} +.ri-mail-close-fill:before { + content: "\eeef"; +} +.ri-mail-close-line:before { + content: "\eef0"; +} +.ri-mail-download-fill:before { + content: "\eef1"; +} +.ri-mail-download-line:before { + content: "\eef2"; +} +.ri-mail-fill:before { + content: "\eef3"; +} +.ri-mail-forbid-fill:before { + content: "\eef4"; +} +.ri-mail-forbid-line:before { + content: "\eef5"; +} +.ri-mail-line:before { + content: "\eef6"; +} +.ri-mail-lock-fill:before { + content: "\eef7"; +} +.ri-mail-lock-line:before { + content: "\eef8"; +} +.ri-mail-open-fill:before { + content: "\eef9"; +} +.ri-mail-open-line:before { + content: "\eefa"; +} +.ri-mail-send-fill:before { + content: "\eefb"; +} +.ri-mail-send-line:before { + content: "\eefc"; +} +.ri-mail-settings-fill:before { + content: "\eefd"; +} +.ri-mail-settings-line:before { + content: "\eefe"; +} +.ri-mail-star-fill:before { + content: "\eeff"; +} +.ri-mail-star-line:before { + content: "\ef00"; +} +.ri-mail-unread-fill:before { + content: "\ef01"; +} +.ri-mail-unread-line:before { + content: "\ef02"; +} +.ri-mail-volume-fill:before { + content: "\ef03"; +} +.ri-mail-volume-line:before { + content: "\ef04"; +} +.ri-map-2-fill:before { + content: "\ef05"; +} +.ri-map-2-line:before { + content: "\ef06"; +} +.ri-map-fill:before { + content: "\ef07"; +} +.ri-map-line:before { + content: "\ef08"; +} +.ri-map-pin-2-fill:before { + content: "\ef09"; +} +.ri-map-pin-2-line:before { + content: "\ef0a"; +} +.ri-map-pin-3-fill:before { + content: "\ef0b"; +} +.ri-map-pin-3-line:before { + content: "\ef0c"; +} +.ri-map-pin-4-fill:before { + content: "\ef0d"; +} +.ri-map-pin-4-line:before { + content: "\ef0e"; +} +.ri-map-pin-5-fill:before { + content: "\ef0f"; +} +.ri-map-pin-5-line:before { + content: "\ef10"; +} +.ri-map-pin-add-fill:before { + content: "\ef11"; +} +.ri-map-pin-add-line:before { + content: "\ef12"; +} +.ri-map-pin-fill:before { + content: "\ef13"; +} +.ri-map-pin-line:before { + content: "\ef14"; +} +.ri-map-pin-range-fill:before { + content: "\ef15"; +} +.ri-map-pin-range-line:before { + content: "\ef16"; +} +.ri-map-pin-time-fill:before { + content: "\ef17"; +} +.ri-map-pin-time-line:before { + content: "\ef18"; +} +.ri-map-pin-user-fill:before { + content: "\ef19"; +} +.ri-map-pin-user-line:before { + content: "\ef1a"; +} +.ri-mark-pen-fill:before { + content: "\ef1b"; +} +.ri-mark-pen-line:before { + content: "\ef1c"; +} +.ri-markdown-fill:before { + content: "\ef1d"; +} +.ri-markdown-line:before { + content: "\ef1e"; +} +.ri-markup-fill:before { + content: "\ef1f"; +} +.ri-markup-line:before { + content: "\ef20"; +} +.ri-mastercard-fill:before { + content: "\ef21"; +} +.ri-mastercard-line:before { + content: "\ef22"; +} +.ri-mastodon-fill:before { + content: "\ef23"; +} +.ri-mastodon-line:before { + content: "\ef24"; +} +.ri-medal-2-fill:before { + content: "\ef25"; +} +.ri-medal-2-line:before { + content: "\ef26"; +} +.ri-medal-fill:before { + content: "\ef27"; +} +.ri-medal-line:before { + content: "\ef28"; +} +.ri-medicine-bottle-fill:before { + content: "\ef29"; +} +.ri-medicine-bottle-line:before { + content: "\ef2a"; +} +.ri-medium-fill:before { + content: "\ef2b"; +} +.ri-medium-line:before { + content: "\ef2c"; +} +.ri-men-fill:before { + content: "\ef2d"; +} +.ri-men-line:before { + content: "\ef2e"; +} +.ri-mental-health-fill:before { + content: "\ef2f"; +} +.ri-mental-health-line:before { + content: "\ef30"; +} +.ri-menu-2-fill:before { + content: "\ef31"; +} +.ri-menu-2-line:before { + content: "\ef32"; +} +.ri-menu-3-fill:before { + content: "\ef33"; +} +.ri-menu-3-line:before { + content: "\ef34"; +} +.ri-menu-4-fill:before { + content: "\ef35"; +} +.ri-menu-4-line:before { + content: "\ef36"; +} +.ri-menu-5-fill:before { + content: "\ef37"; +} +.ri-menu-5-line:before { + content: "\ef38"; +} +.ri-menu-add-fill:before { + content: "\ef39"; +} +.ri-menu-add-line:before { + content: "\ef3a"; +} +.ri-menu-fill:before { + content: "\ef3b"; +} +.ri-menu-fold-fill:before { + content: "\ef3c"; +} +.ri-menu-fold-line:before { + content: "\ef3d"; +} +.ri-menu-line:before { + content: "\ef3e"; +} +.ri-menu-unfold-fill:before { + content: "\ef3f"; +} +.ri-menu-unfold-line:before { + content: "\ef40"; +} +.ri-merge-cells-horizontal:before { + content: "\ef41"; +} +.ri-merge-cells-vertical:before { + content: "\ef42"; +} +.ri-message-2-fill:before { + content: "\ef43"; +} +.ri-message-2-line:before { + content: "\ef44"; +} +.ri-message-3-fill:before { + content: "\ef45"; +} +.ri-message-3-line:before { + content: "\ef46"; +} +.ri-message-fill:before { + content: "\ef47"; +} +.ri-message-line:before { + content: "\ef48"; +} +.ri-messenger-fill:before { + content: "\ef49"; +} +.ri-messenger-line:before { + content: "\ef4a"; +} +.ri-meteor-fill:before { + content: "\ef4b"; +} +.ri-meteor-line:before { + content: "\ef4c"; +} +.ri-mic-2-fill:before { + content: "\ef4d"; +} +.ri-mic-2-line:before { + content: "\ef4e"; +} +.ri-mic-fill:before { + content: "\ef4f"; +} +.ri-mic-line:before { + content: "\ef50"; +} +.ri-mic-off-fill:before { + content: "\ef51"; +} +.ri-mic-off-line:before { + content: "\ef52"; +} +.ri-mickey-fill:before { + content: "\ef53"; +} +.ri-mickey-line:before { + content: "\ef54"; +} +.ri-microscope-fill:before { + content: "\ef55"; +} +.ri-microscope-line:before { + content: "\ef56"; +} +.ri-microsoft-fill:before { + content: "\ef57"; +} +.ri-microsoft-line:before { + content: "\ef58"; +} +.ri-mind-map:before { + content: "\ef59"; +} +.ri-mini-program-fill:before { + content: "\ef5a"; +} +.ri-mini-program-line:before { + content: "\ef5b"; +} +.ri-mist-fill:before { + content: "\ef5c"; +} +.ri-mist-line:before { + content: "\ef5d"; +} +.ri-money-cny-box-fill:before { + content: "\ef5e"; +} +.ri-money-cny-box-line:before { + content: "\ef5f"; +} +.ri-money-cny-circle-fill:before { + content: "\ef60"; +} +.ri-money-cny-circle-line:before { + content: "\ef61"; +} +.ri-money-dollar-box-fill:before { + content: "\ef62"; +} +.ri-money-dollar-box-line:before { + content: "\ef63"; +} +.ri-money-dollar-circle-fill:before { + content: "\ef64"; +} +.ri-money-dollar-circle-line:before { + content: "\ef65"; +} +.ri-money-euro-box-fill:before { + content: "\ef66"; +} +.ri-money-euro-box-line:before { + content: "\ef67"; +} +.ri-money-euro-circle-fill:before { + content: "\ef68"; +} +.ri-money-euro-circle-line:before { + content: "\ef69"; +} +.ri-money-pound-box-fill:before { + content: "\ef6a"; +} +.ri-money-pound-box-line:before { + content: "\ef6b"; +} +.ri-money-pound-circle-fill:before { + content: "\ef6c"; +} +.ri-money-pound-circle-line:before { + content: "\ef6d"; +} +.ri-moon-clear-fill:before { + content: "\ef6e"; +} +.ri-moon-clear-line:before { + content: "\ef6f"; +} +.ri-moon-cloudy-fill:before { + content: "\ef70"; +} +.ri-moon-cloudy-line:before { + content: "\ef71"; +} +.ri-moon-fill:before { + content: "\ef72"; +} +.ri-moon-foggy-fill:before { + content: "\ef73"; +} +.ri-moon-foggy-line:before { + content: "\ef74"; +} +.ri-moon-line:before { + content: "\ef75"; +} +.ri-more-2-fill:before { + content: "\ef76"; +} +.ri-more-2-line:before { + content: "\ef77"; +} +.ri-more-fill:before { + content: "\ef78"; +} +.ri-more-line:before { + content: "\ef79"; +} +.ri-motorbike-fill:before { + content: "\ef7a"; +} +.ri-motorbike-line:before { + content: "\ef7b"; +} +.ri-mouse-fill:before { + content: "\ef7c"; +} +.ri-mouse-line:before { + content: "\ef7d"; +} +.ri-movie-2-fill:before { + content: "\ef7e"; +} +.ri-movie-2-line:before { + content: "\ef7f"; +} +.ri-movie-fill:before { + content: "\ef80"; +} +.ri-movie-line:before { + content: "\ef81"; +} +.ri-music-2-fill:before { + content: "\ef82"; +} +.ri-music-2-line:before { + content: "\ef83"; +} +.ri-music-fill:before { + content: "\ef84"; +} +.ri-music-line:before { + content: "\ef85"; +} +.ri-mv-fill:before { + content: "\ef86"; +} +.ri-mv-line:before { + content: "\ef87"; +} +.ri-navigation-fill:before { + content: "\ef88"; +} +.ri-navigation-line:before { + content: "\ef89"; +} +.ri-netease-cloud-music-fill:before { + content: "\ef8a"; +} +.ri-netease-cloud-music-line:before { + content: "\ef8b"; +} +.ri-netflix-fill:before { + content: "\ef8c"; +} +.ri-netflix-line:before { + content: "\ef8d"; +} +.ri-newspaper-fill:before { + content: "\ef8e"; +} +.ri-newspaper-line:before { + content: "\ef8f"; +} +.ri-node-tree:before { + content: "\ef90"; +} +.ri-notification-2-fill:before { + content: "\ef91"; +} +.ri-notification-2-line:before { + content: "\ef92"; +} +.ri-notification-3-fill:before { + content: "\ef93"; +} +.ri-notification-3-line:before { + content: "\ef94"; +} +.ri-notification-4-fill:before { + content: "\ef95"; +} +.ri-notification-4-line:before { + content: "\ef96"; +} +.ri-notification-badge-fill:before { + content: "\ef97"; +} +.ri-notification-badge-line:before { + content: "\ef98"; +} +.ri-notification-fill:before { + content: "\ef99"; +} +.ri-notification-line:before { + content: "\ef9a"; +} +.ri-notification-off-fill:before { + content: "\ef9b"; +} +.ri-notification-off-line:before { + content: "\ef9c"; +} +.ri-npmjs-fill:before { + content: "\ef9d"; +} +.ri-npmjs-line:before { + content: "\ef9e"; +} +.ri-number-0:before { + content: "\ef9f"; +} +.ri-number-1:before { + content: "\efa0"; +} +.ri-number-2:before { + content: "\efa1"; +} +.ri-number-3:before { + content: "\efa2"; +} +.ri-number-4:before { + content: "\efa3"; +} +.ri-number-5:before { + content: "\efa4"; +} +.ri-number-6:before { + content: "\efa5"; +} +.ri-number-7:before { + content: "\efa6"; +} +.ri-number-8:before { + content: "\efa7"; +} +.ri-number-9:before { + content: "\efa8"; +} +.ri-numbers-fill:before { + content: "\efa9"; +} +.ri-numbers-line:before { + content: "\efaa"; +} +.ri-nurse-fill:before { + content: "\efab"; +} +.ri-nurse-line:before { + content: "\efac"; +} +.ri-oil-fill:before { + content: "\efad"; +} +.ri-oil-line:before { + content: "\efae"; +} +.ri-omega:before { + content: "\efaf"; +} +.ri-open-arm-fill:before { + content: "\efb0"; +} +.ri-open-arm-line:before { + content: "\efb1"; +} +.ri-open-source-fill:before { + content: "\efb2"; +} +.ri-open-source-line:before { + content: "\efb3"; +} +.ri-opera-fill:before { + content: "\efb4"; +} +.ri-opera-line:before { + content: "\efb5"; +} +.ri-order-play-fill:before { + content: "\efb6"; +} +.ri-order-play-line:before { + content: "\efb7"; +} +.ri-organization-chart:before { + content: "\efb8"; +} +.ri-outlet-2-fill:before { + content: "\efb9"; +} +.ri-outlet-2-line:before { + content: "\efba"; +} +.ri-outlet-fill:before { + content: "\efbb"; +} +.ri-outlet-line:before { + content: "\efbc"; +} +.ri-page-separator:before { + content: "\efbd"; +} +.ri-pages-fill:before { + content: "\efbe"; +} +.ri-pages-line:before { + content: "\efbf"; +} +.ri-paint-brush-fill:before { + content: "\efc0"; +} +.ri-paint-brush-line:before { + content: "\efc1"; +} +.ri-paint-fill:before { + content: "\efc2"; +} +.ri-paint-line:before { + content: "\efc3"; +} +.ri-palette-fill:before { + content: "\efc4"; +} +.ri-palette-line:before { + content: "\efc5"; +} +.ri-pantone-fill:before { + content: "\efc6"; +} +.ri-pantone-line:before { + content: "\efc7"; +} +.ri-paragraph:before { + content: "\efc8"; +} +.ri-parent-fill:before { + content: "\efc9"; +} +.ri-parent-line:before { + content: "\efca"; +} +.ri-parentheses-fill:before { + content: "\efcb"; +} +.ri-parentheses-line:before { + content: "\efcc"; +} +.ri-parking-box-fill:before { + content: "\efcd"; +} +.ri-parking-box-line:before { + content: "\efce"; +} +.ri-parking-fill:before { + content: "\efcf"; +} +.ri-parking-line:before { + content: "\efd0"; +} +.ri-passport-fill:before { + content: "\efd1"; +} +.ri-passport-line:before { + content: "\efd2"; +} +.ri-patreon-fill:before { + content: "\efd3"; +} +.ri-patreon-line:before { + content: "\efd4"; +} +.ri-pause-circle-fill:before { + content: "\efd5"; +} +.ri-pause-circle-line:before { + content: "\efd6"; +} +.ri-pause-fill:before { + content: "\efd7"; +} +.ri-pause-line:before { + content: "\efd8"; +} +.ri-pause-mini-fill:before { + content: "\efd9"; +} +.ri-pause-mini-line:before { + content: "\efda"; +} +.ri-paypal-fill:before { + content: "\efdb"; +} +.ri-paypal-line:before { + content: "\efdc"; +} +.ri-pen-nib-fill:before { + content: "\efdd"; +} +.ri-pen-nib-line:before { + content: "\efde"; +} +.ri-pencil-fill:before { + content: "\efdf"; +} +.ri-pencil-line:before { + content: "\efe0"; +} +.ri-pencil-ruler-2-fill:before { + content: "\efe1"; +} +.ri-pencil-ruler-2-line:before { + content: "\efe2"; +} +.ri-pencil-ruler-fill:before { + content: "\efe3"; +} +.ri-pencil-ruler-line:before { + content: "\efe4"; +} +.ri-percent-fill:before { + content: "\efe5"; +} +.ri-percent-line:before { + content: "\efe6"; +} +.ri-phone-camera-fill:before { + content: "\efe7"; +} +.ri-phone-camera-line:before { + content: "\efe8"; +} +.ri-phone-fill:before { + content: "\efe9"; +} +.ri-phone-find-fill:before { + content: "\efea"; +} +.ri-phone-find-line:before { + content: "\efeb"; +} +.ri-phone-line:before { + content: "\efec"; +} +.ri-phone-lock-fill:before { + content: "\efed"; +} +.ri-phone-lock-line:before { + content: "\efee"; +} +.ri-picture-in-picture-2-fill:before { + content: "\efef"; +} +.ri-picture-in-picture-2-line:before { + content: "\eff0"; +} +.ri-picture-in-picture-exit-fill:before { + content: "\eff1"; +} +.ri-picture-in-picture-exit-line:before { + content: "\eff2"; +} +.ri-picture-in-picture-fill:before { + content: "\eff3"; +} +.ri-picture-in-picture-line:before { + content: "\eff4"; +} +.ri-pie-chart-2-fill:before { + content: "\eff5"; +} +.ri-pie-chart-2-line:before { + content: "\eff6"; +} +.ri-pie-chart-box-fill:before { + content: "\eff7"; +} +.ri-pie-chart-box-line:before { + content: "\eff8"; +} +.ri-pie-chart-fill:before { + content: "\eff9"; +} +.ri-pie-chart-line:before { + content: "\effa"; +} +.ri-pin-distance-fill:before { + content: "\effb"; +} +.ri-pin-distance-line:before { + content: "\effc"; +} +.ri-ping-pong-fill:before { + content: "\effd"; +} +.ri-ping-pong-line:before { + content: "\effe"; +} +.ri-pinterest-fill:before { + content: "\efff"; +} +.ri-pinterest-line:before { + content: "\f000"; +} +.ri-pinyin-input:before { + content: "\f001"; +} +.ri-pixelfed-fill:before { + content: "\f002"; +} +.ri-pixelfed-line:before { + content: "\f003"; +} +.ri-plane-fill:before { + content: "\f004"; +} +.ri-plane-line:before { + content: "\f005"; +} +.ri-plant-fill:before { + content: "\f006"; +} +.ri-plant-line:before { + content: "\f007"; +} +.ri-play-circle-fill:before { + content: "\f008"; +} +.ri-play-circle-line:before { + content: "\f009"; +} +.ri-play-fill:before { + content: "\f00a"; +} +.ri-play-line:before { + content: "\f00b"; +} +.ri-play-list-2-fill:before { + content: "\f00c"; +} +.ri-play-list-2-line:before { + content: "\f00d"; +} +.ri-play-list-add-fill:before { + content: "\f00e"; +} +.ri-play-list-add-line:before { + content: "\f00f"; +} +.ri-play-list-fill:before { + content: "\f010"; +} +.ri-play-list-line:before { + content: "\f011"; +} +.ri-play-mini-fill:before { + content: "\f012"; +} +.ri-play-mini-line:before { + content: "\f013"; +} +.ri-playstation-fill:before { + content: "\f014"; +} +.ri-playstation-line:before { + content: "\f015"; +} +.ri-plug-2-fill:before { + content: "\f016"; +} +.ri-plug-2-line:before { + content: "\f017"; +} +.ri-plug-fill:before { + content: "\f018"; +} +.ri-plug-line:before { + content: "\f019"; +} +.ri-polaroid-2-fill:before { + content: "\f01a"; +} +.ri-polaroid-2-line:before { + content: "\f01b"; +} +.ri-polaroid-fill:before { + content: "\f01c"; +} +.ri-polaroid-line:before { + content: "\f01d"; +} +.ri-police-car-fill:before { + content: "\f01e"; +} +.ri-police-car-line:before { + content: "\f01f"; +} +.ri-price-tag-2-fill:before { + content: "\f020"; +} +.ri-price-tag-2-line:before { + content: "\f021"; +} +.ri-price-tag-3-fill:before { + content: "\f022"; +} +.ri-price-tag-3-line:before { + content: "\f023"; +} +.ri-price-tag-fill:before { + content: "\f024"; +} +.ri-price-tag-line:before { + content: "\f025"; +} +.ri-printer-cloud-fill:before { + content: "\f026"; +} +.ri-printer-cloud-line:before { + content: "\f027"; +} +.ri-printer-fill:before { + content: "\f028"; +} +.ri-printer-line:before { + content: "\f029"; +} +.ri-product-hunt-fill:before { + content: "\f02a"; +} +.ri-product-hunt-line:before { + content: "\f02b"; +} +.ri-profile-fill:before { + content: "\f02c"; +} +.ri-profile-line:before { + content: "\f02d"; +} +.ri-projector-2-fill:before { + content: "\f02e"; +} +.ri-projector-2-line:before { + content: "\f02f"; +} +.ri-projector-fill:before { + content: "\f030"; +} +.ri-projector-line:before { + content: "\f031"; +} +.ri-psychotherapy-fill:before { + content: "\f032"; +} +.ri-psychotherapy-line:before { + content: "\f033"; +} +.ri-pulse-fill:before { + content: "\f034"; +} +.ri-pulse-line:before { + content: "\f035"; +} +.ri-pushpin-2-fill:before { + content: "\f036"; +} +.ri-pushpin-2-line:before { + content: "\f037"; +} +.ri-pushpin-fill:before { + content: "\f038"; +} +.ri-pushpin-line:before { + content: "\f039"; +} +.ri-qq-fill:before { + content: "\f03a"; +} +.ri-qq-line:before { + content: "\f03b"; +} +.ri-qr-code-fill:before { + content: "\f03c"; +} +.ri-qr-code-line:before { + content: "\f03d"; +} +.ri-qr-scan-2-fill:before { + content: "\f03e"; +} +.ri-qr-scan-2-line:before { + content: "\f03f"; +} +.ri-qr-scan-fill:before { + content: "\f040"; +} +.ri-qr-scan-line:before { + content: "\f041"; +} +.ri-question-answer-fill:before { + content: "\f042"; +} +.ri-question-answer-line:before { + content: "\f043"; +} +.ri-question-fill:before { + content: "\f044"; +} +.ri-question-line:before { + content: "\f045"; +} +.ri-question-mark:before { + content: "\f046"; +} +.ri-questionnaire-fill:before { + content: "\f047"; +} +.ri-questionnaire-line:before { + content: "\f048"; +} +.ri-quill-pen-fill:before { + content: "\f049"; +} +.ri-quill-pen-line:before { + content: "\f04a"; +} +.ri-radar-fill:before { + content: "\f04b"; +} +.ri-radar-line:before { + content: "\f04c"; +} +.ri-radio-2-fill:before { + content: "\f04d"; +} +.ri-radio-2-line:before { + content: "\f04e"; +} +.ri-radio-button-fill:before { + content: "\f04f"; +} +.ri-radio-button-line:before { + content: "\f050"; +} +.ri-radio-fill:before { + content: "\f051"; +} +.ri-radio-line:before { + content: "\f052"; +} +.ri-rainbow-fill:before { + content: "\f053"; +} +.ri-rainbow-line:before { + content: "\f054"; +} +.ri-rainy-fill:before { + content: "\f055"; +} +.ri-rainy-line:before { + content: "\f056"; +} +.ri-reactjs-fill:before { + content: "\f057"; +} +.ri-reactjs-line:before { + content: "\f058"; +} +.ri-record-circle-fill:before { + content: "\f059"; +} +.ri-record-circle-line:before { + content: "\f05a"; +} +.ri-record-mail-fill:before { + content: "\f05b"; +} +.ri-record-mail-line:before { + content: "\f05c"; +} +.ri-recycle-fill:before { + content: "\f05d"; +} +.ri-recycle-line:before { + content: "\f05e"; +} +.ri-red-packet-fill:before { + content: "\f05f"; +} +.ri-red-packet-line:before { + content: "\f060"; +} +.ri-reddit-fill:before { + content: "\f061"; +} +.ri-reddit-line:before { + content: "\f062"; +} +.ri-refresh-fill:before { + content: "\f063"; +} +.ri-refresh-line:before { + content: "\f064"; +} +.ri-refund-2-fill:before { + content: "\f065"; +} +.ri-refund-2-line:before { + content: "\f066"; +} +.ri-refund-fill:before { + content: "\f067"; +} +.ri-refund-line:before { + content: "\f068"; +} +.ri-registered-fill:before { + content: "\f069"; +} +.ri-registered-line:before { + content: "\f06a"; +} +.ri-remixicon-fill:before { + content: "\f06b"; +} +.ri-remixicon-line:before { + content: "\f06c"; +} +.ri-remote-control-2-fill:before { + content: "\f06d"; +} +.ri-remote-control-2-line:before { + content: "\f06e"; +} +.ri-remote-control-fill:before { + content: "\f06f"; +} +.ri-remote-control-line:before { + content: "\f070"; +} +.ri-repeat-2-fill:before { + content: "\f071"; +} +.ri-repeat-2-line:before { + content: "\f072"; +} +.ri-repeat-fill:before { + content: "\f073"; +} +.ri-repeat-line:before { + content: "\f074"; +} +.ri-repeat-one-fill:before { + content: "\f075"; +} +.ri-repeat-one-line:before { + content: "\f076"; +} +.ri-reply-all-fill:before { + content: "\f077"; +} +.ri-reply-all-line:before { + content: "\f078"; +} +.ri-reply-fill:before { + content: "\f079"; +} +.ri-reply-line:before { + content: "\f07a"; +} +.ri-reserved-fill:before { + content: "\f07b"; +} +.ri-reserved-line:before { + content: "\f07c"; +} +.ri-rest-time-fill:before { + content: "\f07d"; +} +.ri-rest-time-line:before { + content: "\f07e"; +} +.ri-restart-fill:before { + content: "\f07f"; +} +.ri-restart-line:before { + content: "\f080"; +} +.ri-restaurant-2-fill:before { + content: "\f081"; +} +.ri-restaurant-2-line:before { + content: "\f082"; +} +.ri-restaurant-fill:before { + content: "\f083"; +} +.ri-restaurant-line:before { + content: "\f084"; +} +.ri-rewind-fill:before { + content: "\f085"; +} +.ri-rewind-line:before { + content: "\f086"; +} +.ri-rewind-mini-fill:before { + content: "\f087"; +} +.ri-rewind-mini-line:before { + content: "\f088"; +} +.ri-rhythm-fill:before { + content: "\f089"; +} +.ri-rhythm-line:before { + content: "\f08a"; +} +.ri-riding-fill:before { + content: "\f08b"; +} +.ri-riding-line:before { + content: "\f08c"; +} +.ri-road-map-fill:before { + content: "\f08d"; +} +.ri-road-map-line:before { + content: "\f08e"; +} +.ri-roadster-fill:before { + content: "\f08f"; +} +.ri-roadster-line:before { + content: "\f090"; +} +.ri-robot-fill:before { + content: "\f091"; +} +.ri-robot-line:before { + content: "\f092"; +} +.ri-rocket-2-fill:before { + content: "\f093"; +} +.ri-rocket-2-line:before { + content: "\f094"; +} +.ri-rocket-fill:before { + content: "\f095"; +} +.ri-rocket-line:before { + content: "\f096"; +} +.ri-rotate-lock-fill:before { + content: "\f097"; +} +.ri-rotate-lock-line:before { + content: "\f098"; +} +.ri-rounded-corner:before { + content: "\f099"; +} +.ri-route-fill:before { + content: "\f09a"; +} +.ri-route-line:before { + content: "\f09b"; +} +.ri-router-fill:before { + content: "\f09c"; +} +.ri-router-line:before { + content: "\f09d"; +} +.ri-rss-fill:before { + content: "\f09e"; +} +.ri-rss-line:before { + content: "\f09f"; +} +.ri-ruler-2-fill:before { + content: "\f0a0"; +} +.ri-ruler-2-line:before { + content: "\f0a1"; +} +.ri-ruler-fill:before { + content: "\f0a2"; +} +.ri-ruler-line:before { + content: "\f0a3"; +} +.ri-run-fill:before { + content: "\f0a4"; +} +.ri-run-line:before { + content: "\f0a5"; +} +.ri-safari-fill:before { + content: "\f0a6"; +} +.ri-safari-line:before { + content: "\f0a7"; +} +.ri-safe-2-fill:before { + content: "\f0a8"; +} +.ri-safe-2-line:before { + content: "\f0a9"; +} +.ri-safe-fill:before { + content: "\f0aa"; +} +.ri-safe-line:before { + content: "\f0ab"; +} +.ri-sailboat-fill:before { + content: "\f0ac"; +} +.ri-sailboat-line:before { + content: "\f0ad"; +} +.ri-save-2-fill:before { + content: "\f0ae"; +} +.ri-save-2-line:before { + content: "\f0af"; +} +.ri-save-3-fill:before { + content: "\f0b0"; +} +.ri-save-3-line:before { + content: "\f0b1"; +} +.ri-save-fill:before { + content: "\f0b2"; +} +.ri-save-line:before { + content: "\f0b3"; +} +.ri-scales-2-fill:before { + content: "\f0b4"; +} +.ri-scales-2-line:before { + content: "\f0b5"; +} +.ri-scales-3-fill:before { + content: "\f0b6"; +} +.ri-scales-3-line:before { + content: "\f0b7"; +} +.ri-scales-fill:before { + content: "\f0b8"; +} +.ri-scales-line:before { + content: "\f0b9"; +} +.ri-scan-2-fill:before { + content: "\f0ba"; +} +.ri-scan-2-line:before { + content: "\f0bb"; +} +.ri-scan-fill:before { + content: "\f0bc"; +} +.ri-scan-line:before { + content: "\f0bd"; +} +.ri-scissors-2-fill:before { + content: "\f0be"; +} +.ri-scissors-2-line:before { + content: "\f0bf"; +} +.ri-scissors-cut-fill:before { + content: "\f0c0"; +} +.ri-scissors-cut-line:before { + content: "\f0c1"; +} +.ri-scissors-fill:before { + content: "\f0c2"; +} +.ri-scissors-line:before { + content: "\f0c3"; +} +.ri-screenshot-2-fill:before { + content: "\f0c4"; +} +.ri-screenshot-2-line:before { + content: "\f0c5"; +} +.ri-screenshot-fill:before { + content: "\f0c6"; +} +.ri-screenshot-line:before { + content: "\f0c7"; +} +.ri-sd-card-fill:before { + content: "\f0c8"; +} +.ri-sd-card-line:before { + content: "\f0c9"; +} +.ri-sd-card-mini-fill:before { + content: "\f0ca"; +} +.ri-sd-card-mini-line:before { + content: "\f0cb"; +} +.ri-search-2-fill:before { + content: "\f0cc"; +} +.ri-search-2-line:before { + content: "\f0cd"; +} +.ri-search-eye-fill:before { + content: "\f0ce"; +} +.ri-search-eye-line:before { + content: "\f0cf"; +} +.ri-search-fill:before { + content: "\f0d0"; +} +.ri-search-line:before { + content: "\f0d1"; +} +.ri-secure-payment-fill:before { + content: "\f0d2"; +} +.ri-secure-payment-line:before { + content: "\f0d3"; +} +.ri-seedling-fill:before { + content: "\f0d4"; +} +.ri-seedling-line:before { + content: "\f0d5"; +} +.ri-send-backward:before { + content: "\f0d6"; +} +.ri-send-plane-2-fill:before { + content: "\f0d7"; +} +.ri-send-plane-2-line:before { + content: "\f0d8"; +} +.ri-send-plane-fill:before { + content: "\f0d9"; +} +.ri-send-plane-line:before { + content: "\f0da"; +} +.ri-send-to-back:before { + content: "\f0db"; +} +.ri-sensor-fill:before { + content: "\f0dc"; +} +.ri-sensor-line:before { + content: "\f0dd"; +} +.ri-separator:before { + content: "\f0de"; +} +.ri-server-fill:before { + content: "\f0df"; +} +.ri-server-line:before { + content: "\f0e0"; +} +.ri-service-fill:before { + content: "\f0e1"; +} +.ri-service-line:before { + content: "\f0e2"; +} +.ri-settings-2-fill:before { + content: "\f0e3"; +} +.ri-settings-2-line:before { + content: "\f0e4"; +} +.ri-settings-3-fill:before { + content: "\f0e5"; +} +.ri-settings-3-line:before { + content: "\f0e6"; +} +.ri-settings-4-fill:before { + content: "\f0e7"; +} +.ri-settings-4-line:before { + content: "\f0e8"; +} +.ri-settings-5-fill:before { + content: "\f0e9"; +} +.ri-settings-5-line:before { + content: "\f0ea"; +} +.ri-settings-6-fill:before { + content: "\f0eb"; +} +.ri-settings-6-line:before { + content: "\f0ec"; +} +.ri-settings-fill:before { + content: "\f0ed"; +} +.ri-settings-line:before { + content: "\f0ee"; +} +.ri-shape-2-fill:before { + content: "\f0ef"; +} +.ri-shape-2-line:before { + content: "\f0f0"; +} +.ri-shape-fill:before { + content: "\f0f1"; +} +.ri-shape-line:before { + content: "\f0f2"; +} +.ri-share-box-fill:before { + content: "\f0f3"; +} +.ri-share-box-line:before { + content: "\f0f4"; +} +.ri-share-circle-fill:before { + content: "\f0f5"; +} +.ri-share-circle-line:before { + content: "\f0f6"; +} +.ri-share-fill:before { + content: "\f0f7"; +} +.ri-share-forward-2-fill:before { + content: "\f0f8"; +} +.ri-share-forward-2-line:before { + content: "\f0f9"; +} +.ri-share-forward-box-fill:before { + content: "\f0fa"; +} +.ri-share-forward-box-line:before { + content: "\f0fb"; +} +.ri-share-forward-fill:before { + content: "\f0fc"; +} +.ri-share-forward-line:before { + content: "\f0fd"; +} +.ri-share-line:before { + content: "\f0fe"; +} +.ri-shield-check-fill:before { + content: "\f0ff"; +} +.ri-shield-check-line:before { + content: "\f100"; +} +.ri-shield-cross-fill:before { + content: "\f101"; +} +.ri-shield-cross-line:before { + content: "\f102"; +} +.ri-shield-fill:before { + content: "\f103"; +} +.ri-shield-flash-fill:before { + content: "\f104"; +} +.ri-shield-flash-line:before { + content: "\f105"; +} +.ri-shield-keyhole-fill:before { + content: "\f106"; +} +.ri-shield-keyhole-line:before { + content: "\f107"; +} +.ri-shield-line:before { + content: "\f108"; +} +.ri-shield-star-fill:before { + content: "\f109"; +} +.ri-shield-star-line:before { + content: "\f10a"; +} +.ri-shield-user-fill:before { + content: "\f10b"; +} +.ri-shield-user-line:before { + content: "\f10c"; +} +.ri-ship-2-fill:before { + content: "\f10d"; +} +.ri-ship-2-line:before { + content: "\f10e"; +} +.ri-ship-fill:before { + content: "\f10f"; +} +.ri-ship-line:before { + content: "\f110"; +} +.ri-shirt-fill:before { + content: "\f111"; +} +.ri-shirt-line:before { + content: "\f112"; +} +.ri-shopping-bag-2-fill:before { + content: "\f113"; +} +.ri-shopping-bag-2-line:before { + content: "\f114"; +} +.ri-shopping-bag-3-fill:before { + content: "\f115"; +} +.ri-shopping-bag-3-line:before { + content: "\f116"; +} +.ri-shopping-bag-fill:before { + content: "\f117"; +} +.ri-shopping-bag-line:before { + content: "\f118"; +} +.ri-shopping-basket-2-fill:before { + content: "\f119"; +} +.ri-shopping-basket-2-line:before { + content: "\f11a"; +} +.ri-shopping-basket-fill:before { + content: "\f11b"; +} +.ri-shopping-basket-line:before { + content: "\f11c"; +} +.ri-shopping-cart-2-fill:before { + content: "\f11d"; +} +.ri-shopping-cart-2-line:before { + content: "\f11e"; +} +.ri-shopping-cart-fill:before { + content: "\f11f"; +} +.ri-shopping-cart-line:before { + content: "\f120"; +} +.ri-showers-fill:before { + content: "\f121"; +} +.ri-showers-line:before { + content: "\f122"; +} +.ri-shuffle-fill:before { + content: "\f123"; +} +.ri-shuffle-line:before { + content: "\f124"; +} +.ri-shut-down-fill:before { + content: "\f125"; +} +.ri-shut-down-line:before { + content: "\f126"; +} +.ri-side-bar-fill:before { + content: "\f127"; +} +.ri-side-bar-line:before { + content: "\f128"; +} +.ri-signal-tower-fill:before { + content: "\f129"; +} +.ri-signal-tower-line:before { + content: "\f12a"; +} +.ri-signal-wifi-1-fill:before { + content: "\f12b"; +} +.ri-signal-wifi-1-line:before { + content: "\f12c"; +} +.ri-signal-wifi-2-fill:before { + content: "\f12d"; +} +.ri-signal-wifi-2-line:before { + content: "\f12e"; +} +.ri-signal-wifi-3-fill:before { + content: "\f12f"; +} +.ri-signal-wifi-3-line:before { + content: "\f130"; +} +.ri-signal-wifi-error-fill:before { + content: "\f131"; +} +.ri-signal-wifi-error-line:before { + content: "\f132"; +} +.ri-signal-wifi-fill:before { + content: "\f133"; +} +.ri-signal-wifi-line:before { + content: "\f134"; +} +.ri-signal-wifi-off-fill:before { + content: "\f135"; +} +.ri-signal-wifi-off-line:before { + content: "\f136"; +} +.ri-sim-card-2-fill:before { + content: "\f137"; +} +.ri-sim-card-2-line:before { + content: "\f138"; +} +.ri-sim-card-fill:before { + content: "\f139"; +} +.ri-sim-card-line:before { + content: "\f13a"; +} +.ri-single-quotes-l:before { + content: "\f13b"; +} +.ri-single-quotes-r:before { + content: "\f13c"; +} +.ri-sip-fill:before { + content: "\f13d"; +} +.ri-sip-line:before { + content: "\f13e"; +} +.ri-skip-back-fill:before { + content: "\f13f"; +} +.ri-skip-back-line:before { + content: "\f140"; +} +.ri-skip-back-mini-fill:before { + content: "\f141"; +} +.ri-skip-back-mini-line:before { + content: "\f142"; +} +.ri-skip-forward-fill:before { + content: "\f143"; +} +.ri-skip-forward-line:before { + content: "\f144"; +} +.ri-skip-forward-mini-fill:before { + content: "\f145"; +} +.ri-skip-forward-mini-line:before { + content: "\f146"; +} +.ri-skull-2-fill:before { + content: "\f147"; +} +.ri-skull-2-line:before { + content: "\f148"; +} +.ri-skull-fill:before { + content: "\f149"; +} +.ri-skull-line:before { + content: "\f14a"; +} +.ri-skype-fill:before { + content: "\f14b"; +} +.ri-skype-line:before { + content: "\f14c"; +} +.ri-slack-fill:before { + content: "\f14d"; +} +.ri-slack-line:before { + content: "\f14e"; +} +.ri-slice-fill:before { + content: "\f14f"; +} +.ri-slice-line:before { + content: "\f150"; +} +.ri-slideshow-2-fill:before { + content: "\f151"; +} +.ri-slideshow-2-line:before { + content: "\f152"; +} +.ri-slideshow-3-fill:before { + content: "\f153"; +} +.ri-slideshow-3-line:before { + content: "\f154"; +} +.ri-slideshow-4-fill:before { + content: "\f155"; +} +.ri-slideshow-4-line:before { + content: "\f156"; +} +.ri-slideshow-fill:before { + content: "\f157"; +} +.ri-slideshow-line:before { + content: "\f158"; +} +.ri-smartphone-fill:before { + content: "\f159"; +} +.ri-smartphone-line:before { + content: "\f15a"; +} +.ri-snapchat-fill:before { + content: "\f15b"; +} +.ri-snapchat-line:before { + content: "\f15c"; +} +.ri-snowy-fill:before { + content: "\f15d"; +} +.ri-snowy-line:before { + content: "\f15e"; +} +.ri-sort-asc:before { + content: "\f15f"; +} +.ri-sort-desc:before { + content: "\f160"; +} +.ri-sound-module-fill:before { + content: "\f161"; +} +.ri-sound-module-line:before { + content: "\f162"; +} +.ri-soundcloud-fill:before { + content: "\f163"; +} +.ri-soundcloud-line:before { + content: "\f164"; +} +.ri-space-ship-fill:before { + content: "\f165"; +} +.ri-space-ship-line:before { + content: "\f166"; +} +.ri-space:before { + content: "\f167"; +} +.ri-spam-2-fill:before { + content: "\f168"; +} +.ri-spam-2-line:before { + content: "\f169"; +} +.ri-spam-3-fill:before { + content: "\f16a"; +} +.ri-spam-3-line:before { + content: "\f16b"; +} +.ri-spam-fill:before { + content: "\f16c"; +} +.ri-spam-line:before { + content: "\f16d"; +} +.ri-speaker-2-fill:before { + content: "\f16e"; +} +.ri-speaker-2-line:before { + content: "\f16f"; +} +.ri-speaker-3-fill:before { + content: "\f170"; +} +.ri-speaker-3-line:before { + content: "\f171"; +} +.ri-speaker-fill:before { + content: "\f172"; +} +.ri-speaker-line:before { + content: "\f173"; +} +.ri-spectrum-fill:before { + content: "\f174"; +} +.ri-spectrum-line:before { + content: "\f175"; +} +.ri-speed-fill:before { + content: "\f176"; +} +.ri-speed-line:before { + content: "\f177"; +} +.ri-speed-mini-fill:before { + content: "\f178"; +} +.ri-speed-mini-line:before { + content: "\f179"; +} +.ri-split-cells-horizontal:before { + content: "\f17a"; +} +.ri-split-cells-vertical:before { + content: "\f17b"; +} +.ri-spotify-fill:before { + content: "\f17c"; +} +.ri-spotify-line:before { + content: "\f17d"; +} +.ri-spy-fill:before { + content: "\f17e"; +} +.ri-spy-line:before { + content: "\f17f"; +} +.ri-stack-fill:before { + content: "\f180"; +} +.ri-stack-line:before { + content: "\f181"; +} +.ri-stack-overflow-fill:before { + content: "\f182"; +} +.ri-stack-overflow-line:before { + content: "\f183"; +} +.ri-stackshare-fill:before { + content: "\f184"; +} +.ri-stackshare-line:before { + content: "\f185"; +} +.ri-star-fill:before { + content: "\f186"; +} +.ri-star-half-fill:before { + content: "\f187"; +} +.ri-star-half-line:before { + content: "\f188"; +} +.ri-star-half-s-fill:before { + content: "\f189"; +} +.ri-star-half-s-line:before { + content: "\f18a"; +} +.ri-star-line:before { + content: "\f18b"; +} +.ri-star-s-fill:before { + content: "\f18c"; +} +.ri-star-s-line:before { + content: "\f18d"; +} +.ri-star-smile-fill:before { + content: "\f18e"; +} +.ri-star-smile-line:before { + content: "\f18f"; +} +.ri-steam-fill:before { + content: "\f190"; +} +.ri-steam-line:before { + content: "\f191"; +} +.ri-steering-2-fill:before { + content: "\f192"; +} +.ri-steering-2-line:before { + content: "\f193"; +} +.ri-steering-fill:before { + content: "\f194"; +} +.ri-steering-line:before { + content: "\f195"; +} +.ri-stethoscope-fill:before { + content: "\f196"; +} +.ri-stethoscope-line:before { + content: "\f197"; +} +.ri-sticky-note-2-fill:before { + content: "\f198"; +} +.ri-sticky-note-2-line:before { + content: "\f199"; +} +.ri-sticky-note-fill:before { + content: "\f19a"; +} +.ri-sticky-note-line:before { + content: "\f19b"; +} +.ri-stock-fill:before { + content: "\f19c"; +} +.ri-stock-line:before { + content: "\f19d"; +} +.ri-stop-circle-fill:before { + content: "\f19e"; +} +.ri-stop-circle-line:before { + content: "\f19f"; +} +.ri-stop-fill:before { + content: "\f1a0"; +} +.ri-stop-line:before { + content: "\f1a1"; +} +.ri-stop-mini-fill:before { + content: "\f1a2"; +} +.ri-stop-mini-line:before { + content: "\f1a3"; +} +.ri-store-2-fill:before { + content: "\f1a4"; +} +.ri-store-2-line:before { + content: "\f1a5"; +} +.ri-store-3-fill:before { + content: "\f1a6"; +} +.ri-store-3-line:before { + content: "\f1a7"; +} +.ri-store-fill:before { + content: "\f1a8"; +} +.ri-store-line:before { + content: "\f1a9"; +} +.ri-strikethrough-2:before { + content: "\f1aa"; +} +.ri-strikethrough:before { + content: "\f1ab"; +} +.ri-subscript-2:before { + content: "\f1ac"; +} +.ri-subscript:before { + content: "\f1ad"; +} +.ri-subtract-fill:before { + content: "\f1ae"; +} +.ri-subtract-line:before { + content: "\f1af"; +} +.ri-subway-fill:before { + content: "\f1b0"; +} +.ri-subway-line:before { + content: "\f1b1"; +} +.ri-subway-wifi-fill:before { + content: "\f1b2"; +} +.ri-subway-wifi-line:before { + content: "\f1b3"; +} +.ri-suitcase-2-fill:before { + content: "\f1b4"; +} +.ri-suitcase-2-line:before { + content: "\f1b5"; +} +.ri-suitcase-3-fill:before { + content: "\f1b6"; +} +.ri-suitcase-3-line:before { + content: "\f1b7"; +} +.ri-suitcase-fill:before { + content: "\f1b8"; +} +.ri-suitcase-line:before { + content: "\f1b9"; +} +.ri-sun-cloudy-fill:before { + content: "\f1ba"; +} +.ri-sun-cloudy-line:before { + content: "\f1bb"; +} +.ri-sun-fill:before { + content: "\f1bc"; +} +.ri-sun-foggy-fill:before { + content: "\f1bd"; +} +.ri-sun-foggy-line:before { + content: "\f1be"; +} +.ri-sun-line:before { + content: "\f1bf"; +} +.ri-superscript-2:before { + content: "\f1c0"; +} +.ri-superscript:before { + content: "\f1c1"; +} +.ri-surgical-mask-fill:before { + content: "\f1c2"; +} +.ri-surgical-mask-line:before { + content: "\f1c3"; +} +.ri-surround-sound-fill:before { + content: "\f1c4"; +} +.ri-surround-sound-line:before { + content: "\f1c5"; +} +.ri-survey-fill:before { + content: "\f1c6"; +} +.ri-survey-line:before { + content: "\f1c7"; +} +.ri-swap-box-fill:before { + content: "\f1c8"; +} +.ri-swap-box-line:before { + content: "\f1c9"; +} +.ri-swap-fill:before { + content: "\f1ca"; +} +.ri-swap-line:before { + content: "\f1cb"; +} +.ri-switch-fill:before { + content: "\f1cc"; +} +.ri-switch-line:before { + content: "\f1cd"; +} +.ri-sword-fill:before { + content: "\f1ce"; +} +.ri-sword-line:before { + content: "\f1cf"; +} +.ri-syringe-fill:before { + content: "\f1d0"; +} +.ri-syringe-line:before { + content: "\f1d1"; +} +.ri-t-box-fill:before { + content: "\f1d2"; +} +.ri-t-box-line:before { + content: "\f1d3"; +} +.ri-t-shirt-2-fill:before { + content: "\f1d4"; +} +.ri-t-shirt-2-line:before { + content: "\f1d5"; +} +.ri-t-shirt-air-fill:before { + content: "\f1d6"; +} +.ri-t-shirt-air-line:before { + content: "\f1d7"; +} +.ri-t-shirt-fill:before { + content: "\f1d8"; +} +.ri-t-shirt-line:before { + content: "\f1d9"; +} +.ri-table-2:before { + content: "\f1da"; +} +.ri-table-alt-fill:before { + content: "\f1db"; +} +.ri-table-alt-line:before { + content: "\f1dc"; +} +.ri-table-fill:before { + content: "\f1dd"; +} +.ri-table-line:before { + content: "\f1de"; +} +.ri-tablet-fill:before { + content: "\f1df"; +} +.ri-tablet-line:before { + content: "\f1e0"; +} +.ri-takeaway-fill:before { + content: "\f1e1"; +} +.ri-takeaway-line:before { + content: "\f1e2"; +} +.ri-taobao-fill:before { + content: "\f1e3"; +} +.ri-taobao-line:before { + content: "\f1e4"; +} +.ri-tape-fill:before { + content: "\f1e5"; +} +.ri-tape-line:before { + content: "\f1e6"; +} +.ri-task-fill:before { + content: "\f1e7"; +} +.ri-task-line:before { + content: "\f1e8"; +} +.ri-taxi-fill:before { + content: "\f1e9"; +} +.ri-taxi-line:before { + content: "\f1ea"; +} +.ri-taxi-wifi-fill:before { + content: "\f1eb"; +} +.ri-taxi-wifi-line:before { + content: "\f1ec"; +} +.ri-team-fill:before { + content: "\f1ed"; +} +.ri-team-line:before { + content: "\f1ee"; +} +.ri-telegram-fill:before { + content: "\f1ef"; +} +.ri-telegram-line:before { + content: "\f1f0"; +} +.ri-temp-cold-fill:before { + content: "\f1f1"; +} +.ri-temp-cold-line:before { + content: "\f1f2"; +} +.ri-temp-hot-fill:before { + content: "\f1f3"; +} +.ri-temp-hot-line:before { + content: "\f1f4"; +} +.ri-terminal-box-fill:before { + content: "\f1f5"; +} +.ri-terminal-box-line:before { + content: "\f1f6"; +} +.ri-terminal-fill:before { + content: "\f1f7"; +} +.ri-terminal-line:before { + content: "\f1f8"; +} +.ri-terminal-window-fill:before { + content: "\f1f9"; +} +.ri-terminal-window-line:before { + content: "\f1fa"; +} +.ri-test-tube-fill:before { + content: "\f1fb"; +} +.ri-test-tube-line:before { + content: "\f1fc"; +} +.ri-text-direction-l:before { + content: "\f1fd"; +} +.ri-text-direction-r:before { + content: "\f1fe"; +} +.ri-text-spacing:before { + content: "\f1ff"; +} +.ri-text-wrap:before { + content: "\f200"; +} +.ri-text:before { + content: "\f201"; +} +.ri-thermometer-fill:before { + content: "\f202"; +} +.ri-thermometer-line:before { + content: "\f203"; +} +.ri-thumb-down-fill:before { + content: "\f204"; +} +.ri-thumb-down-line:before { + content: "\f205"; +} +.ri-thumb-up-fill:before { + content: "\f206"; +} +.ri-thumb-up-line:before { + content: "\f207"; +} +.ri-thunderstorms-fill:before { + content: "\f208"; +} +.ri-thunderstorms-line:before { + content: "\f209"; +} +.ri-ticket-2-fill:before { + content: "\f20a"; +} +.ri-ticket-2-line:before { + content: "\f20b"; +} +.ri-ticket-fill:before { + content: "\f20c"; +} +.ri-ticket-line:before { + content: "\f20d"; +} +.ri-time-fill:before { + content: "\f20e"; +} +.ri-time-line:before { + content: "\f20f"; +} +.ri-timer-2-fill:before { + content: "\f210"; +} +.ri-timer-2-line:before { + content: "\f211"; +} +.ri-timer-fill:before { + content: "\f212"; +} +.ri-timer-flash-fill:before { + content: "\f213"; +} +.ri-timer-flash-line:before { + content: "\f214"; +} +.ri-timer-line:before { + content: "\f215"; +} +.ri-todo-fill:before { + content: "\f216"; +} +.ri-todo-line:before { + content: "\f217"; +} +.ri-toggle-fill:before { + content: "\f218"; +} +.ri-toggle-line:before { + content: "\f219"; +} +.ri-tools-fill:before { + content: "\f21a"; +} +.ri-tools-line:before { + content: "\f21b"; +} +.ri-tornado-fill:before { + content: "\f21c"; +} +.ri-tornado-line:before { + content: "\f21d"; +} +.ri-trademark-fill:before { + content: "\f21e"; +} +.ri-trademark-line:before { + content: "\f21f"; +} +.ri-traffic-light-fill:before { + content: "\f220"; +} +.ri-traffic-light-line:before { + content: "\f221"; +} +.ri-train-fill:before { + content: "\f222"; +} +.ri-train-line:before { + content: "\f223"; +} +.ri-train-wifi-fill:before { + content: "\f224"; +} +.ri-train-wifi-line:before { + content: "\f225"; +} +.ri-translate-2:before { + content: "\f226"; +} +.ri-translate:before { + content: "\f227"; +} +.ri-travesti-fill:before { + content: "\f228"; +} +.ri-travesti-line:before { + content: "\f229"; +} +.ri-treasure-map-fill:before { + content: "\f22a"; +} +.ri-treasure-map-line:before { + content: "\f22b"; +} +.ri-trello-fill:before { + content: "\f22c"; +} +.ri-trello-line:before { + content: "\f22d"; +} +.ri-trophy-fill:before { + content: "\f22e"; +} +.ri-trophy-line:before { + content: "\f22f"; +} +.ri-truck-fill:before { + content: "\f230"; +} +.ri-truck-line:before { + content: "\f231"; +} +.ri-tumblr-fill:before { + content: "\f232"; +} +.ri-tumblr-line:before { + content: "\f233"; +} +.ri-tv-2-fill:before { + content: "\f234"; +} +.ri-tv-2-line:before { + content: "\f235"; +} +.ri-tv-fill:before { + content: "\f236"; +} +.ri-tv-line:before { + content: "\f237"; +} +.ri-twitch-fill:before { + content: "\f238"; +} +.ri-twitch-line:before { + content: "\f239"; +} +.ri-twitter-fill:before { + content: "\f23a"; +} +.ri-twitter-line:before { + content: "\f23b"; +} +.ri-typhoon-fill:before { + content: "\f23c"; +} +.ri-typhoon-line:before { + content: "\f23d"; +} +.ri-u-disk-fill:before { + content: "\f23e"; +} +.ri-u-disk-line:before { + content: "\f23f"; +} +.ri-ubuntu-fill:before { + content: "\f240"; +} +.ri-ubuntu-line:before { + content: "\f241"; +} +.ri-umbrella-fill:before { + content: "\f242"; +} +.ri-umbrella-line:before { + content: "\f243"; +} +.ri-underline:before { + content: "\f244"; +} +.ri-uninstall-fill:before { + content: "\f245"; +} +.ri-uninstall-line:before { + content: "\f246"; +} +.ri-unsplash-fill:before { + content: "\f247"; +} +.ri-unsplash-line:before { + content: "\f248"; +} +.ri-upload-2-fill:before { + content: "\f249"; +} +.ri-upload-2-line:before { + content: "\f24a"; +} +.ri-upload-cloud-2-fill:before { + content: "\f24b"; +} +.ri-upload-cloud-2-line:before { + content: "\f24c"; +} +.ri-upload-cloud-fill:before { + content: "\f24d"; +} +.ri-upload-cloud-line:before { + content: "\f24e"; +} +.ri-upload-fill:before { + content: "\f24f"; +} +.ri-upload-line:before { + content: "\f250"; +} +.ri-usb-fill:before { + content: "\f251"; +} +.ri-usb-line:before { + content: "\f252"; +} +.ri-user-2-fill:before { + content: "\f253"; +} +.ri-user-2-line:before { + content: "\f254"; +} +.ri-user-3-fill:before { + content: "\f255"; +} +.ri-user-3-line:before { + content: "\f256"; +} +.ri-user-4-fill:before { + content: "\f257"; +} +.ri-user-4-line:before { + content: "\f258"; +} +.ri-user-5-fill:before { + content: "\f259"; +} +.ri-user-5-line:before { + content: "\f25a"; +} +.ri-user-6-fill:before { + content: "\f25b"; +} +.ri-user-6-line:before { + content: "\f25c"; +} +.ri-user-add-fill:before { + content: "\f25d"; +} +.ri-user-add-line:before { + content: "\f25e"; +} +.ri-user-fill:before { + content: "\f25f"; +} +.ri-user-follow-fill:before { + content: "\f260"; +} +.ri-user-follow-line:before { + content: "\f261"; +} +.ri-user-heart-fill:before { + content: "\f262"; +} +.ri-user-heart-line:before { + content: "\f263"; +} +.ri-user-line:before { + content: "\f264"; +} +.ri-user-location-fill:before { + content: "\f265"; +} +.ri-user-location-line:before { + content: "\f266"; +} +.ri-user-received-2-fill:before { + content: "\f267"; +} +.ri-user-received-2-line:before { + content: "\f268"; +} +.ri-user-received-fill:before { + content: "\f269"; +} +.ri-user-received-line:before { + content: "\f26a"; +} +.ri-user-search-fill:before { + content: "\f26b"; +} +.ri-user-search-line:before { + content: "\f26c"; +} +.ri-user-settings-fill:before { + content: "\f26d"; +} +.ri-user-settings-line:before { + content: "\f26e"; +} +.ri-user-shared-2-fill:before { + content: "\f26f"; +} +.ri-user-shared-2-line:before { + content: "\f270"; +} +.ri-user-shared-fill:before { + content: "\f271"; +} +.ri-user-shared-line:before { + content: "\f272"; +} +.ri-user-smile-fill:before { + content: "\f273"; +} +.ri-user-smile-line:before { + content: "\f274"; +} +.ri-user-star-fill:before { + content: "\f275"; +} +.ri-user-star-line:before { + content: "\f276"; +} +.ri-user-unfollow-fill:before { + content: "\f277"; +} +.ri-user-unfollow-line:before { + content: "\f278"; +} +.ri-user-voice-fill:before { + content: "\f279"; +} +.ri-user-voice-line:before { + content: "\f27a"; +} +.ri-video-add-fill:before { + content: "\f27b"; +} +.ri-video-add-line:before { + content: "\f27c"; +} +.ri-video-chat-fill:before { + content: "\f27d"; +} +.ri-video-chat-line:before { + content: "\f27e"; +} +.ri-video-download-fill:before { + content: "\f27f"; +} +.ri-video-download-line:before { + content: "\f280"; +} +.ri-video-fill:before { + content: "\f281"; +} +.ri-video-line:before { + content: "\f282"; +} +.ri-video-upload-fill:before { + content: "\f283"; +} +.ri-video-upload-line:before { + content: "\f284"; +} +.ri-vidicon-2-fill:before { + content: "\f285"; +} +.ri-vidicon-2-line:before { + content: "\f286"; +} +.ri-vidicon-fill:before { + content: "\f287"; +} +.ri-vidicon-line:before { + content: "\f288"; +} +.ri-vimeo-fill:before { + content: "\f289"; +} +.ri-vimeo-line:before { + content: "\f28a"; +} +.ri-vip-crown-2-fill:before { + content: "\f28b"; +} +.ri-vip-crown-2-line:before { + content: "\f28c"; +} +.ri-vip-crown-fill:before { + content: "\f28d"; +} +.ri-vip-crown-line:before { + content: "\f28e"; +} +.ri-vip-diamond-fill:before { + content: "\f28f"; +} +.ri-vip-diamond-line:before { + content: "\f290"; +} +.ri-vip-fill:before { + content: "\f291"; +} +.ri-vip-line:before { + content: "\f292"; +} +.ri-virus-fill:before { + content: "\f293"; +} +.ri-virus-line:before { + content: "\f294"; +} +.ri-visa-fill:before { + content: "\f295"; +} +.ri-visa-line:before { + content: "\f296"; +} +.ri-voice-recognition-fill:before { + content: "\f297"; +} +.ri-voice-recognition-line:before { + content: "\f298"; +} +.ri-voiceprint-fill:before { + content: "\f299"; +} +.ri-voiceprint-line:before { + content: "\f29a"; +} +.ri-volume-down-fill:before { + content: "\f29b"; +} +.ri-volume-down-line:before { + content: "\f29c"; +} +.ri-volume-mute-fill:before { + content: "\f29d"; +} +.ri-volume-mute-line:before { + content: "\f29e"; +} +.ri-volume-off-vibrate-fill:before { + content: "\f29f"; +} +.ri-volume-off-vibrate-line:before { + content: "\f2a0"; +} +.ri-volume-up-fill:before { + content: "\f2a1"; +} +.ri-volume-up-line:before { + content: "\f2a2"; +} +.ri-volume-vibrate-fill:before { + content: "\f2a3"; +} +.ri-volume-vibrate-line:before { + content: "\f2a4"; +} +.ri-vuejs-fill:before { + content: "\f2a5"; +} +.ri-vuejs-line:before { + content: "\f2a6"; +} +.ri-walk-fill:before { + content: "\f2a7"; +} +.ri-walk-line:before { + content: "\f2a8"; +} +.ri-wallet-2-fill:before { + content: "\f2a9"; +} +.ri-wallet-2-line:before { + content: "\f2aa"; +} +.ri-wallet-3-fill:before { + content: "\f2ab"; +} +.ri-wallet-3-line:before { + content: "\f2ac"; +} +.ri-wallet-fill:before { + content: "\f2ad"; +} +.ri-wallet-line:before { + content: "\f2ae"; +} +.ri-water-flash-fill:before { + content: "\f2af"; +} +.ri-water-flash-line:before { + content: "\f2b0"; +} +.ri-webcam-fill:before { + content: "\f2b1"; +} +.ri-webcam-line:before { + content: "\f2b2"; +} +.ri-wechat-2-fill:before { + content: "\f2b3"; +} +.ri-wechat-2-line:before { + content: "\f2b4"; +} +.ri-wechat-fill:before { + content: "\f2b5"; +} +.ri-wechat-line:before { + content: "\f2b6"; +} +.ri-wechat-pay-fill:before { + content: "\f2b7"; +} +.ri-wechat-pay-line:before { + content: "\f2b8"; +} +.ri-weibo-fill:before { + content: "\f2b9"; +} +.ri-weibo-line:before { + content: "\f2ba"; +} +.ri-whatsapp-fill:before { + content: "\f2bb"; +} +.ri-whatsapp-line:before { + content: "\f2bc"; +} +.ri-wheelchair-fill:before { + content: "\f2bd"; +} +.ri-wheelchair-line:before { + content: "\f2be"; +} +.ri-wifi-fill:before { + content: "\f2bf"; +} +.ri-wifi-line:before { + content: "\f2c0"; +} +.ri-wifi-off-fill:before { + content: "\f2c1"; +} +.ri-wifi-off-line:before { + content: "\f2c2"; +} +.ri-window-2-fill:before { + content: "\f2c3"; +} +.ri-window-2-line:before { + content: "\f2c4"; +} +.ri-window-fill:before { + content: "\f2c5"; +} +.ri-window-line:before { + content: "\f2c6"; +} +.ri-windows-fill:before { + content: "\f2c7"; +} +.ri-windows-line:before { + content: "\f2c8"; +} +.ri-windy-fill:before { + content: "\f2c9"; +} +.ri-windy-line:before { + content: "\f2ca"; +} +.ri-wireless-charging-fill:before { + content: "\f2cb"; +} +.ri-wireless-charging-line:before { + content: "\f2cc"; +} +.ri-women-fill:before { + content: "\f2cd"; +} +.ri-women-line:before { + content: "\f2ce"; +} +.ri-wubi-input:before { + content: "\f2cf"; +} +.ri-xbox-fill:before { + content: "\f2d0"; +} +.ri-xbox-line:before { + content: "\f2d1"; +} +.ri-xing-fill:before { + content: "\f2d2"; +} +.ri-xing-line:before { + content: "\f2d3"; +} +.ri-youtube-fill:before { + content: "\f2d4"; +} +.ri-youtube-line:before { + content: "\f2d5"; +} +.ri-zcool-fill:before { + content: "\f2d6"; +} +.ri-zcool-line:before { + content: "\f2d7"; +} +.ri-zhihu-fill:before { + content: "\f2d8"; +} +.ri-zhihu-line:before { + content: "\f2d9"; +} +.ri-zoom-in-fill:before { + content: "\f2da"; +} +.ri-zoom-in-line:before { + content: "\f2db"; +} +.ri-zoom-out-fill:before { + content: "\f2dc"; +} +.ri-zoom-out-line:before { + content: "\f2dd"; +} +.ri-zzz-fill:before { + content: "\f2de"; +} +.ri-zzz-line:before { + content: "\f2df"; +} +.ri-arrow-down-double-fill:before { + content: "\f2e0"; +} +.ri-arrow-down-double-line:before { + content: "\f2e1"; +} +.ri-arrow-left-double-fill:before { + content: "\f2e2"; +} +.ri-arrow-left-double-line:before { + content: "\f2e3"; +} +.ri-arrow-right-double-fill:before { + content: "\f2e4"; +} +.ri-arrow-right-double-line:before { + content: "\f2e5"; +} +.ri-arrow-turn-back-fill:before { + content: "\f2e6"; +} +.ri-arrow-turn-back-line:before { + content: "\f2e7"; +} +.ri-arrow-turn-forward-fill:before { + content: "\f2e8"; +} +.ri-arrow-turn-forward-line:before { + content: "\f2e9"; +} +.ri-arrow-up-double-fill:before { + content: "\f2ea"; +} +.ri-arrow-up-double-line:before { + content: "\f2eb"; +} +.ri-bard-fill:before { + content: "\f2ec"; +} +.ri-bard-line:before { + content: "\f2ed"; +} +.ri-bootstrap-fill:before { + content: "\f2ee"; +} +.ri-bootstrap-line:before { + content: "\f2ef"; +} +.ri-box-1-fill:before { + content: "\f2f0"; +} +.ri-box-1-line:before { + content: "\f2f1"; +} +.ri-box-2-fill:before { + content: "\f2f2"; +} +.ri-box-2-line:before { + content: "\f2f3"; +} +.ri-box-3-fill:before { + content: "\f2f4"; +} +.ri-box-3-line:before { + content: "\f2f5"; +} +.ri-brain-fill:before { + content: "\f2f6"; +} +.ri-brain-line:before { + content: "\f2f7"; +} +.ri-candle-fill:before { + content: "\f2f8"; +} +.ri-candle-line:before { + content: "\f2f9"; +} +.ri-cash-fill:before { + content: "\f2fa"; +} +.ri-cash-line:before { + content: "\f2fb"; +} +.ri-contract-left-fill:before { + content: "\f2fc"; +} +.ri-contract-left-line:before { + content: "\f2fd"; +} +.ri-contract-left-right-fill:before { + content: "\f2fe"; +} +.ri-contract-left-right-line:before { + content: "\f2ff"; +} +.ri-contract-right-fill:before { + content: "\f300"; +} +.ri-contract-right-line:before { + content: "\f301"; +} +.ri-contract-up-down-fill:before { + content: "\f302"; +} +.ri-contract-up-down-line:before { + content: "\f303"; +} +.ri-copilot-fill:before { + content: "\f304"; +} +.ri-copilot-line:before { + content: "\f305"; +} +.ri-corner-down-left-fill:before { + content: "\f306"; +} +.ri-corner-down-left-line:before { + content: "\f307"; +} +.ri-corner-down-right-fill:before { + content: "\f308"; +} +.ri-corner-down-right-line:before { + content: "\f309"; +} +.ri-corner-left-down-fill:before { + content: "\f30a"; +} +.ri-corner-left-down-line:before { + content: "\f30b"; +} +.ri-corner-left-up-fill:before { + content: "\f30c"; +} +.ri-corner-left-up-line:before { + content: "\f30d"; +} +.ri-corner-right-down-fill:before { + content: "\f30e"; +} +.ri-corner-right-down-line:before { + content: "\f30f"; +} +.ri-corner-right-up-fill:before { + content: "\f310"; +} +.ri-corner-right-up-line:before { + content: "\f311"; +} +.ri-corner-up-left-double-fill:before { + content: "\f312"; +} +.ri-corner-up-left-double-line:before { + content: "\f313"; +} +.ri-corner-up-left-fill:before { + content: "\f314"; +} +.ri-corner-up-left-line:before { + content: "\f315"; +} +.ri-corner-up-right-double-fill:before { + content: "\f316"; +} +.ri-corner-up-right-double-line:before { + content: "\f317"; +} +.ri-corner-up-right-fill:before { + content: "\f318"; +} +.ri-corner-up-right-line:before { + content: "\f319"; +} +.ri-cross-fill:before { + content: "\f31a"; +} +.ri-cross-line:before { + content: "\f31b"; +} +.ri-edge-new-fill:before { + content: "\f31c"; +} +.ri-edge-new-line:before { + content: "\f31d"; +} +.ri-equal-fill:before { + content: "\f31e"; +} +.ri-equal-line:before { + content: "\f31f"; +} +.ri-expand-left-fill:before { + content: "\f320"; +} +.ri-expand-left-line:before { + content: "\f321"; +} +.ri-expand-left-right-fill:before { + content: "\f322"; +} +.ri-expand-left-right-line:before { + content: "\f323"; +} +.ri-expand-right-fill:before { + content: "\f324"; +} +.ri-expand-right-line:before { + content: "\f325"; +} +.ri-expand-up-down-fill:before { + content: "\f326"; +} +.ri-expand-up-down-line:before { + content: "\f327"; +} +.ri-flickr-fill:before { + content: "\f328"; +} +.ri-flickr-line:before { + content: "\f329"; +} +.ri-forward-10-fill:before { + content: "\f32a"; +} +.ri-forward-10-line:before { + content: "\f32b"; +} +.ri-forward-15-fill:before { + content: "\f32c"; +} +.ri-forward-15-line:before { + content: "\f32d"; +} +.ri-forward-30-fill:before { + content: "\f32e"; +} +.ri-forward-30-line:before { + content: "\f32f"; +} +.ri-forward-5-fill:before { + content: "\f330"; +} +.ri-forward-5-line:before { + content: "\f331"; +} +.ri-graduation-cap-fill:before { + content: "\f332"; +} +.ri-graduation-cap-line:before { + content: "\f333"; +} +.ri-home-office-fill:before { + content: "\f334"; +} +.ri-home-office-line:before { + content: "\f335"; +} +.ri-hourglass-2-fill:before { + content: "\f336"; +} +.ri-hourglass-2-line:before { + content: "\f337"; +} +.ri-hourglass-fill:before { + content: "\f338"; +} +.ri-hourglass-line:before { + content: "\f339"; +} +.ri-javascript-fill:before { + content: "\f33a"; +} +.ri-javascript-line:before { + content: "\f33b"; +} +.ri-loop-left-fill:before { + content: "\f33c"; +} +.ri-loop-left-line:before { + content: "\f33d"; +} +.ri-loop-right-fill:before { + content: "\f33e"; +} +.ri-loop-right-line:before { + content: "\f33f"; +} +.ri-memories-fill:before { + content: "\f340"; +} +.ri-memories-line:before { + content: "\f341"; +} +.ri-meta-fill:before { + content: "\f342"; +} +.ri-meta-line:before { + content: "\f343"; +} +.ri-microsoft-loop-fill:before { + content: "\f344"; +} +.ri-microsoft-loop-line:before { + content: "\f345"; +} +.ri-nft-fill:before { + content: "\f346"; +} +.ri-nft-line:before { + content: "\f347"; +} +.ri-notion-fill:before { + content: "\f348"; +} +.ri-notion-line:before { + content: "\f349"; +} +.ri-openai-fill:before { + content: "\f34a"; +} +.ri-openai-line:before { + content: "\f34b"; +} +.ri-overline:before { + content: "\f34c"; +} +.ri-p2p-fill:before { + content: "\f34d"; +} +.ri-p2p-line:before { + content: "\f34e"; +} +.ri-presentation-fill:before { + content: "\f34f"; +} +.ri-presentation-line:before { + content: "\f350"; +} +.ri-replay-10-fill:before { + content: "\f351"; +} +.ri-replay-10-line:before { + content: "\f352"; +} +.ri-replay-15-fill:before { + content: "\f353"; +} +.ri-replay-15-line:before { + content: "\f354"; +} +.ri-replay-30-fill:before { + content: "\f355"; +} +.ri-replay-30-line:before { + content: "\f356"; +} +.ri-replay-5-fill:before { + content: "\f357"; +} +.ri-replay-5-line:before { + content: "\f358"; +} +.ri-school-fill:before { + content: "\f359"; +} +.ri-school-line:before { + content: "\f35a"; +} +.ri-shining-2-fill:before { + content: "\f35b"; +} +.ri-shining-2-line:before { + content: "\f35c"; +} +.ri-shining-fill:before { + content: "\f35d"; +} +.ri-shining-line:before { + content: "\f35e"; +} +.ri-sketching:before { + content: "\f35f"; +} +.ri-skip-down-fill:before { + content: "\f360"; +} +.ri-skip-down-line:before { + content: "\f361"; +} +.ri-skip-left-fill:before { + content: "\f362"; +} +.ri-skip-left-line:before { + content: "\f363"; +} +.ri-skip-right-fill:before { + content: "\f364"; +} +.ri-skip-right-line:before { + content: "\f365"; +} +.ri-skip-up-fill:before { + content: "\f366"; +} +.ri-skip-up-line:before { + content: "\f367"; +} +.ri-slow-down-fill:before { + content: "\f368"; +} +.ri-slow-down-line:before { + content: "\f369"; +} +.ri-sparkling-2-fill:before { + content: "\f36a"; +} +.ri-sparkling-2-line:before { + content: "\f36b"; +} +.ri-sparkling-fill:before { + content: "\f36c"; +} +.ri-sparkling-line:before { + content: "\f36d"; +} +.ri-speak-fill:before { + content: "\f36e"; +} +.ri-speak-line:before { + content: "\f36f"; +} +.ri-speed-up-fill:before { + content: "\f370"; +} +.ri-speed-up-line:before { + content: "\f371"; +} +.ri-tiktok-fill:before { + content: "\f372"; +} +.ri-tiktok-line:before { + content: "\f373"; +} +.ri-token-swap-fill:before { + content: "\f374"; +} +.ri-token-swap-line:before { + content: "\f375"; +} +.ri-unpin-fill:before { + content: "\f376"; +} +.ri-unpin-line:before { + content: "\f377"; +} +.ri-wechat-channels-fill:before { + content: "\f378"; +} +.ri-wechat-channels-line:before { + content: "\f379"; +} +.ri-wordpress-fill:before { + content: "\f37a"; +} +.ri-wordpress-line:before { + content: "\f37b"; +} +.ri-blender-fill:before { + content: "\f37c"; +} +.ri-blender-line:before { + content: "\f37d"; +} +.ri-emoji-sticker-fill:before { + content: "\f37e"; +} +.ri-emoji-sticker-line:before { + content: "\f37f"; +} +.ri-git-close-pull-request-fill:before { + content: "\f380"; +} +.ri-git-close-pull-request-line:before { + content: "\f381"; +} +.ri-instance-fill:before { + content: "\f382"; +} +.ri-instance-line:before { + content: "\f383"; +} +.ri-megaphone-fill:before { + content: "\f384"; +} +.ri-megaphone-line:before { + content: "\f385"; +} +.ri-pass-expired-fill:before { + content: "\f386"; +} +.ri-pass-expired-line:before { + content: "\f387"; +} +.ri-pass-pending-fill:before { + content: "\f388"; +} +.ri-pass-pending-line:before { + content: "\f389"; +} +.ri-pass-valid-fill:before { + content: "\f38a"; +} +.ri-pass-valid-line:before { + content: "\f38b"; +} +.ri-ai-generate:before { + content: "\f38c"; +} +.ri-calendar-close-fill:before { + content: "\f38d"; +} +.ri-calendar-close-line:before { + content: "\f38e"; +} +.ri-draggable:before { + content: "\f38f"; +} +.ri-font-family:before { + content: "\f390"; +} +.ri-font-mono:before { + content: "\f391"; +} +.ri-font-sans-serif:before { + content: "\f392"; +} +.ri-font-sans:before { + content: "\f393"; +} +.ri-hard-drive-3-fill:before { + content: "\f394"; +} +.ri-hard-drive-3-line:before { + content: "\f395"; +} +.ri-kick-fill:before { + content: "\f396"; +} +.ri-kick-line:before { + content: "\f397"; +} +.ri-list-check-3:before { + content: "\f398"; +} +.ri-list-indefinite:before { + content: "\f399"; +} +.ri-list-ordered-2:before { + content: "\f39a"; +} +.ri-list-radio:before { + content: "\f39b"; +} +.ri-openbase-fill:before { + content: "\f39c"; +} +.ri-openbase-line:before { + content: "\f39d"; +} +.ri-planet-fill:before { + content: "\f39e"; +} +.ri-planet-line:before { + content: "\f39f"; +} +.ri-prohibited-fill:before { + content: "\f3a0"; +} +.ri-prohibited-line:before { + content: "\f3a1"; +} +.ri-quote-text:before { + content: "\f3a2"; +} +.ri-seo-fill:before { + content: "\f3a3"; +} +.ri-seo-line:before { + content: "\f3a4"; +} +.ri-slash-commands:before { + content: "\f3a5"; +} diff --git a/nummi/main/static/main/remixicon/remixicon.woff2 b/nummi/main/static/main/remixicon/remixicon.woff2 index 336375c04c6041ee63b9106e462c717e238a93f9..47444bc6bdd5615cbbcde6cb1b7a0c4abfbeb9d0 100644 GIT binary patch literal 139320 zcmbq(bx<7Nw(c;v5AHDd;O;iK6WrZHaCdjNU?Ert9-I&eGPo1m9Rfju1h??=JNKM> ze)rb9Rqxj8Kf0^C*4}HaZ+-h))!p0Yl^iz!4gdfE!GZwvKUbvPDFBda=O54h-2V52 zTpP(85T0s}_I8dgJ)a7WA4ms3Mu$EI0U&@8qnVMzdrXbYw_D;0xWbj$3PJ%;8fp3;NX8PYo}BGFd^afAGgo9?COH$(byYgpr#k9AK?FaLRL7Cfac z{ye4k^BMVe_r6)S8r&B~6Aq70RMhsxnkkDBy@~mg?GbyKN_3U@hZ@GpWt;6VLKhN_ z0|IWY*Kb;u)hbZ6ijTC&(Iw*KoxWB?-(Fy`1To(aDF58YX^WKWhzZb+^7=ZXQuwoc`xfHM=F-1N^IAAFy9{@i?O`SX-zsZIo9dANtNkw)(`C2J-}NB5zV zK5*!Vbu){Ul_&Q~z(3B&|2Sh7bCGc0eJFFrA$f}HX^o5;N$n{_J*L@vrOyx{rC(d{~SqoGAjSsk_2sw}9^)1GCKD=Lex)(QIExpcq$ z%oVxK4e8KmsidDi-C#avX%B`$H}Ifw|0jne4O8Kk7D8bVcu3}3K)*9K6L7gZ?;AsxF{S8G9z$6D1BGS zB5pNsv|g`#E#UC5-4yD`-}>E9~DTwIgC6XUot=v4AgiFvHbEG#1~rI=!b^2zh{ z+zSF7vL!>kK1KQllGVpaYaVeFasv(&xW-Q&a@ab)q%Gf&-!_2?@gE?Zx%80qna+J5|Z7@ExdzYiPjR%I^vgS+)wt zN=(&>g(+4@L^S;6`0E?hht4W7m!1U9Eet^fRNNv6+sFCUBFw{b{Hi^5UU7C|s&Cbk zrtO>bqa5ireb4U_RhNS+U0(Vutqg@+1$T(k3qfk=ra$>Q;T8>ZU-DPFo8L}8vJ~5( z#E!$g%V7A(J3uXIQStL>gzNQ@{kO?oj3OtZbr zzB;)mDP`a&$Vd`0VOVz-0U*+pe$m&Q!}H_ZK| zZFNLvyca}R%InY;T~$pn8W1cIPTBU0z1I^%3DxZKe|?BhTy{Rnv!G7^pv= z6}^Xy=z_a4f5j-Y8ba|8!w^tWBB4Nw52>k;e+p?JE4-gM(i zs|{RQY9tPVD|9Lu_%iqL2?P7vwN%`KQm(zQEHC*7O=fm>c0u-F3>2?fBo}4Zn=GqL z^DS(tby>>z#YBiyAD9J4DLq<}IQK=fr5%_VVeOT=wn3um?b0mJe*5!lE93rdH2&`w z9Tc4??I?W!F^n!$y8;M712d693F1-klN5Lqr8ClvMkwP;=9O*;6-I$fUlB)7`R6sf z3j+Wm@ar0ZT|^0$>%`61Tka+DNwVnubZ)S7hf`)-%=KyXNm+y}dLqMYx!6Os&s z7?dl|FpljQ>_iQ`J9ZS98e%rXTq#_AFV!>YOlF zl|dkh$q7U{4!4KN_valh*1d9a;UYG>bWL{+ijCx%Z>^GZvK5$ipG32G;s~-*Rmp#% zvfYt~E@3SpkEr*a8or}L`}9py8J>cfMv!v(P_BRf)WkR`7KDuY+BFr@pZmU3UfFFl zxxfUfjdO6TqCZxVW;7*W2Kt!-$`W&95 zDXZdsYm+Lp`(kx4abp^DZOB4PjVcX`LmO2XUf3{`D4G2|OuD#PXqv%0W$x$~)@O*s z@mk{3jr{(J#CsnnmsYRBqDVT7mOhWU2_^7x)I(6+-evk2bvdQ3&b*!utz2SkY(FhPa939L*HV=Jyzz^59YfX@VNs-wDrC89d69m^Ec{R^(sBP;>)s?ORV?) zEP<>3$>j8JRl&-iiYq^U`cPf&Vs3Bk=>A%W4=+Z2FiLx637oh?=OsRt&{*~E^Kpz@ zUbfe2%4K`u?IRp090$J-HNAu+YZp-?oj_+6bGDIU@zP++f#SY?KyUQp=gAVQ(;+_t z(Yi;!ZYlsZu_RYMm?_Ufh{RVi(^_`kGh(95Mx^!t*#e*ObrzA0j)2o5_AW2S@n@<7 zFNw2T_)st>0vfykeJX?(DS;MDN5eo(Pg`D8T2fa0@q-@%@#9zoJ9LiZn-v8Z@`q9qfr9q6!Aks6ck5n4KI!a&GCuh2Lcj=2cg(`vfP( z+J6rDt*`lQoFPL|#Q@N;xtCW#P-hq zL|+U?w-5Ct%D<+-?1iXl9yE18{^6u(?O#d#2?&7FgA)Iz(@kTHU4%nM;;Rm$UoR&g z-~Zj|-xK(Mu%9lb8hmZK;3P459Q+3~kcAWpk>+bFlfQtq_0!Pxn)QC(8v3V&GgHB2 z_8-#h9tO5;oThlK27x5vq3Hx~4L^MRWF((r8uS1cTfdI-4=6C#2>)H2gvs(}PPNNM z=SMfw+sj}HfD~uArJ$rHZ=ZVySgEnZq20pp^R9pJ-`)N<`yHy7|ILU{6ui+dsY+zW z;hgaqy#Hi8vc%tFz*Sahn_Gyy1PE0`qxOZNR9l`NxOf7@b75S zEg!J}fA~1y8eVE2{r#Wp?`w7(y1s*9|Eb;#E5~S+^GfZ1LH57U@jprR4SO8xgP`J{ zYHMiBPB~2fo5O#V`=2bYLI3-tPk$qX0xQ>dO(QCYTHs%#IaJa0KFt^PyZnv}?nS^y zl5Z}Zk!7=r_yfS){r@JWzq|W8JN|19lXdw2Qi;D@@Q(e|&D2?MHjzlgvnBhtu*l^6 zLwfXy{({rM^e>b5%m2YCRo_3f|`026!C+GN{gpckYtJ=$Sf3~*;9#yt> z{h<_RaUyzUbHiUWKMMT6NedW(77~pYEdSs5G_!j7ACdjP>)roo|9?rtMB?sm3Yw;G zIm!!Zxh~$%;WPTFrswmjZX@XSuzd6P!=G|QaX_^)EEr|LSHFze;M3%6>ldq|p4H z`**jYZRpQbb_%L!6Jz`2^fM`W@(E7VWL>N?=sCBfOTq;G0S0fkSS{ln!W7>D#$f6$unaY- z0Yr*`w3tA+m*o=-d&^_f{c2kjJ}MFcE5};f-DV&MC0->vRRBT+W09=&tCCrqk9 z3-Q~^U^#}@@~gbIWSz`I~=06XYi#NgJd>UEbOj`TnR4FWz5 zBFt%gwrN6&X@b!I&w32MO1kz`?bjafH)ieEtm!v&?AJZ%Hzn`a$VZ@)0wXFFACkby%3dNR@Po-$Nwl;FPmsDOJFZYAOSHr6i5dH(i8z1wt&>B!STNWRpAxj z#{r|jNw^VsHh?Zl2Z{{WFs}N?qA^%z#3z7Wt`1Dk4im+p2`lm%#0OnNQN$~qNNs^` zpvjVD{#5s}W~}&HbQt{^x!v@~qxp{*H$a(WPK-H8v=_o>jisS2;usHLt>TyRt=woI zq%MQI15l2{khHQkZsY-KO%TDZF5JB3mGVY!KWSQmv6* z6QI4!}&w$?0JR zxW*r0CZwV^FeAdi?=Uk;AOTn(N9rxun1n_LY)D`^4>qO1BrXRM%NX4zTL7~B&bl)Q@?-|6H+^fnUqu8@n4Unc9D~?q_*K2RHt^53cX4FO6Yf-+D(Z_rv3#- z_MLhM3B8eeJAuurdKU$OhfI99&^}3CN~u{3UMk5kjb=*e zRk!9s=#Zjkfz;w_&qB#>23G~rn|@bjFqkZcY44N`hFQRou_*xfT}bkfzg5pWU$ z>yal9?wz(H59t}v%Mb2b_Q`*N4ZJe`-8=7M{L(X#%o1F&oh|&Ec&iXmhM&5Dg!|ox zdPKR;{r$Q*%cSV5<~Bx-jxq0ly4Axoiqa@rVNDpu@4T#Hd`fv=;cKxfma4?0E4Llhe7>xfmP< zrTUiaNsuXaOYB535>vQ%EP&%Gu~BjL++6$&J(Hz^IT(x?yOwBcDseHD=iGeaWj)`T z5pt5v345<1n^0`Qqlx`Fae_P<=7j0vO7Y7IJR&6f;IaI@oCHx1Epy8Fapkys2#*8- zVbN&#oPMk@`m8xl9;;HqfPIr7MOM*R`jvjXxH8WlMPMC^{Z8B@il=Kn8b7WdC&bcZ zPLNeI8b7b!BuutoK9+Z)pCI)Z7EiS?9v>sz;QHT!na9? z>hsoE*3(kFgb}(qS(qbB>`#QIPLMZfG`V+4QBWkooG9&tIeuNH=_~OKXslpoNm(>N z*_W*d29=mOU}$k5*c?Z_d9KbtrNBl;ochVi?gh`yhX0LDDt9g?CIn8>nBWE+2;}wbTH6W$kM6g|r%%D*V_Fm>-4WA_^%sB7Qn6X-b25?H$^3)uJtTcz|H!aMWd z7vtzSdyCz1QS2lBt7l03zMrV@VV_4L@HCs_>4H9ESt5i05JCYC!2^YmafT3~git|V zK*TS^f-m?8FXVJDxcD!myf1_pFO;k=IOH!RqAvu%7YdpeJlq#D?iV7o7b<1|J1-y_ ztB4ao&H(KN+Ajgnk+-&iG@{UG>0pUCiNX-qF!zzV@lGCe~03GI_ z2}nPtfwaQra@aM*p%k>PmO$rmSOVf*LpYcpG>BCq2qkA!*a5y?11BKwU4+w!s}EvV zAwb`Q?Qwxly+z6hS8?IgT$~J8WpuE7I{kFun;COuq?3_wS|L{k>{@)-dm3L&pv&l% zGU8LE6qpaC46B$IoX;RL6K=l(Qbyi6l%f$+D8sJA0KccU<_>opOdUh`l`BQf1F^;W z$Xb+7uiY5#u%JGMbhIo*D`Ibp{fWHjJ*}sExbx)H7~(^ltpMjRMYJCb@{0HXF{8sG z4}1JqlwWZDFjW>JtO}}6gzzrh!U*LAEh@;u6+)gY3#(KRBtpzC**3&ji;oI$JBLsf z(8H=_e6tY>)3=S#o|K|OUZX%L!fe15DhSz8sf{>>SiPgNzJ9R~sx$&{m6VKLWKKMe z5$X=JY@m%o1$mqfxKfEuFDkPt%@A{@O4i?NxPmg54_qy0y%?FFmu7_i%Rx5S2~t53 z<+Y~=f*^k$FoleDdPFJlFVcuLzl+i0guE@lP zpbr!lHCBl};c;qQ%OqvOL!LxAhqz597PW82eIlZq8GgU{yYocf|zSg#_6EgQbAfmoPk(nGhL=R7J;Sm+^r zgWYKsM&7d!YqDPA#A}GfWlp?sX_98>k$9 z<_cN>2jKyFm5pQAH(BdK3)!0B2g!5sI1fhGLiw=k4-h8<*F%C~CIDY_U|Yk)zddZN0|LpyLugSPF?v+N%}kL35yShSb)cn@R3M9jBj$(` zxP`5>HDdfGbsf%KL@JmOa)L2nQq;_n=@T(}s=f~I5Fi!AVSj=-5meN|(R>>*`FlzX z;Eycz0wyNM=#w`0gd}1|jBIm?0d3`bpCJ0=m}6GvUaU1K5fc~nVsPG!y}#)M^D&0B zwmg~hUqy@^x{AR&+x0%P`sQPP^VsrY@Aww+?f$$I5KPqjLW5wO2vY`mGKF(U4}VAL z1X}3PJTc1{Ck?*=d9jt$NsnL0cEY&{(EMhgTTARWO7&z(caa|bIZu)5&p2}`^{_tt z1v&SY=2+Z%GbJB6^%4AwQtEdf#1|ndrQ`<}zC?05qxu8gXo1xIY=tkwH>vL)75q>n}H1BiZ}74HUl28Y&SPe0@Mi_4uc z{&>WHF0|P4zz7a2=HPlFkn0-!Awc{{xVvew6QJ@tw33SJ8P7I&@Ps4hk>Il1V%vxH zci2aXh9@H3--AaI`j14%FBZEYcS$x{fM8{4un{;|-aJ?j6#QxmBYKl4Ujh^^fuAmc z8!thaCxH_!L69YZmn=afPP@e+v&SL3Qzx@uC%f$;Ghu&cG<`3^br-;OkKAx4-*C_9 zc4y~yPjqpocX2O(dgp_Bj~#dSD(;>`@ySUl9!pIhQoS%}~$e>xAelH;Edr8gg6SPuK_}A-_a@eW!czahs zzfMU>D)l*{%qw`CXUPG8Iv;2b1LzeY*$0~9#c#%dcj}WILYd;jy-FONhHesbauQ-B z!PD;cEFo}m;i#wrc`|y|@as7VSXJR!W_wmJ>bdY7Cx9YNJsae%oJ8ak@Qjx|%Rtu# zkTDxjAfaa+_q-lolntI^tY;PNya6}x6HubMXOk4Ao)Gvc0(?xpgoM(7Bjps%`Hp&x zFt(n6#wh}_O1**=+kj{JGhFa1^#-LvJrVBD2)ak=WjF;lkS0YqZw&Q1&af*!H$?>N z5cMkRup6%X`*6{6>P-@eD|@cXsg_L4_*;vq=Q=V(|)Qg&Us3 zdbn_F@dkyxD-r2>1jB9dGQ9l-$WSbtKc#pb@5ed5uvi59x8haw9~Zd(o#Enj#hYZr z=Y&X|5!7cEOUT3*IC4SZT!j{EL^ zLI8bKkS=_9OQ?kyXbAqp8f&zU0c@+&O2WcsXz>_;OI<%v|4I0UB5b>t$iHll?;iTB?gaM^jm~Jwe)Se z0oM#$-x1CyYZ6KKL{2}sOdzdVT3Ttpf|jjyCFd_~5?O~tZZEmFNFUTPrb`FxXKg(X zIUhVpr2P}Qf(A3;tR_<%to!wOY#nnsZ&XO+Z4-Gs2Hzs?PNrX25BN6O1}<~{cFcu( zt8fM|WWwW3rp4{`%XrvYwsBq<=gK&%aQiU4Mb4Pa7}*=JzO;3}=e!Thl?qnj3MtbD zXtAeO;`eK#+d4#XUQ6Z5S*SF*mg&eO_cteuPUom`skG{s>5& za}}A!o3(9qNqIQl8)Wvdj#=y4a2+@1stS#NHn!EHY~m;s(uR?R@R2{a8H_}?jEA3PB5nbXKnC6Zs zTC_Q2eKQCPW%Ya!okHYnTaKuDEMJWJW|1~NpSMJGivcM>C$Z{`ks`D+h|{&PUeX;R zxMIKX(mPX+l02!bA81yw+kOvY7jee!fRy?mDMj4zN5l@h6|ai8;`Z?kXYIcg@u06S z|Co>rVMn^;%uksHCK6dTqRbBDu=NBo5suXxC(onR=vg)~S6h5#cw^DZer(YV7n3CD z-20km(#Ul={+NI5^KKV>%6s3_-Zugp0q4sJXW~ycyDqHpB!C3O&;}rcA25WKDhMED zgzW%**Fs~FQ!jwb;?N=N90b5S@D?sGuotR;5E}<%;sP^bWzvP_(M_cT{byhbNI4@w z79n#+?0o#tcQogkz~E7^0%BxkID`*Wj+M>}&0`pz0s5^JDIjMahBJ$)mt*H)K;KdS z;06W_nhzr+=7uxzaN1#IvBL7`ml}Zq3tPiT`OD!fBCdAW1>~@Iw0G{nkV()mVtAVr zL;&RgD@_!f$JqZX+;<~&7&-l3idjP806P~5HUjUVg$MSiLlEMlq$)WfL|AWWiVW%I z;=}!?ryxjq15(w3_C(n4af^&-u9U)qM>rvf(I0y&c!{&IGPunR8OBFPm(5MFSVyC5 z{OXyh76@QvQcJoBU*j#zP|2C2tZZDX$R~C7lw2#(YIXWPk$%xbPdVB$5bJOV^+`If zMD611t0TNyj1OG_>5GIPL0#XYRH7Gq(biB#E+)i2fD9x`NTHq)v9D1YFllRX`UT=6 z2UC{>)AOM2849nNUr9Ug_z}+JN&8<8+r#Cu(Nxo0wCPDg~#R9m)*p}s?;t?w0)BE*wt6*kJ_bz7VnWr+hwhM?2jlX z#EL%3H{V2lE|}gx7xtI6bNO*Zu^v=Z{_5%`s;g^y6C3Gaz&wQbh^h}6HVnt*4^E^Y z-bL=|m*n!}RG}cBje(6*a|J-gDM&A4dU`Dyd<9u4D97Ysqc{!zbTt$t$MQXc8V!ED zjuaHDL$FD%h5*(R3bMzco*p+hUr}-j2|U;Zs+&7aegUx^W6!;!n;W-r0l8i|?3&5V zgL$oh)Tg}X`SpdXuxJ70D?8XF$%Q*ZM*)e8UC*Pzg&Tih0fpHC>{jT)gZ-(1EaV`} zFpvvSB#D4Y?MqBetIr)j#TA)E8wkY7h|QOp<1MiR@gyt>^-s_S!=WZ97)#A?r|YFQ zqVb&*%?JS6a7f;zwvkk@4X>bG@msqf+akMr1HYoTeGOZMe=-H#gngkZg&!7) zeUb)KA$>=wkk@dCmJ1^XI$D(O$vh@2`Bxr39LQtS`B*0L{BXmtUxHfmmDT8WM3J(jlRKkEheU^@w?g{S<<87jyJN(#h!7P09>Klgs5LSCm zQq?FEd@05RqEGqUitj-J7UYxGdzNE3{_ufQfuaa`LPxf}o8Yjrx9M7R3=^GqXBt~D zzNYK!f~M>Rn$PM1J1G2-W>6{Oz)aj^YWt1YkFN|{q&d<=FDAs7+O`j~ZO2GI_hOYO zuDuO{B-3Olwus1+5j4wvF%g%Gw6LZw6xfl*!Z!-ITc1`RFOOmBjopt?#PeMm^Aanj z-i0TVC;cuZAKQK#Rb{j@lrn~;gA-k=_HiHGgM{=)T`zo7Xbux9f~;+9;``rhIyrfi z%P2?d&hWmDLCuChG}a6Dqyy>R(12Y*|91v+2`lL6ps3n}Ifgls{z#X8R76f#{JIde zE@N+4KcjiB$i&1Aq3jmCZI0_Gjk&2Ga3teOF)bA%3}q>r&6`)>o$9e9k*v?awvrx) zmcHiQTSh~y_bp{{-+ycjM(oeFftCiX^Y>zYRb=o>g5ly@+o#2Oq`#_&>DuDAd<5Jf zW^blatOACGJddqETbnWT=z-LGb?iBk&~gp3IOq-(s@s4rHsXuON}E)802jRa#@Qu z?F=pzozOhTL%Ao4vE1x>j^!KhkfV;1e@ix#VJurFv(JD19(E6MkyF}QWwsz_sqoM} z&gz3lkNJ`Lbsdk6FF}~wK(V33Vi1cl`(vhye71zar+6^es%2v%YeLpkB>rwRYFS8P z!wIE`l|AlfBp{HK+}rdnCL z%M-DLj4ZN8I?bCwcB0LQMpc7^&+?mB;t$hfv8EMrmF=N=WVz3 z0Xx69uiy2$8P^xFgrNk28*48n`8%=VO$nr(4yyB*>NZo%?o2ad8`(GwBLRPQR!@op zD{5^IDjGEQkL<4iGiaUg^7CGjHLXR|Q$#Hr=!pcc51T~l24`*BJ{a}^{A7~pfWk$*AU{CClx9aY9E~t{Ofa+Sg17D=VH|0^DzJ4 z(6@=VlNly8*JIq_I{hd;)o~RBgr=er8&`!_C9miQS7TcoyT6QeI6Z18*spz4y$#B| z1ang$JJepXTYs}Vd!xJT9MU4;Xfg|u#l0SV-||6U8jUehJdv19w;QRjMp2c7XG7F` z!{x>=6FoHz(|y=h#P*VtYT@E$;W4TKkP#|Ji$-|13My`&UM1e}jAm|1wL4FE$$H-* zVSWu+oYdfo+bH|mMUZL~d|@VqD6Ry7ja=PX*A0hf2<}+y zlEx4%wFFjW^xS8bSX~JHplYTooNn5uHml=33SsCEELC`!NhH)jE4J|iYYQ`Rwv+Y+ z3(uA;d&}nOrQvB;&*?9*g9r|`;pGGDUOB98^>FxbGF;||P-Pv;10m8c?@ITp7G_S4 z_ddvd@VFyKguVv|>Knb$m2KfOYB@Ly?`6lwLyp59%YFuF@F{6mvT&vli=EOHHE(Pq z^|l)k96z`RjXRA$d%>B)<*UW?HEO*nE(+%Z5l;a`luAA?JZra(HGls)f>;g^mo7me zg)=V&0Ad)m)h7}c=4!egCVw*Cq$Sz+zVHm1?KwGy_NkmpQ0Po1&HJbA*qmk$Qw9~9pxKx)tmaI z`HS~$`)i~}+LSWVE=dc99-2hisItZhtM`8sa%~cieR@oXL zrn_oTZ|zZ41Zlx-$oRk%_ju#xR=>UYh+#aWaI~2tbdrou22M8@Iqop%1x>8s=^#!h zt%`hJ6kf#Jm;v}bze@X&w3^nNp%Q_jsH@^(OK-oxjM2QamZ9?Uq42S~iYs-heX?b; zd+xpNT;4CzX~*#`Bw?NbcVWgfi!8FE*{KxXht&__-SgL(kvr?u3aZV-M<>H-dRBJx z11*zYM4v*3EyEC{9QDV#4y`;V2bXyV?9B%vFYv8+P&KmhVuNCJPwdxeQ16K+ap1_p zYK!eOUSGKj+}hi6`6nRoDK!-CUO)TC_SBeLsiVs_vk#Ey6K3dp@5&uoSZ!iCkr^G) zUPS+(&Lag(A303Q5jB`MG#tjYa_ZJFK;#GZJMK{cx)zs+KGQ2chhzY1eXgA+XJWO4 z8(kSK+mH4ppjppbyh$NO)qlr`I30<(Lc3cnPkzEXf_k*Fa67E1P6+my2SytA4M zneomM5Y*~-aMuL4vmPn)bL!g(ppCfMHOeB?Rr=|Ujr`YHW$eq7W%xmjb7qB(5@+h` zC%EadE0AT$pR37EuUF@Q+kRZ*Be>4C{byI)eZ9@1tu_4U3j*i-{2En^zcxH}gDL`7 z)un5*ks(*PP|BaR3y#=@EwV;tE?jAF|ciL(jO)fWclczS*zsl}E9 zS4|4qz4vJBUCQ6z72};NEtBMdgO%E6joG}Zy5&{{!er(5me`VRlN8>1$LRfyNs-u;vnxip?-h}mZlCR z9BQ?ORo8;UeI558lTgG>AieS4=+tzo*>7a%QrLD;P1w7y#b3@fI`C9)0P~rg4{3o? z3e=&WbT$D3%#msGirajf{^Aegs~GsO!`45q6kWnM6XJzsFdx740r6c1!*s!PO<8wg zM4cEp3|*8-CcuHzmjL_O*M`LqM)gveN043OuIL8Y(9pea7I4L|i~Od%WT9VaFKV)* zVcoUCj@R$F4r^vJzu@B4*S@evP1r+Ar2t2mSg{@m_rql5ENU5U&d9O?2e=3dvk(PK z`r78Pr`=OW!>eNo0>Wsbe2(#Eusq!KP7O@lhIJ0uktz}A&DYMZD@DaY8 z;0@~7jal_IwOCobTXr;At2BwTcF>kpOj{T_W6u^xxA822SE0M+>BTAHeo&XTj~ z#k=Rz+W<;2z!&ftp&4Q-L{=#SUjsszN1p(Zy(coFn)or#5?HwYXW#3Z>a9MzjeOS21dJl&;)0o=+%g$K9u%hdi>yRw)Y{xCfkM`|I2jqUOplp#{{H6Y)@g+t z^)+O5G`UC~8g;S7lQx>NF-Zh{5jb2r9q#_R8@58qslj{ZMwyK9rG=5ID6NXZjdk9(g0cnG)dgN;lnrGVQ&=61tbz5A){b4;T_Pl1210+m z=fBebyf6ke9)=;x&1AfP4QVB;d}Munn&b=g&VhTx7t{S7O3J{^Fi# zwqzVW8hdoOAr*erY5;{}9{0i86Ni@8hE^MZrZ6W5Fa09wq+4ZUFA;k|)j{pvq1vhT zo<>1IWZ)_KuPD85z3quh7d8pB@S!!bnE=?;M)H_h?=;F%UDg0FSgWfe6@%O~iBQ1~JVnygGg+2gt{~<8~=ES|O zv3v*o_+S74wJG!?ls-X|#+tl>ih!DcEKmGw-?z)44*8XkVDS;>VvU%0JD$cuF;(-> z8FA5a&5H+aXcUr)x&H!^F_COi>YOlEy!%L=|S9MEejh;QY zxZbZ8#2Z>jKY&ChLy4nK07v9kO7~xsWtg}#ej1I#4JN`Mrp3QKm5yJVY#JIDdhn>n z7&A>4?63zKOH6!F0Eo*F0z@9n9+5NbHA)Z-s|g(hI>=}3g+XzP@KRJXGe~(cbW(Jh zFSg0^_ ztw!W@=~urLnuU%jrYCr&x5q9IHqTB2sPhJYI<@|`UU~kaqN%2mCj?Av5Q+D0OVE|2 z_&_J{<vkvT3>iEL^YZiSsy#S3em1iGafeA$g%w#f5xBz@a)YCF+I8o?zR}VW;Pj?* zq-*~2-Yxb!Bdi&*_`!v%YvPVF`2381)o1U>$-mI}ouQ|JsA9c1vBxiW;m#T-1?xE* z13kL#3uLpwwau#(f!qOMM){dlug3aqC*ncp;hRFcO{lY^%_~i#A3D*V2gO$_JR5C8 zEM>PN&r;v@?59-#Fq*S>j$|)nNr-78Mpq}v$4j^=^t5xz53XJ%xK>D?orU6$=6@aB zj{Q~+IP3cH=&<@Bpi8ZJ@mkcU4-?NMSE~8cqkkm51&^`gGnl{@5?8NFY;S31DNi6! zASfzQ#W`!aBCyTFA>;M2qse}ZWN@B1Y$49u+(^E@-u^5?G@3KPVR63d^ICa;V#I>y zJF({Qj}Uv%6}AWbEx_Ik$Plc0YiO`BC2tfidU2Gc~-;rF4XOHosebd1N2le zx>BmX$q@Oy6XvxKrf-Q8KOF%gbaV{8ucc2T7`f3k+5zzusaC1-}@Bp z=%=?@k1*y--I&ga$go$~Yw3h}XL_aGZ1K18f-`!j6O6iCvbEsF9#h=dEcNTDw(lE1 z*7pj^bZVXM1`iE!_PkDF%=+WA%+Lpibiurqud8UtKRcf(d>jyrc>zw4yC9!VM^vzTCbLTAt~H{7q`z znsfB+0tp^5batEp%K#q=!7I~#y^U&IrWr#Ay1BlvlB5i19^>^&k3HAq$5ZVTo9g~H zEI%&qJe)3!cuJ30{-u<|RE-+i>C@ictNos&5vd52bTqPJ!z+Lut3$I?HSXQKX0BbL zu>n470eoIej-5&s@kdvGEvVCyRf8iR>EzU!+l`yZs&JR(W+davVC)0>1J9^IQ&ID} z(ipcE{BPyo9`d_x?i02ArmCFfFGD#S3n_UPLDoo7Cd(GyO-jcv973nEJxP+A{1=oz zFquZ)5*OVIdy#6G))<*yP^4^8gM@vlmgr4pV-b~+-|g{4k^uL~#niByRpiitzEsfW ze63vCpj%eykfdcS&XUr{#@C6}*Bg@Fr7Oh~kumx;ImcH1emVG87%IappH;t`yuFR) zr@gL20)f|F{-%wvyNS?Gv=c?Vlq9@7~qs7+TItc1pV)KN3{x?L4qM- zL7ltnpUPX2d#~@I-5$gfq=jPJfNm{q+~;ciCqu;~5u}gQFT6>Y*aD|{V7id!O76X# z?2m0%c!7e#-foLYHzfFcS54S44gP*;99vliKZ+GP7%)YutfNEjGMG(Q41-ElTu`5P zLK{;Ly(WLOaKFo7_rRp(^RH)U{@!zK;G~UpCh4%Z&$M_d;ul1 zoz-MkhP1e~%dT+t;ax^XZCEA!`2rqthtoByL6>GVik5NT^oIZK9fM6pNY-4_@w-=d zZWb#%GBfP%S}r)E3tNodjHk6OMY+d6JCX$~-Z)Noas%-FOuD*}Y|lTWX`q%7CTBV{ zc{nPNI-F2vXuWNKnx!oCVB6JXQJ%lSj$vJR9_muO5}^KacZ69eQ2QxQ$F!^B7nOX~ zdyVYAu>GMT-4u;6oKm{TsdkLko>VF$RG%Q>FV|@MABr*IKlqF6Sz7VdcfadC&FT+; z3V5B(sgUL!SSx%lZ_tjftgbG;3-EVP`4wiMdXP7WDqWmO2u>>;<-lH(+P;MT>I9Vq z#`*l+~ zf)`G`y9!^KfX^qxF}EjD>`m3xw)US*>bw|HMNP1c-OT0y;RV|U>Sb&^8OCWIB^D*h3dc^yKStWtj=*f z^KR^u{6??B(PLtg&@QLo%|Gz>4iXEKhi##)q?VC72(D!c*1G9KBXy{?3Z{Dvt>Q!6 zyZdEI+|lC49%P)dR&{mKzY#L#s^)lT3WhX|6@O&lTsO8m3sGNr5~u_66%}Gkh$3V$ zEdJTKc2ULfTBplUy??PrE##!NZzw{5YMO3~EYeo{=w?PISgr>Os-$C98MqO3YQ=_D zZ6B4!kXWf^%Q=@qp0)W zjKmHmV~SUnZO+)c7mo6_6&;D+Uj_dk03|@$zlfmU#q0FPr=8h{?#ZL}^u5Opjckpf zhct>=3?4Ex_4O4$n<%CAA8Pvq#CF_r*_nOUHZf4@)>-vFK0oL_^>U%ZbBwI`m;Ag0$7jAu&()`YF&JpMwc@!d0E@DqpxCaW@+P(j>c)3yhTIpMU$VM zjgbyN<5pHR{qf8k`U+ruX9OwKa?CXMlb_L^>48decy(co*2!bW$v7x4N(Vx0Wc)%k z78&ILkbGm4*w%!`N)2w_Vpa^8jD3V zxE>Ry^NmT+qlXYue*!{w4b!bhm63pYHWVzuObQ=8q>${F>PduuViY45RhwQ_@D@UP6O zN*c4H=V>RdhXD~#Zj?eFw)ze1md=HiQa!^8a0tY#*Syg64`y}mr0Ji>33ilDeE%ofSo0Y%lJAdOJ8Oai688x_C7+ zL1y3(h7L3~$I|z=7({Nq;)pifv5S$D1yBRQySb|b0gt=Z5RD)+0*N#TAUDdJKoxot zM%@TO$w<2M<3K?6X=W2&DF1W}6!1}&qcGOhK=v^yD?zVTccf+lt z4?YG672EyM3NQd4DB_4h%HcAwS$OSy&ibJ>d$fk#tk0KFZFF{xVb6V&GRwug2EvYA z!1Qx>ITd%05E!)X`J%JmB~mlbEN&pAC|fntJUw@eplu)Ky5CgE&5{l@;-P2sG2$|t zd0vBAyO{C;BdP!C6(c6Ul|~`Fkf;&~TwQhuUSw_|XDit%?xl+oIbGp^p}pY&9!Jhm zq+>!NLXjdiZ^hQWkpmX_9l5ElCr4e^(-l<6RFA3+3AUakA>*K&vu{%#NXi2fF={5H zTDV0eL;1BQr=(SgHiA1$a#W~cuK`{36bY0T#oq`DcsnDZ#cte9%qH3`X@)FVHMbZW z#%XY52uFi8NU9Yb#`D;~%JrfJNHkXJ_O)8=fl_Fb&Tb*v#(xes(mL*D#NYddTPK9a zI|iUnN<{VHg=L6a`B^)He58ep)YFd|?_D=P%h37st@$4ldfcAK&pqsOkw;U;lT13= zNt&(!x|29eyV9u+3l;pN>bK>zvvbBQ#o55qs!d)a)o0n*%|4B|U!(}!I#G6MaB9z8 zCl&XN)5jV~eOq)}i!JAq%eF=7(=+RW5S5AQ^_v?abjq!^6lbkZ#2;q6KYnxvA5(|0 zN!UqTXs@l_4@3$bCl*PXYpoR%%^CtGvhn*5dTpzE1axUm!o=w>56ce@UOf9vMvPSi zex6&1x0A`#rhPyjVo@ryoX>9&!rD`#u8vv>h9&+t1Jc;-M2`Sk1*(pjo<{NdBMNla68i<9CmTJWm(WiQS%PZ1*F{ z{bAT_576p*4mih!&?4?$pZ9`NWt-4B0~x|oPbCxsK^sy?1bF;0(tq ztwlj(N-DIXR6WBNp7*8R%!%rw_f97dyReds3``4boBj@n^o?&H+brGT$f4#+*1 zQxMitJz7}noA=E}+GG{@nm!Y#0o@5SS_&p+kCQn|2FM60BJtbd^w;`fYLaM^wb-We z+&=4lMF!?fc&-p+o!q5N-f_?VIy3SnI_Yto4T4)1lO1!-=?*J5j63UlUAwqJM@i#c zIrC_8RT@5HDpd~p2!_B`7-!rr;vS&!)haH%!gTQdBDC@xmbmD&3q=$X0g)Y@1wmwA zC}g}-eUp+SRvJp0<+$=ihaF6DJ`w<6j!H;Kb*roxEWb`^sae(MQe4%Z7k4-4*|asG z?&g%}SJ8b4O;mMiLpOXU_Px7W>TpmU9b$;a!pjReY^af2(Xqu!bc%`eR5dV&;&GN6NK6EP zu%^3jQJ!Tqo|q8*y?EdvtUdefq4GN`7yoQ&Mg9H%`{-1zP&BDjZX3*~E( z8)H`-ZG;*YwgyxuqQ@R@^mV{cbs&t-(kApgeK!8Yq#A#)kWl+1mOZX6o2i>}hmd2M z64UAJK?-zAzU9!>9DFd$>|NKlz@((|!MauS_-0%^kApn(b;Ao?_5AW7t+^_g@zWJyy(-8K}= zswc!mNlBy>NB$N8mq0u_r3M4|m_kD=5xj8IH+x<`kVIlPYTcZbNV7+@Lyd<3T0>Gb zHQSestz{pYWqr^;Fw>jX_|-VbVGFcj5>`U0%+NIMpk7cc;!4I(2U>Mo%BcM5 zt{^@irv(L1?1AwjHw#n>17D{*wY*SnNRE4Q zseC&Z$e=P2Ssy?BfI!pPjPo5LZoF!OW$KE?R3g;KH41|Ee^DNiq+H=$UHRsEh{(1#KTK2ojL*#lQ-dF>yS9d8WO1b^^qzXW%me-t4kW}g9FtoGw! zr;0f2W%CO={IN0kT|yI>=T#1ud5~-ftX5h#y&q2>WYq5Z;<)__Fs%WPhqIOmJ<=KF zs-)8~m|Gx9VZna2@w67XyaghAv*!=_BJu*2 z>}~FvE~@42s29IYF2q!%aX@LqiSYvV3cZ$n`^Bogq8p0D9Q z^Dr(&blFL8slQaQ&kK_CMrR8oRvA!$>je;zA|q3YOJj+Eg&EuA`&$(%dJ4I*k*t7g zt3A_iHC`v%_DvyvbLn5u3*sA70H$E>BaUbwTjG7Q21hmfoq$mc1cxl;4@9d{!6J)v zc-R~}k2_d24>tg6&;cJu`|BdqB7x^gU$w=l6#h6Kp|%s1nh*kC$21yF9Id%o7h?_f z6_lcIQK*bmL;X`fn03mpUVL_=#!GzFH_!efLE!URO4UH*E?x}<=}pRzFCMME8iDKC zy0crjl8j-ZZw+8@Q||Ox9NPdYU0m-|MB}1|zYYtjfxa2$bTT>GL!o$U;;us?wD?Gl z3bmw{M%WAf97eQBc7{XJJb+-E-(p6c!?=WX2mcOPsCqCM(Al8o3IG#yJ%`io>+P)) zk#4;B2-F-<0}TIB6D()7w~XXOY^2?r8U%(EW`4egpul88_b3nE5`A~G27?h&^MOsCL4se7Am0!2H4kE4tdKplz@D%yc%TK`uMrSdBAJbyD$k)>)#BbO?A-P{ zFBM#O$hmJg!r-tBu!DBduidqZ%nX!x4Z9%GA2~UH`cdcm_dm+?Lk{iqvx&owixJ!^ z6SJm+KeCe&haDCpc@U+mdTA)A(4&Y8X%~zz%sf(?T<*$*kR%E%BxK zAznQon80qJc(o(cCM8Ar>&kJLzHT7Wc9OH_ZK2iq`N|yg)^CCN-iGEy1?CT-2ygjk z6_czJ>|g({8z>W?OSkM3iW{muI>eYr1+ozWGP+t9nS4(LfU=kdNl1kaN(0G>O$hnV z6P_C#SAu6#)*g?0?Aa0XX(mV-O|>j2YycT_s!u?X64!Epr?sR9dQ$CEdD-9G-;9N_ zL{kqmo_4ZrB9s31C1;YC8J_+0tfCSc$;*>fan3>xv$&YG)* zHIPe*-`m3$Ir}|^F|X(U*Kl#-lCqIkf^z`Xr9p|@JEIemk85Pb$jkyp2%rf6pC|~R zHw5TI=t+Q7$~il`v4R4+D_~1%gxv9s#mo;xeI4Ud=gC-ukx!+$%j^^ww>SUejZ{CO zTkNgJ?IUFy!W87|ba4I5pD9hJ{vZFfa{l{2{@3UJ!T%h}h5kkVzd9fZKFs_R{?VV+ zMf2>`Q3?H-q!v7!0ivNIrE}s*?ug(5H;l#FDC1Bn( zjKDQoxV1&Ut(zOfEXk_=;5(Q3^Eg&oNie!wykOl=zZ`$bvTc*Z$i6$X6aH68!} zD@2X7bW9sK!&Xh$^RVcWL*Sds#1|v=gExc(48(&5-5@ZLTN*XL8^0kIX&Xe|B{z85 z3ZCh)p+3_%K~ux4U7Y9ipe7(V9iJpoI&H4rwcB*4p%4}v24t|G?gz53^w?b*U}^1K zo05RDCzwIA)P9%DJ*&Zky)G5?TDEl6(hTT!q=?xrVj)nTt-Za)_zplfs~sB73M*K& z{yecueNoUs9n*F$uGlv4fyI;)yn%IGrXbT@URXTfar%H^zv-Xzx7-`Fv(^mY(Gk{f zYX;PCW0e$dH3IQD2Q|UC!QeP)sdd-L)X}q@93v2;X;YKdgH2fxIZb53!c7q;`!Pi{ z)LL`#+ptP^BX~A#>6)yKCMz#tmCV~1qrQ{XaRL<<$B1!NHkE+6(`6;PY3yqNsJ7M$AXH~p zBGqEt(?q8UIaJQ2i!{3;#D-?wWjbf0>8uj>XK*&;PGSX%a$IFLOdXZ@~~9$(lB+f()I#JFU51pqu5Xf&_DW_UB5LZ*`} zisSKekHRr);0SdwnJ8Qh9V-YC_E0xT9ifHR?>=QYc2_X|1M(K&j&EMGS`kIuLiNI& zFun+lA?5Ho*B$C=ZHgyC*+`xG5D7qgcjyPzVb)!B$R*YYj+mgoSQqH$R7TMOpbqBh z2JN=k@0$8KN4B~WDLQJK(JX7r{xG!cBA?w!&9T=; u>(9dms2_UunX`C#Wf$K^h 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

x{oikkGNb6@}P+}4OtQLMI zPAQ#-q}XOUxAJ)f2^;*NBsfIfgwqt))bLnd6--=dyQ13lT37BZLEO@^iN%M z6GLI@>?G#%edPPNcM8Bl!;YK-fQg9p4^>i$K^0u?n32PUN6OCmYo(dxv)uDD^@$iJ zTH+F;nKTX=+B-c}&^aW4-eoLwXFw5EA9QT!?@v*#7uC|*7~GH)U?aGQ6U#0&wo(*y z{VZ6KN(+FAWYbqByahpTwSdz|-m&Nb{Hh3qzk{h=t3XQ^d2SNA#si522R2OXQ>wC) zyaO@FcVZ8MxGf2`lM)(wi6A1Caf7!zj2*ZS zGhu09IaX)v-4|pQkZq3+e?HiqgN- zprL-R3+aIPkWr~fyB84{=@@IJ_T-Q|yQIwNT%h+|jwc2-t$$%;8Gz-sUNfIhoas$&=H^@il5flaU1x1?_2wb{p{>U>JT8!ON3x@U z&QD)q{6`;2Ek})6cZWc2ba$V9L;v!9U?g~o6@L;kV3yOUhHP%YEiN;vmYueZl(q^ zDV21p@Au#g=MsK)#P>y*7MaE!_%6&50f`SDL&nY5ghI%CZ?I;AkNnvK;v(7s9tZOe7Bpdxl18iO;B+TwX1xIeD> zf25U{u`?3f!2dA`_(mwR)`khIK}eOOOldhVO4)%Vy88=A;^s9Hk;c6&t|Ec|^P@PY zCk>jLfg@@BbnVqZ+YWeT-7Im5AiJ&6ygfRz+!{TN1TkML2y4Cn5DssCNjfG_nf|^GU|4#BuYmqCdX14y_a|=Yj}mQ z2awjE!FjZ*1lOnKCkmWLhR3pg=yDJ3N**y~r|E>~(5x+K8i^I(a<4GfgB{DBL43J& zOgj46q3FAwMEX|t(hKy+nsTvuqgX`ZuATKuHXn^iU)f4 z<}wD2()YZPJO4RVBbUQoU&7|S%c%IK3X0y?DCn*(Vk+Q_RFt@8ZLEAq{5&uAAMKF* znLS)A9F?|dMtB~A=IpPt*cORVT;RKOuA*Ff5^Oy~iC7Q*9)Lku zMMhJwEVfs3=9nRc;zP0?oq|UFUm*^uQ$;4vsizwzP?xBcQ zcHW}zUkkiF`upqN%~HM>tMvX|qZw8wfXMfYB^n6If=^=8{w@aN^&ti4m?dd0rvS(c z-lH~99=%}yVhepJB0~z_lrT;9A*}y}y93*I?D_F;s^6gd0^@h=J^UU0>mN1Dv<@qT zgy#!5mQyQkq~40Mag$5`(7q02M4}#+jQWP6b!~S=BAT|PS=Pb(&DT&C+Nl8z_l38b z?OCjsNR5FSK0||a?XU%+3<5`D`475HtASfEW1}OtUCTRW=Z4r8WT!^Mxkipz;=7`j z?>M2ctra}DR2ILAfg~|$m+Dy(E?aWdvO&#|mCpd98>iM)o>*^v_%5veHHF5fPG64T z*yQ+swZZ6XtZ+G_g=(7YyRmVA0J}%w5H9n1Yu+JQAk*O%yXFQA6$W8icH4mCJ8COC z04WNEX<4+I>S1W|YVevP-0@N*&wPe7T}}(Cb zn*e-}7_4;)YjxdM>*X*5oMGANzkx!_mwaY;AkdQPI@~HQNb={Q3lykmEK>1Eh|Kc4 zD0!@?yo5j8p(nMNUOR$rEkv8ihyOD}WVEfDI6}R~yn0%HH|d-Y4kVlNK_U78Kdi&rAn~&u`YSo6fejClpZRM?3ab$`{Zd`aW zD2rX3nt*A$_KvHpFsPwf>j#RNKT@vE9j1sOw|f;JTv`yu^;Sns?d6MOG_WLZBFxtf z)wwUzA#j~Q zvYf@bZXm?Ui^{!MCZqQVWLF@dU+^)uP#>i;dRTKYzIw#e@2~X%1ZD_J3_*!_s92_e zBvA!O`%h4~(rG62OMQX)bK_4TWjEx3NIGO;RExY33_~yV+xj6(1nrXO;+p=Zc{0mh+?vEMa}@V zOE}`_0T_~M%|TTfM3bS^s$N`P&k{_A2}DM=HYWP2h0S5_3{f@f-xuME>#=)T#5x?e z-S?)O8k$H+^3lmFnIjRoXkku+mPF-Ol8ZEpdXC>BteDk*T�$acwQ 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�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 G#@^~4+5fZSzec1 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 literal 984 zcmV;}11J15DhE>=Z3humlzn7>8r~2C)!tD|P78Xb zg4w{n`cl`f)hjV;$)Pa+QQO3#C(eg+4#5NM=ic4@4KdTUgm@^dl8BHk5sF&!z6yc1 z;q_Yxig=Vn0D^cN!jTEYgq(2`!jmk1Zg3g|l+ZE^GU*1}C=(30W85{4dcNw)1Kt6U zu;a?0-uHD{%9dsN!vb8{GT!yI?!w66cd~>FC23^;9{`2b+zSsTu zvM!v^EZS!gN%5)rK>7}3Q-+U9?D_}-Sc;cR?r+u2j5jUMGSO@+1s>Vr4r2oh69Y|T z3+4pOLrJL|8KVYX-U&!`2aPpTt4AHX;b1)|eq60e_* zL3s;BQ!JN(k_raQNrcs7c;o*RY!yNZ^YM=-DTp4EX$>G5I;F&f2QCEu3|vV38KWrB zLR99jk$Tu9)>|%iTNhk9_wH`@=7#oZxwVLUZTD6|s~2ta4T!s~cL4dy7x~^lo(t*C z55wJ^oPO3@V=c?0ZA)z}%{xRZqAmY`Q))_ASIYDr&QnvJ+M}u>D=H$ZqK*K7J3Mb` ze;&T27)tBwOB>4gBoGLky5!UlUJ_PR6jl;`Kp+s9*WeT%TUQs`Pv9SocN%u`JINhC z?Jr3O$DG`f63)jIrtHu02KcW~`v5KEC4l@NhzBq>c z)0yTgGUR^oxCrG}3H*PYOF%Uq+Xsg{BZuFq2nB?~73TNP&a6$bB|C=lQZq?Y>P;Bs zRw~Eyuf*P@fQ5R?R9S9tnSA8|-ZBO*VbsWcARS(8D@%Aga%nNuI$yian|k<$SnDhv zzBq>c)0yTEY~bs`#ziQrG6wk<$|ayb@Tm0=s?cZ<_oEOB5EQOp{&efi+90&vHBL0 zFLt$Hs&&5VcKlFO8cRs5&9N%>Kk;B#X8Y+nw#caHNVSriB5Q3VC|}&f?*C2}E37{p G0001@N5EwO diff --git a/nummi/main/templates/main/form/account.html b/nummi/main/templates/main/form/account.html index b9b94bc..f14da2d 100644 --- a/nummi/main/templates/main/form/account.html +++ b/nummi/main/templates/main/form/account.html @@ -16,7 +16,7 @@ type="text/css" /> {% endblock %} {% block body %} -

{{ form.instance.uicon }} {{ form.instance }}

+

{{ form.instance.icon|remix }}{{ form.instance }}

{% csrf_token %} {{ form }} diff --git a/nummi/main/templates/main/form/category.html b/nummi/main/templates/main/form/category.html index f2ecfb0..27ae5e9 100644 --- a/nummi/main/templates/main/form/category.html +++ b/nummi/main/templates/main/form/category.html @@ -16,7 +16,7 @@ type="text/css" /> {% endblock %} {% block body %} -

{{ form.instance.uicon }} {{ form.instance }}

+

{{ form.instance.icon|remix }}{{ form.instance }}

{% csrf_token %} {{ form }} diff --git a/nummi/main/templates/main/icons/checkmark.svg b/nummi/main/templates/main/icons/checkmark.svg deleted file mode 100644 index 5f87bac..0000000 --- a/nummi/main/templates/main/icons/checkmark.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/nummi/main/templates/main/icons/error.svg b/nummi/main/templates/main/icons/error.svg deleted file mode 100644 index f02b911..0000000 --- a/nummi/main/templates/main/icons/error.svg +++ /dev/null @@ -1 +0,0 @@ -error diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 13d9e3d..57b1d7f 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -4,9 +4,6 @@ {% load i18n %} {% block link %} {{ block.super }} - @@ -20,7 +17,7 @@ {% spaceless %}

{% for acc in accounts %} - {{ acc.uicon }} {{ acc }} + {{ acc.icon|remix }}{{ acc }} {% endfor %}

{% endspaceless %} @@ -36,7 +33,7 @@ {% spaceless %}

{% for cat in categories %} - {{ cat.uicon }} {{ cat }} + {{ cat.icon|remix }}{{ cat }} {% endfor %}

{% endspaceless %} diff --git a/nummi/main/templates/main/plot/category.html b/nummi/main/templates/main/plot/category.html index 3dfb290..71cddaa 100644 --- a/nummi/main/templates/main/plot/category.html +++ b/nummi/main/templates/main/plot/category.html @@ -24,7 +24,7 @@ {% if cat.category %}{{ cat.category__name }}{% endif %} - {% if cat.category %}{{ cat.category__uicon }}{% endif %} + {% if cat.category %}{{ cat.category__icon|remix }}{% endif %} {{ cat.sum_m|pmrvalue }} diff --git a/nummi/main/templates/main/table/snapshot.html b/nummi/main/templates/main/table/snapshot.html index fb622c8..f731f45 100644 --- a/nummi/main/templates/main/table/snapshot.html +++ b/nummi/main/templates/main/table/snapshot.html @@ -17,8 +17,8 @@ - - + {{ "check"|remix }} + {{ "attachment"|remix }} {% translate "Date" %} {% if not account %} {% translate "Account" %} @@ -31,18 +31,18 @@ {% for snap in snapshots %} {% if snap.sum == snap.diff %} - + {{ "check"|remix }} {% else %} - + {{ "close"|remix }} {% endif %} - {% if snap.file %}{% endif %} + {% if snap.file %}{{ "attachment"|remix }}{% endif %} {{ snap.date|date:"Y-m-d" }} {% if not account %} - {{ snap.account.uicon }} + {{ snap.account.icon|remix }} {{ snap.account }} diff --git a/nummi/main/templates/main/table/transaction.html b/nummi/main/templates/main/table/transaction.html index 933bb86..e4611d9 100644 --- a/nummi/main/templates/main/table/transaction.html +++ b/nummi/main/templates/main/table/transaction.html @@ -23,7 +23,7 @@ {% endif %} - + {{ "attachment"|remix }} {% translate "Date" %} {% translate "Name" %} {% translate "Value" %} @@ -39,26 +39,26 @@ {% for trans in transactions %} - {% for invoice in trans.invoices %}{% endfor %} + {% for invoice in trans.invoices %}{{ "attachment"|remix }}{% endfor %} {{ trans.date|date:"Y-m-d" }} {{ trans.name }} {{ trans.value|pmvalue }} - {{ trans.trader|default_if_none:"–" }} + {{ trans.trader|default_if_none:"" }} {% if not category %} {% if trans.category %} - {{ trans.category.uicon }} + {{ trans.category.icon|remix }} {{ trans.category }} {% else %} - – + {% endif %} {% endif %} {% if not account %} - {{ trans.account.uicon }} + {{ trans.account.icon|remix }} {{ trans.account }} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index acdcfe0..6cf7c9a 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -40,3 +40,8 @@ def form_buttons(instance): "adding": instance._state.adding, "del_url": f"del_{instance.__class__.__name__.lower()}", } + + +@register.filter +def remix(icon): + return mark_safe(f"""""") diff --git a/nummi/main/views.py b/nummi/main/views.py index 834053d..c81e778 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -295,7 +295,7 @@ class SnapshotUpdateView(NummiUpdateView): _transactions = snapshot.transaction_set.all() if _transactions: _categories = ( - _transactions.values("category", "category__name", "category__uicon") + _transactions.values("category", "category__name", "category__icon") .annotate( sum=models.Sum("value"), sum_m=models.Sum("value", filter=models.Q(value__lt=0)), From 0ec3fa74b2392e72f8a88f2693a5ca7836e58fcd Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 15:44:05 +0200 Subject: [PATCH 002/276] Set icon field length to 24 --- ..._alter_account_icon_alter_category_icon.py | 24 +++++++++++++++++++ nummi/main/models.py | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 nummi/main/migrations/0028_alter_account_icon_alter_category_icon.py diff --git a/nummi/main/migrations/0028_alter_account_icon_alter_category_icon.py b/nummi/main/migrations/0028_alter_account_icon_alter_category_icon.py new file mode 100644 index 0000000..7e5d4a3 --- /dev/null +++ b/nummi/main/migrations/0028_alter_account_icon_alter_category_icon.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1.4 on 2023-04-19 13:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("main", "0027_remove_account_uicon_remove_category_uicon_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="account", + name="icon", + field=models.SlugField(default="bank", max_length=24, verbose_name="Icon"), + ), + migrations.AlterField( + model_name="category", + name="icon", + field=models.SlugField( + default="folder", max_length=24, verbose_name="Icon" + ), + ), + ] diff --git a/nummi/main/models.py b/nummi/main/models.py index db6144f..ffcd65d 100644 --- a/nummi/main/models.py +++ b/nummi/main/models.py @@ -44,7 +44,7 @@ class Account(CustomModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name")) icon = models.SlugField( - max_length=16, + max_length=24, default="bank", verbose_name=_("Icon"), ) @@ -97,7 +97,7 @@ class Category(CustomModel): max_length=64, default=_("Category"), verbose_name=_("Name") ) icon = models.SlugField( - max_length=16, + max_length=24, default="folder", verbose_name=_("Icon"), ) From f4077456af630ecef517531d84f05ad9cb719d95 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 16:58:40 +0200 Subject: [PATCH 003/276] Fix snapshot page --- nummi/main/templates/main/form/snapshot.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/nummi/main/templates/main/form/snapshot.html b/nummi/main/templates/main/form/snapshot.html index a7420f3..78ebae8 100644 --- a/nummi/main/templates/main/form/snapshot.html +++ b/nummi/main/templates/main/form/snapshot.html @@ -18,11 +18,7 @@ {% block body %} {% with snapshot=form.instance %}

- {% if snapshot.sum == snapshot.diff %} - ✅ - {% else %} - ❌ - {% endif %} + {{ snapshot }}

From 2c9537987079e5c132fdd3e068c440ce3a796db5 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 17:08:03 +0200 Subject: [PATCH 004/276] Raise skip link opacity --- 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 22ea06a..a916006 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -110,7 +110,7 @@ nav ul { position: relative; } nav .skip-link { - opacity: 0.1; + opacity: 0.8; } nav .skip-link:active, nav .skip-link:focus { From 06b51a87ff58c9cc91d8dff4f5d9be240dad25b1 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 17:09:00 +0200 Subject: [PATCH 005/276] Update PKGBUILD --- PKGBUILD | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/PKGBUILD b/PKGBUILD index 94d5107..bad6c31 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,5 +1,5 @@ pkgname=nummi-git -pkgver=r151.ee26d0d +pkgver=r298.2c95379 pkgrel=1 pkgdesc="Web-based accounting interface" arch=("any") @@ -8,7 +8,6 @@ license=('AGPL3') depends=( "gunicorn" "python-django" - "python-matplotlib" "python-toml" "python-psycopg2" ) @@ -24,11 +23,11 @@ source=( "config.toml" ) b2sums=('SKIP' - '9e9b62a141bb4594dc203978d5c56dd397ed6c095e6f83fdea07ded2fa46dd2e052aa8f80729a3b612e3773aa487660fcca791079ad4885b825c9c831df61b45' + 'c1e81c04cb09bf03e6f1f06b1de1a28bf39bf2b1571819071fe95e214847c7712e11ec9988061284fda2a3f565262018ac733603034c4c4aa98a50bc42f6bd95' 'cd42e864d82ca5ea191d2c15cec55afea272a875f19ebc1252e03fb86ef3d62820e2fedd706a8da0b0e3a7aee355e0bb5dcdb7f3bd803497c1320fab540f7d70' '78bebc6cca3f8b520783565de9122cc57e62eb629d3693dfaa322a77f4be3c55c5cd5a460bd1f6f3028f4dd1b2018ec6166b7f9aaf55ac3be6178bd1a3a00405' 'c878c463ecb58d94b3f9b96d798b098a7718aec6df4b424a60674c0a9daf9abcb4556e527e55f67dbb093e109145b0ca17556f7dce66116ba7b170ead61acc16' - '5bdc097ab3fa7e8e128d194bfb6c0fefb5528903e2b8765557a82033f8925c1174dbe73007a595e32c5e415cf6f19957c35bbe463c50d2813894c20c8a28a88d') + 'efe7c0a7da971bbd43675c71bc16bd3d44948cec55fb5c2237196ad52d712f3e958790f171f0fab82e9ea5529a60789685c5936489b4e2ffdb4a0e1b3fcb031c') pkgver() { cd "$pkgname" From 9deba5b28ffe8343441b63d3562e38f6ce3e91db Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 17:19:01 +0200 Subject: [PATCH 006/276] Show file extension --- nummi/main/templates/main/form/fileinput.html | 4 +++- nummi/main/templates/main/table/invoice.html | 2 +- nummi/main/templatetags/main_extras.py | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/nummi/main/templates/main/form/fileinput.html b/nummi/main/templates/main/form/fileinput.html index 7d6d7bc..c9ffd65 100644 --- a/nummi/main/templates/main/form/fileinput.html +++ b/nummi/main/templates/main/form/fileinput.html @@ -1,9 +1,11 @@ +{% load i18n %} +{% load main_extras %} {% if widget.is_initial %} {% if not widget.required %} diff --git a/nummi/main/templates/main/table/invoice.html b/nummi/main/templates/main/table/invoice.html index 86506f1..bc3024b 100644 --- a/nummi/main/templates/main/table/invoice.html +++ b/nummi/main/templates/main/table/invoice.html @@ -18,7 +18,7 @@ {{ invoice.name }} + + {% endblock %} diff --git a/nummi/main/templates/main/form/search.html b/nummi/main/templates/main/form/search.html index 6b71216..ffb1775 100644 --- a/nummi/main/templates/main/form/search.html +++ b/nummi/main/templates/main/form/search.html @@ -1,7 +1,10 @@ {% extends "main/form/base.html" %} {% load i18n %} {% block buttons %} -
- -
+ + + {% endblock %} From a1d48ca2467d714f94c7496ccdde54a7d8e4bc1b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 10:21:18 +0200 Subject: [PATCH 020/276] Remove empty value fill-ins --- nummi/main/templates/main/plot/history.html | 2 +- nummi/main/templatetags/main_extras.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nummi/main/templates/main/plot/history.html b/nummi/main/templates/main/plot/history.html index 4eb48ae..d47af15 100644 --- a/nummi/main/templates/main/plot/history.html +++ b/nummi/main/templates/main/plot/history.html @@ -22,7 +22,7 @@ {% for date in history.data %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index a0dad28..7558403 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -8,7 +8,7 @@ register = template.Library() @register.filter def value(val, pm=False, r=2): if not val: - return mark_safe("–") + return "" _prefix = "" _suffix = " €" _val = formats.number_format(round(val, r), r, use_l10n=True, force_grouping=True) From e3ddf4cbaaabc6302558710e157d381f32e522fe Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 10:22:01 +0200 Subject: [PATCH 021/276] Right-align whole tfoot in forms --- nummi/main/static/main/css/form.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index f40bb62..ae97b3a 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -35,7 +35,7 @@ table.file-input tr :last-child { padding-right: 0; } -.buttons { +form tfoot { text-align: right; } .buttons input { From 3eb40cd5ed318ccdab10f173ba1fab2fb2d82f89 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 11:07:17 +0200 Subject: [PATCH 022/276] Remove addmore checkbox from update forms --- nummi/main/templates/main/form/base.html | 17 ++++++++--------- nummi/main/views.py | 6 +----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html index d4c9652..e0b5b7d 100644 --- a/nummi/main/templates/main/form/base.html +++ b/nummi/main/templates/main/form/base.html @@ -23,15 +23,14 @@ {% block buttons %} - - - + {% if form.instance.adding %} + + + + {% endif %} - {% block buttons %} - {% if form.instance.adding %} - - - - {% endif %} - - + - - {% endblock %} + {% endblock %} + +
{{ widget.initial_text }} - Fichier + {% translate "File" %} [{{ widget.value|extension }}]
- {% translate "File" %} + {% translate "File" %} [{{ invoice.file|extension }}] {% translate "Delete" %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 6cf7c9a..b1b67ff 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -45,3 +45,8 @@ def form_buttons(instance): @register.filter def remix(icon): return mark_safe(f"""""") + + +@register.filter +def extension(file): + return file.name.split(".")[-1].upper() From b98743d88b38e11c57b66d81f4595f91a51180da Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 17:25:33 +0200 Subject: [PATCH 007/276] Remove footer --- nummi/main/templates/main/base.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 3eeae87..c9550e6 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -91,8 +91,5 @@
{% block body %}{% endblock %}
-
- {% block footer %}© 2023 Edgar P. Burkhart – Nummi{% endblock %} -
From 9db123457fac8a302358e89e08af39c4abc57a32 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 17:27:12 +0200 Subject: [PATCH 008/276] Change skip link font weight --- nummi/main/static/main/css/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index a916006..98e1e98 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -111,6 +111,7 @@ nav ul { } nav .skip-link { opacity: 0.8; + font-weight: 300; } nav .skip-link:active, nav .skip-link:focus { From 5534a4352dcf725478ff7ab857f44b00b948d62e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 18:35:57 +0200 Subject: [PATCH 009/276] Add trend to history chart --- nummi/main/templates/main/plot/history.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nummi/main/templates/main/plot/history.html b/nummi/main/templates/main/plot/history.html index 73c80d4..4eb48ae 100644 --- a/nummi/main/templates/main/plot/history.html +++ b/nummi/main/templates/main/plot/history.html @@ -3,6 +3,7 @@
+ @@ -10,6 +11,7 @@ + @@ -19,6 +21,9 @@ {% spaceless %} {% for date in history.data %} + + + + {% endfor %} - - - - - - + {% block buttons %} + + + + + - + + + {% endblock %}
{{ "expand-up-down"|remix }} {% translate "Month" %} {% translate "Expenses" %} {% translate "Income" %}
+ + {{ date.month|date:"Y-m" }} {{ date.sum_m|pmrvalue }} From 917531aa9757bc16014b54d7c693354ae2b77c5f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 18:59:37 +0200 Subject: [PATCH 010/276] Add history function to view --- nummi/main/views.py | 80 ++++++++++++++------------------------------- 1 file changed, 24 insertions(+), 56 deletions(-) diff --git a/nummi/main/views.py b/nummi/main/views.py index c81e778..806b473 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -34,6 +34,27 @@ from .forms import ( from .models import Account, Category, Invoice, Snapshot, Transaction +def history(transaction_set): + _history = ( + transaction_set.values(month=models.functions.TruncMonth("date")) + .annotate( + sum_p=models.Sum("value", filter=models.Q(value__gt=0)), + sum_m=models.Sum("value", filter=models.Q(value__lt=0)), + sum=models.Sum("value"), + ) + .order_by("-month") + ) + return { + "data": _history, + "max": max( + _history.aggregate( + max=models.Max("sum_p", default=0), + min=-models.Min("sum_m", default=0), + ).values(), + ), + } + + class IndexView(LoginRequiredMixin, TemplateView): template_name = "main/index.html" @@ -41,31 +62,13 @@ class IndexView(LoginRequiredMixin, TemplateView): _max = 8 _transactions = Transaction.objects.filter(user=self.request.user) _snapshots = Snapshot.objects.filter(user=self.request.user) - _history = ( - _transactions.filter(category__budget=True) - .values(month=models.functions.TruncMonth("date")) - .annotate( - sum_p=models.Sum("value", filter=models.Q(value__gt=0)), - sum_m=models.Sum("value", filter=models.Q(value__lt=0)), - sum=models.Sum("value"), - ) - .order_by("-month") - ) res = { "accounts": Account.objects.filter(user=self.request.user), "transactions": _transactions[:_max], "categories": Category.objects.filter(user=self.request.user), "snapshots": _snapshots[:_max], - "history": { - "data": _history, - "max": max( - _history.aggregate( - max=models.Max("sum_p"), - min=-models.Min("sum_m"), - ).values(), - ), - }, + "history": history(_transactions.filter(category__budget=True)), } if _transactions.count() > _max: res["transactions_url"] = reverse_lazy("transactions") @@ -194,31 +197,13 @@ class AccountUpdateView(NummiUpdateView): "account_snapshots", args=(account.pk,) ) - _history = ( - account.transaction_set.values(month=models.functions.TruncMonth("date")) - .annotate( - sum_p=models.Sum("value", filter=models.Q(value__gt=0)), - sum_m=models.Sum("value", filter=models.Q(value__lt=0)), - sum=models.Sum("value"), - ) - .order_by("-month") - ) - return data | { "transactions": _transactions[:8], "new_snapshot_url": reverse_lazy( "snapshot", kwargs={"account": account.pk} ), "snapshots": _snapshots[:8], - "history": { - "data": _history, - "max": max( - _history.aggregate( - max=models.Max("sum_p", default=0), - min=-models.Min("sum_m", default=0), - ).values(), - ), - }, + "history": history(account.transaction_set), } @@ -256,30 +241,13 @@ class CategoryUpdateView(NummiUpdateView): def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) category = data["form"].instance - _history = ( - category.transaction_set.values(month=models.functions.TruncMonth("date")) - .annotate( - sum_p=models.Sum("value", filter=models.Q(value__gt=0)), - sum_m=models.Sum("value", filter=models.Q(value__lt=0)), - sum=models.Sum("value"), - ) - .order_by("-month") - ) return data | { "transactions": category.transaction_set.all()[:8], "transactions_url": reverse_lazy( "category_transactions", args=(category.pk,) ), - "history": { - "data": _history, - "max": max( - _history.aggregate( - max=models.Max("sum_p", default=0), - min=-models.Min("sum_m", default=0), - ).values(), - ), - }, + "history": history(category.transaction_set), } From 5a0958d52ce191af279aeb37e587aa2146623484 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 19 Apr 2023 21:43:43 +0200 Subject: [PATCH 011/276] Fill empty months in history --- nummi/main/views.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/nummi/main/views.py b/nummi/main/views.py index 806b473..552d6a5 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -34,18 +34,37 @@ from .forms import ( from .models import Account, Category, Invoice, Snapshot, Transaction +class GenerateMonth(models.Func): + function = "generate_series" + template = "%(function)s(%(expressions)s, '1 month')::date" + + def history(transaction_set): - _history = ( - transaction_set.values(month=models.functions.TruncMonth("date")) - .annotate( - sum_p=models.Sum("value", filter=models.Q(value__gt=0)), - sum_m=models.Sum("value", filter=models.Q(value__lt=0)), - sum=models.Sum("value"), + _transaction_month = transaction_set.values( + month=models.functions.TruncMonth("date") + ).order_by("-date") + _months = ( + transaction_set.values( + month=GenerateMonth( + _transaction_month.last()["month"], + models.functions.Now(output_field=models.DateField()), + ) + ) + .annotate(sum_m=models.Value(0), sum_p=models.Value(0), sum=models.Value(0)) + .difference( + _transaction_month.annotate( + sum_m=models.Value(0), sum_p=models.Value(0), sum=models.Value(0) + ) ) - .order_by("-month") ) + _history = _transaction_month.annotate( + sum_p=models.Sum("value", filter=models.Q(value__gt=0)), + sum_m=models.Sum("value", filter=models.Q(value__lt=0)), + sum=models.Sum("value"), + ).order_by("-month") + return { - "data": _history, + "data": _history.union(_months).order_by("-month"), "max": max( _history.aggregate( max=models.Max("sum_p", default=0), From 4792388ccdd852044dce75a416687905d75bb5b1 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 09:19:15 +0200 Subject: [PATCH 012/276] Move function definition to utils --- nummi/main/models.py | 23 +++++++------------- nummi/main/utils.py | 52 ++++++++++++++++++++++++++++++++++++++++++++ nummi/main/views.py | 41 +--------------------------------- 3 files changed, 61 insertions(+), 55 deletions(-) create mode 100644 nummi/main/utils.py diff --git a/nummi/main/models.py b/nummi/main/models.py index ffcd65d..3b0ed62 100644 --- a/nummi/main/models.py +++ b/nummi/main/models.py @@ -9,6 +9,8 @@ from django.urls import reverse from django.utils.text import format_lazy from django.utils.translation import gettext_lazy as _ +from .utils import get_path + class UserModel(models.Model): user = models.ForeignKey( @@ -22,7 +24,7 @@ class UserModel(models.Model): abstract = True -class CustomModel(UserModel): +class NummiModel(UserModel): @property def adding(self): return self._state.adding @@ -31,16 +33,7 @@ class CustomModel(UserModel): abstract = True -def get_path(instance, filename): - return pathlib.Path( - "user", - str(instance.user.username), - instance._meta.model_name, - str(instance.pk), - ).with_suffix(".pdf") - - -class Account(CustomModel): +class Account(NummiModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name")) icon = models.SlugField( @@ -80,7 +73,7 @@ class Account(CustomModel): verbose_name_plural = _("Accounts") -class AccountModel(CustomModel): +class AccountModel(NummiModel): account = models.ForeignKey( Account, on_delete=models.CASCADE, @@ -91,7 +84,7 @@ class AccountModel(CustomModel): abstract = True -class Category(CustomModel): +class Category(NummiModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=64, default=_("Category"), verbose_name=_("Name") @@ -202,7 +195,7 @@ class Snapshot(AccountModel): verbose_name_plural = _("Statements") -class Transaction(CustomModel): +class Transaction(NummiModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=256, default=_("Transaction"), verbose_name=_("Name") @@ -272,7 +265,7 @@ class Transaction(CustomModel): verbose_name_plural = _("Transactions") -class Invoice(CustomModel): +class Invoice(NummiModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=256, default=_("Invoice"), verbose_name=_("Name") diff --git a/nummi/main/utils.py b/nummi/main/utils.py new file mode 100644 index 0000000..f034df6 --- /dev/null +++ b/nummi/main/utils.py @@ -0,0 +1,52 @@ +import pathlib + +from django.db import models +from django.db.models import Func, Max, Min, Q, Sum, Value +from django.db.models.functions import Now, TruncMonth + + +def get_path(instance, filename): + return pathlib.Path( + "user", + str(instance.user.username), + instance._meta.model_name, + str(instance.pk), + ).with_suffix(".pdf") + + +class GenerateMonth(Func): + function = "generate_series" + template = "%(function)s(%(expressions)s, '1 month')::date" + + +def history(transaction_set): + _transaction_month = transaction_set.values(month=TruncMonth("date")).order_by( + "-date" + ) + _months = ( + transaction_set.values( + month=GenerateMonth( + _transaction_month.last()["month"], + Now(output_field=models.DateField()), + ) + ) + .annotate(sum_m=Value(0), sum_p=Value(0), sum=Value(0)) + .difference( + _transaction_month.annotate(sum_m=Value(0), sum_p=Value(0), sum=Value(0)) + ) + ) + _history = _transaction_month.annotate( + sum_p=Sum("value", filter=Q(value__gt=0)), + sum_m=Sum("value", filter=Q(value__lt=0)), + sum=Sum("value"), + ).order_by("-month") + + return { + "data": _history.union(_months).order_by("-month"), + "max": max( + _history.aggregate( + max=Max("sum_p", default=0), + min=-Min("sum_m", default=0), + ).values(), + ), + } diff --git a/nummi/main/views.py b/nummi/main/views.py index 552d6a5..40f5276 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -32,46 +32,7 @@ from .forms import ( TransactionForm, ) from .models import Account, Category, Invoice, Snapshot, Transaction - - -class GenerateMonth(models.Func): - function = "generate_series" - template = "%(function)s(%(expressions)s, '1 month')::date" - - -def history(transaction_set): - _transaction_month = transaction_set.values( - month=models.functions.TruncMonth("date") - ).order_by("-date") - _months = ( - transaction_set.values( - month=GenerateMonth( - _transaction_month.last()["month"], - models.functions.Now(output_field=models.DateField()), - ) - ) - .annotate(sum_m=models.Value(0), sum_p=models.Value(0), sum=models.Value(0)) - .difference( - _transaction_month.annotate( - sum_m=models.Value(0), sum_p=models.Value(0), sum=models.Value(0) - ) - ) - ) - _history = _transaction_month.annotate( - sum_p=models.Sum("value", filter=models.Q(value__gt=0)), - sum_m=models.Sum("value", filter=models.Q(value__lt=0)), - sum=models.Sum("value"), - ).order_by("-month") - - return { - "data": _history.union(_months).order_by("-month"), - "max": max( - _history.aggregate( - max=models.Max("sum_p", default=0), - min=-models.Min("sum_m", default=0), - ).values(), - ), - } +from .utils import history class IndexView(LoginRequiredMixin, TemplateView): From 492e6e77d162901ff7bd618d43ab6af07289fe73 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 09:23:30 +0200 Subject: [PATCH 013/276] Update __str__ functions --- nummi/main/models.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nummi/main/models.py b/nummi/main/models.py index 3b0ed62..d967eda 100644 --- a/nummi/main/models.py +++ b/nummi/main/models.py @@ -153,7 +153,7 @@ class Snapshot(AccountModel): def __str__(self): desc = _("%(date)s statement") % {"date": self.date} if hasattr(self, "account"): - return f"{desc} – {self.account}" + return f"{desc} – {self.account}" return desc def save(self, *args, **kwargs): @@ -243,7 +243,7 @@ class Transaction(NummiModel): self.snapshot.update_sum() def __str__(self): - return f"{self.date} – {self.name}" + return f"{self.name}" def get_absolute_url(self): return reverse("transaction", kwargs={"pk": self.pk}) @@ -288,8 +288,6 @@ class Invoice(NummiModel): super().save(*args, **kwargs) def __str__(self): - if hasattr(self, "transaction"): - return str(format_lazy("{} – {}", self.name, self.transaction.name)) return str(self.name) def delete(self, *args, **kwargs): From 3ca027afc596e9b4f9f0a589f9c81773bc4eb1ed Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 09:39:24 +0200 Subject: [PATCH 014/276] Update invoice form --- nummi/main/templates/main/form/invoice.html | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/nummi/main/templates/main/form/invoice.html b/nummi/main/templates/main/form/invoice.html index a0fb629..26c9dd1 100644 --- a/nummi/main/templates/main/form/invoice.html +++ b/nummi/main/templates/main/form/invoice.html @@ -13,11 +13,16 @@ type="text/css" /> {% endblock %} {% block body %} -

{{ form.instance }}

- {% spaceless %} - - {% csrf_token %} - {{ form }} - - {% endspaceless %} + {% with invoice=form.instance %} +

{{ invoice }}

+

+ {{ invoice.transaction }} +

+ {% spaceless %} +
+ {% csrf_token %} + {{ form }} +
+ {% endspaceless %} + {% endwith %} {% endblock %} From 306a1e06c2cc33cfdb86b582c6c30e76bb8a583a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 09:39:40 +0200 Subject: [PATCH 015/276] Add checkbox 'Add more' to frontend --- nummi/main/templates/main/form/base.html | 9 +++++++++ nummi/main/templates/main/tag/form_buttons.html | 9 --------- nummi/main/templatetags/main_extras.py | 9 --------- 3 files changed, 9 insertions(+), 18 deletions(-) delete mode 100644 nummi/main/templates/main/tag/form_buttons.html diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html index 81b9cd7..e2cc2e4 100644 --- a/nummi/main/templates/main/form/base.html +++ b/nummi/main/templates/main/form/base.html @@ -22,6 +22,15 @@ {% endfor %}
+ + +
{% block buttons %} diff --git a/nummi/main/templates/main/tag/form_buttons.html b/nummi/main/templates/main/tag/form_buttons.html deleted file mode 100644 index 707ece3..0000000 --- a/nummi/main/templates/main/tag/form_buttons.html +++ /dev/null @@ -1,9 +0,0 @@ -{% load i18n %} -
- {% if not adding %} - {% translate "Delete" as del %} - {{ del }} - {% endif %} - - -
diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index b1b67ff..a0dad28 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -33,15 +33,6 @@ def pmrvalue(val): return value(val, True, r=0) -@register.inclusion_tag("main/tag/form_buttons.html") -def form_buttons(instance): - return { - "instance": instance, - "adding": instance._state.adding, - "del_url": f"del_{instance.__class__.__name__.lower()}", - } - - @register.filter def remix(icon): return mark_safe(f"""""") From 821d0d290ab3ea636785a2bd79cdfe562d92ef59 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 09:40:39 +0200 Subject: [PATCH 016/276] Update translations --- nummi/main/locale/fr/LC_MESSAGES/django.mo | Bin 3100 -> 3165 bytes nummi/main/locale/fr/LC_MESSAGES/django.po | 124 +++++++++++---------- 2 files changed, 64 insertions(+), 60 deletions(-) diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.mo b/nummi/main/locale/fr/LC_MESSAGES/django.mo index 8ff91e2a7db7e78921af685230045cc478b0bd24..e5a3e89157d0fcbae4c3c2a4fdad94383cd015e0 100644 GIT binary patch delta 1375 zcmYk+OKeP09LMoLGt-*UR#jUwMNMl|AA>=uM`apCBwh>Aq?^)2qiv*<)O2BW6FU}N z5RuS@vZ)mznn-MYI|Q7oa7@|8#jmOyn_@A)RT4Y@4CP47EU?Xq5epkQEj)GPEL zV^|-m<5Q@K3}G3LdH%fT$FZF9>$n7OqbB?itMLgg$M>iSd_#S}Pt-Ue3d4+VQ64C> z3RJ@yRK-?QhwZ3|WWD^Lmmf#9JL&IFV=ei!s0m+0eaKbRfD^b3Z(A2B`Yi^q`8jPy^pX zeesM}c!6B@#{G=?nSMnL6y>J(wWt+oM4f>aRJ|@_hYN#m$N!Tipz~+Dh>e6gSWPq& zO8QSw=US>ZVjHoR*g{P~+4qwKfwN2WJV$wD&C3Idk6D@>JyOMr>>j-V7QaiDQ&>8rb_-rj#idaEx zAhaC~L@_lMA8_I2NF-g_Ka$H24vgMUN8W|1yK^J?p^>qH(d5jHWdCq(EYF)zG!gm> DHtb>I delta 1314 zcmX}sOKeP07{KwPooQz}>d{dK(-v)2Umk5ynl{ozun@74SdgY03yYG74KfK~F^NRd zOk&}+!J@I6T{l*QU?CzxQc2Zc(&()#~7hMhR}d%ti?R0unm`CFAn1f*5LW*C9I=dMBA6p{CEwgf1@V6Z+5x z526zp#>IFbmXE~pIM!1?flKiMI^nCh0M_Wa3$q^=!B1>1Dr$$JcU_2i;Y;sJsk5LEO=v<{qcc6 zkh<_2bC_V%QEWo*kD>QZpc9)wCv*<^ge!bG@HMnuDc0Xa+ux6tqYu;Uzgzh%HhhbA zoI@Y{C6<4p6PSX;Yu#X21Vo( zCZo5}pXC%f(34pI8r_ju^z1*O?G})u4TI!vayjXQ+sR6-2F`2Ae?R&um|!=H}fd$FF} zLT)GBr9RT|EM2`vw3+N8-JPwZ;Z|=TTS(8p61`mXkX0lb#Zj=dZZ7\n" "Language-Team: edpibu \n" @@ -21,143 +21,144 @@ msgstr "" msgid "Add transactions" msgstr "Ajouter des transactions" -#: .\forms.py:108 .\templates\main\base.html:87 +#: .\forms.py:108 .\templates\main\base.html:76 #: .\templates\main\form\search.html:5 .\templates\main\list\transaction.html:9 #: .\templates\main\list\transaction.html:26 .\templates\main\search.html:6 #: .\templates\main\search.html:18 msgid "Search" msgstr "Rechercher" -#: .\models.py:17 +#: .\models.py:19 msgid "User" msgstr "Utilisateur" -#: .\models.py:45 .\models.py:79 .\models.py:87 .\models.py:237 -#: .\templates\main\table\snapshot.html:28 -#: .\templates\main\table\transaction.html:37 +#: .\models.py:38 .\models.py:72 .\models.py:80 .\models.py:230 +#: .\templates\main\table\snapshot.html:24 +#: .\templates\main\table\transaction.html:35 msgid "Account" msgstr "Compte" -#: .\models.py:45 .\models.py:97 .\models.py:208 .\models.py:278 +#: .\models.py:38 .\models.py:90 .\models.py:201 .\models.py:271 #: .\templates\main\table\invoice.html:9 -#: .\templates\main\table\transaction.html:30 +#: .\templates\main\table\transaction.html:28 msgid "Name" msgstr "Nom" -#: .\models.py:49 .\models.py:102 +#: .\models.py:42 .\models.py:95 msgid "Icon" msgstr "Icône" -#: .\models.py:51 +#: .\models.py:44 msgid "Default" msgstr "Défaut" -#: .\models.py:80 .\templates\main\index.html:19 +#: .\models.py:73 .\templates\main\index.html:16 msgid "Accounts" msgstr "Comptes" -#: .\models.py:97 .\models.py:121 .\models.py:227 +#: .\models.py:90 .\models.py:114 .\models.py:220 #: .\templates\main\plot\category.html:14 -#: .\templates\main\table\transaction.html:34 +#: .\templates\main\table\transaction.html:32 msgid "Category" msgstr "Catégorie" -#: .\models.py:104 +#: .\models.py:97 msgid "Budget" msgstr "Budget" -#: .\models.py:122 .\templates\main\form\snapshot.html:35 -#: .\templates\main\index.html:35 +#: .\models.py:115 .\templates\main\form\snapshot.html:29 +#: .\templates\main\index.html:32 msgid "Categories" msgstr "Catégories" -#: .\models.py:127 +#: .\models.py:120 msgid "End date" msgstr "Date de fin" -#: .\models.py:129 +#: .\models.py:122 msgid "Start date" msgstr "Date de début" -#: .\models.py:132 +#: .\models.py:125 msgid "End value" msgstr "Valeur de fin" -#: .\models.py:135 +#: .\models.py:128 msgid "Start value" msgstr "Valeur de début" -#: .\models.py:141 .\templates\main\table\snapshot.html:31 +#: .\models.py:134 .\templates\main\table\snapshot.html:27 msgid "Difference" msgstr "Différence" -#: .\models.py:148 +#: .\models.py:141 msgid "Transaction difference" msgstr "Différence des transactions" -#: .\models.py:154 .\models.py:283 .\templates\main\table\invoice.html:10 +#: .\models.py:147 .\models.py:276 .\templates\main\form\fileinput.html:8 +#: .\templates\main\table\invoice.html:10 #: .\templates\main\table\invoice.html:21 msgid "File" msgstr "Fichier" -#: .\models.py:161 +#: .\models.py:154 #, python-format msgid "%(date)s statement" msgstr "Relevé du %(date)s" -#: .\models.py:201 .\models.py:232 +#: .\models.py:194 .\models.py:225 msgid "Statement" msgstr "Relevé" -#: .\models.py:202 .\templates\main\form\account.html:27 +#: .\models.py:195 .\templates\main\form\account.html:25 #: .\templates\main\list\snapshot.html:6 .\templates\main\list\snapshot.html:20 msgid "Statements" msgstr "Relevés" -#: .\models.py:208 .\models.py:271 +#: .\models.py:201 .\models.py:264 msgid "Transaction" msgstr "Transaction" -#: .\models.py:210 +#: .\models.py:203 msgid "Description" msgstr "Description" -#: .\models.py:212 .\templates\main\table\snapshot.html:30 -#: .\templates\main\table\transaction.html:31 +#: .\models.py:205 .\templates\main\table\snapshot.html:26 +#: .\templates\main\table\transaction.html:29 msgid "Value" msgstr "Valeur" -#: .\models.py:214 .\templates\main\table\snapshot.html:26 -#: .\templates\main\table\transaction.html:29 +#: .\models.py:207 .\templates\main\table\snapshot.html:22 +#: .\templates\main\table\transaction.html:27 msgid "Date" msgstr "Date" -#: .\models.py:215 +#: .\models.py:208 msgid "Real date" msgstr "Date réelle" -#: .\models.py:217 .\templates\main\table\transaction.html:32 +#: .\models.py:210 .\templates\main\table\transaction.html:30 msgid "Trader" msgstr "Commerçant" -#: .\models.py:220 +#: .\models.py:213 msgid "Payment" msgstr "Paiement" -#: .\models.py:272 .\templates\main\base.html:52 -#: .\templates\main\form\account.html:31 .\templates\main\form\category.html:27 -#: .\templates\main\form\snapshot.html:39 .\templates\main\index.html:29 +#: .\models.py:265 .\templates\main\base.html:50 +#: .\templates\main\form\account.html:29 .\templates\main\form\category.html:25 +#: .\templates\main\form\snapshot.html:33 .\templates\main\index.html:26 #: .\templates\main\list\transaction.html:6 #: .\templates\main\list\transaction.html:23 -#: .\templates\main\table\snapshot.html:32 +#: .\templates\main\table\snapshot.html:28 msgid "Transactions" msgstr "Transactions" -#: .\models.py:278 .\models.py:317 +#: .\models.py:271 .\models.py:308 msgid "Invoice" msgstr "Facture" -#: .\models.py:318 .\templates\main\form\transaction.html:24 +#: .\models.py:309 .\templates\main\form\transaction.html:24 msgid "Invoices" msgstr "Factures" @@ -165,35 +166,35 @@ msgstr "Factures" msgid "Skip to main content" msgstr "Aller au contenu principal" -#: .\templates\main\base.html:40 +#: .\templates\main\base.html:39 msgid "Home" msgstr "Accueil" -#: .\templates\main\base.html:46 .\templates\main\index.html:45 +#: .\templates\main\base.html:44 .\templates\main\index.html:42 msgid "Snapshots" msgstr "Relevés" -#: .\templates\main\base.html:59 +#: .\templates\main\base.html:56 msgid "New account" msgstr "Nouveau compte" -#: .\templates\main\base.html:66 +#: .\templates\main\base.html:61 msgid "New snapshot" msgstr "Nouveau relevé" -#: .\templates\main\base.html:73 +#: .\templates\main\base.html:66 msgid "New category" msgstr "Nouvelle catégorie" -#: .\templates\main\base.html:80 .\templates\main\table\transaction.html:5 +#: .\templates\main\base.html:71 .\templates\main\table\transaction.html:5 msgid "New transaction" msgstr "Nouvelle transaction" -#: .\templates\main\base.html:91 +#: .\templates\main\base.html:79 msgid "Log out" msgstr "Se déconnecter" -#: .\templates\main\base.html:96 .\templates\main\form\login.html:7 +#: .\templates\main\base.html:84 .\templates\main\form\login.html:7 msgid "Log in" msgstr "Se connecter" @@ -210,22 +211,25 @@ msgstr "Annuler" msgid "Confirm" msgstr "Confirmer" -#: .\templates\main\form\account.html:33 .\templates\main\form\category.html:29 -#: .\templates\main\index.html:31 +#: .\templates\main\form\account.html:31 .\templates\main\form\category.html:27 +#: .\templates\main\index.html:28 msgid "History" msgstr "Historique" -#: .\templates\main\form\base.html:29 .\templates\main\table\invoice.html:11 +#: .\templates\main\form\base.html:27 +msgid "Add another" +msgstr "Continuer à ajouter" + +#: .\templates\main\form\base.html:38 .\templates\main\table\invoice.html:11 #: .\templates\main\table\invoice.html:24 -#: .\templates\main\tag\form_buttons.html:4 msgid "Delete" msgstr "Supprimer" -#: .\templates\main\form\base.html:31 +#: .\templates\main\form\base.html:40 msgid "Reset" msgstr "Réinitialiser" -#: .\templates\main\form\base.html:32 .\templates\main\tag\form_buttons.html:8 +#: .\templates\main\form\base.html:41 msgid "Save" msgstr "Enregistrer" @@ -241,15 +245,15 @@ msgstr "Aucune transaction à afficher" msgid "Log In" msgstr "Se connecter" -#: .\templates\main\plot\category.html:15 .\templates\main\plot\history.html:14 +#: .\templates\main\plot\category.html:15 .\templates\main\plot\history.html:16 msgid "Expenses" msgstr "Dépenses" -#: .\templates\main\plot\category.html:16 .\templates\main\plot\history.html:15 +#: .\templates\main\plot\category.html:16 .\templates\main\plot\history.html:17 msgid "Income" msgstr "Revenus" -#: .\templates\main\plot\history.html:13 +#: .\templates\main\plot\history.html:15 msgid "Month" msgstr "Mois" @@ -265,11 +269,11 @@ msgstr "Nouvelle facture" msgid "New statement" msgstr "Nouveau relevé" -#: .\templates\main\table\snapshot.html:68 +#: .\templates\main\table\snapshot.html:60 msgid "View all statements" msgstr "Voir tous les relevés" -#: .\templates\main\table\transaction.html:83 +#: .\templates\main\table\transaction.html:73 msgid "View all transactions" msgstr "Voir toutes les transactions" From 4a1d7778df8792d72f5199dac714bbf6b1accfae Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 10:07:53 +0200 Subject: [PATCH 017/276] Add backend for Add more checkbox --- nummi/main/models.py | 15 +++++++++++++++ nummi/main/views.py | 13 ++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/nummi/main/models.py b/nummi/main/models.py index d967eda..1d77de0 100644 --- a/nummi/main/models.py +++ b/nummi/main/models.py @@ -53,6 +53,9 @@ class Account(NummiModel): def __str__(self): return str(self.name) + def get_create_url(self): + return reverse("account") + def get_absolute_url(self): return reverse("account", kwargs={"pk": self.pk}) @@ -99,6 +102,9 @@ class Category(NummiModel): def __str__(self): return str(self.name) + def get_create_url(self): + return reverse("category") + def get_absolute_url(self): return reverse("category", kwargs={"pk": self.pk}) @@ -183,6 +189,9 @@ class Snapshot(AccountModel): self.file.delete() super().delete(*args, **kwargs) + def get_create_url(self): + return reverse("snapshot") + def get_absolute_url(self): return reverse("snapshot", kwargs={"pk": self.pk}) @@ -245,6 +254,9 @@ class Transaction(NummiModel): def __str__(self): return f"{self.name}" + def get_create_url(self): + return reverse("transaction") + def get_absolute_url(self): return reverse("transaction", kwargs={"pk": self.pk}) @@ -294,6 +306,9 @@ class Invoice(NummiModel): self.file.delete() super().delete(*args, **kwargs) + def get_create_url(self): + return reverse("invoice", kwargs={"transaction_pk": self.transaction.pk}) + def get_absolute_url(self): return reverse( "invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk} diff --git a/nummi/main/views.py b/nummi/main/views.py index 40f5276..3fc87d9 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -9,7 +9,7 @@ from django.contrib.postgres.search import ( ) from django.core.exceptions import PermissionDenied from django.db import models -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect from django.urls import reverse_lazy from django.views import View @@ -73,11 +73,18 @@ class UserFormMixin: class NummiCreateView(UserMixin, UserFormMixin, CreateView): def form_valid(self, form): form.instance.user = self.request.user - return super().form_valid(form) + _res = super().form_valid(form) + if form.data["addmore"]: + return HttpResponseRedirect(self.object.get_create_url()) + return _res class NummiUpdateView(UserMixin, UserFormMixin, UpdateView): - pass + def form_valid(self, form): + _res = super().form_valid(form) + if form.data["addmore"]: + return HttpResponseRedirect(self.object.get_create_url()) + return _res class NummiDeleteView(UserMixin, DeleteView): From 751ddba4638d65cbc1d5077c817b4328eac84908 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 10:11:14 +0200 Subject: [PATCH 018/276] Fix empty account bug --- nummi/main/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nummi/main/utils.py b/nummi/main/utils.py index f034df6..cbb3d7a 100644 --- a/nummi/main/utils.py +++ b/nummi/main/utils.py @@ -20,6 +20,8 @@ class GenerateMonth(Func): def history(transaction_set): + if not transaction_set.exists(): + return None _transaction_month = transaction_set.values(month=TruncMonth("date")).order_by( "-date" ) From c77b88c2f74420c9b74821683330829624be55b0 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 10:17:40 +0200 Subject: [PATCH 019/276] Fix add more checkbox in login and search form --- nummi/main/templates/main/form/base.html | 32 +++++++++++----------- nummi/main/templates/main/form/login.html | 12 ++++---- nummi/main/templates/main/form/search.html | 9 ++++-- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html index e2cc2e4..d4c9652 100644 --- a/nummi/main/templates/main/form/base.html +++ b/nummi/main/templates/main/form/base.html @@ -21,27 +21,27 @@
- - -
- {% block buttons %} +
+ + +
{% if not form.instance.adding %} {% translate "Delete" %} {% endif %} - {% endblock %} -
{% endblock %} diff --git a/nummi/main/templates/main/form/login.html b/nummi/main/templates/main/form/login.html index 1dcd88e..4e344c7 100644 --- a/nummi/main/templates/main/form/login.html +++ b/nummi/main/templates/main/form/login.html @@ -1,9 +1,11 @@ {% extends "main/form/base.html" %} {% load i18n %} {% block buttons %} -
- - - -
+
+ + + +
+ + +
- + {{ date.month|date:"Y-m" }} {{ date.sum_m|pmrvalue }}
- - -
+ + +
{% if not form.instance.adding %} diff --git a/nummi/main/views.py b/nummi/main/views.py index 3fc87d9..3c465fc 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -80,11 +80,7 @@ class NummiCreateView(UserMixin, UserFormMixin, CreateView): class NummiUpdateView(UserMixin, UserFormMixin, UpdateView): - def form_valid(self, form): - _res = super().form_valid(form) - if form.data["addmore"]: - return HttpResponseRedirect(self.object.get_create_url()) - return _res + pass class NummiDeleteView(UserMixin, DeleteView): From 9263af0d40d2522bbf15af47d4d62d4ade49eb58 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 11:11:06 +0200 Subject: [PATCH 023/276] Create view returns to create another ; remove checkbox --- nummi/main/templates/main/form/base.html | 20 ++++++-------------- nummi/main/templates/main/form/login.html | 10 +++------- nummi/main/templates/main/form/search.html | 8 ++------ nummi/main/views.py | 8 ++++---- 4 files changed, 15 insertions(+), 31 deletions(-) diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html index e0b5b7d..edb30f4 100644 --- a/nummi/main/templates/main/form/base.html +++ b/nummi/main/templates/main/form/base.html @@ -22,25 +22,17 @@ {% endfor %}
- - -
+
+ {% block buttons %} {% if not form.instance.adding %} {% translate "Delete" %} {% endif %} -
{% endblock %} diff --git a/nummi/main/templates/main/form/login.html b/nummi/main/templates/main/form/login.html index 4e344c7..841e941 100644 --- a/nummi/main/templates/main/form/login.html +++ b/nummi/main/templates/main/form/login.html @@ -1,11 +1,7 @@ {% extends "main/form/base.html" %} {% load i18n %} {% block buttons %} - - - - - - - + + + {% endblock %} diff --git a/nummi/main/templates/main/form/search.html b/nummi/main/templates/main/form/search.html index ffb1775..58d3a2f 100644 --- a/nummi/main/templates/main/form/search.html +++ b/nummi/main/templates/main/form/search.html @@ -1,10 +1,6 @@ {% extends "main/form/base.html" %} {% load i18n %} {% block buttons %} - - - - - - + + {% endblock %} diff --git a/nummi/main/views.py b/nummi/main/views.py index 3c465fc..27bfd0f 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -71,12 +71,12 @@ class UserFormMixin: class NummiCreateView(UserMixin, UserFormMixin, CreateView): + def get_success_url(self): + return self.object.get_create_url() + def form_valid(self, form): form.instance.user = self.request.user - _res = super().form_valid(form) - if form.data["addmore"]: - return HttpResponseRedirect(self.object.get_create_url()) - return _res + return super().form_valid(form) class NummiUpdateView(UserMixin, UserFormMixin, UpdateView): From 110a699f1ab4cb0843990df377c2e8c527aaf683 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 11:28:06 +0200 Subject: [PATCH 024/276] Show title as New on forms --- nummi/main/forms.py | 2 +- nummi/main/templates/main/form/account.html | 36 +++++++++++-------- nummi/main/templates/main/form/category.html | 28 +++++++++------ .../main/form/{base.html => form_base.html} | 0 nummi/main/templates/main/form/invoice.html | 6 +++- nummi/main/templates/main/form/login.html | 2 +- nummi/main/templates/main/form/search.html | 2 +- nummi/main/templates/main/form/snapshot.html | 12 ++++--- .../main/templates/main/form/transaction.html | 28 +++++++++------ 9 files changed, 71 insertions(+), 45 deletions(-) rename nummi/main/templates/main/form/{base.html => form_base.html} (100%) diff --git a/nummi/main/forms.py b/nummi/main/forms.py index d1e5de1..aff90df 100644 --- a/nummi/main/forms.py +++ b/nummi/main/forms.py @@ -10,7 +10,7 @@ class NummiFileInput(forms.ClearableFileInput): class NummiForm(forms.ModelForm): - template_name = "main/form/base.html" + template_name = "main/form/form_base.html" def __init__(self, *args, user, **kwargs): super().__init__(*args, **kwargs) diff --git a/nummi/main/templates/main/form/account.html b/nummi/main/templates/main/form/account.html index f14da2d..f6c8f7c 100644 --- a/nummi/main/templates/main/form/account.html +++ b/nummi/main/templates/main/form/account.html @@ -16,19 +16,25 @@ type="text/css" /> {% endblock %} {% block body %} -

{{ form.instance.icon|remix }}{{ form.instance }}

-
- {% csrf_token %} - {{ form }} -
- {% if not form.instance.adding %} -

{% translate "Statements" %}

- {% include "main/table/snapshot.html" %} - {% endif %} - {% if transactions %} -

{% translate "Transactions" %}

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

{% translate "History" %}

- {% include "main/plot/history.html" %} - {% endif %} + {% with account=form.instance %} + {% if account.adding %} +

{% translate "New account" %}

+ {% else %} +

{{ account.icon|remix }}{{ account }}

+ {% endif %} +
+ {% csrf_token %} + {{ form }} +
+ {% if not account.adding %} +

{% translate "Statements" %}

+ {% include "main/table/snapshot.html" %} + {% endif %} + {% if transactions %} +

{% translate "Transactions" %}

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

{% translate "History" %}

+ {% include "main/plot/history.html" %} + {% endif %} + {% endwith %} {% endblock %} diff --git a/nummi/main/templates/main/form/category.html b/nummi/main/templates/main/form/category.html index 27ae5e9..c5dd794 100644 --- a/nummi/main/templates/main/form/category.html +++ b/nummi/main/templates/main/form/category.html @@ -16,15 +16,21 @@ type="text/css" /> {% endblock %} {% block body %} -

{{ form.instance.icon|remix }}{{ form.instance }}

-
- {% csrf_token %} - {{ form }} -
- {% if form.instance.transactions %} -

{% translate "Transactions" %}

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

{% translate "History" %}

- {% include "main/plot/history.html" %} - {% endif %} + {% with category=form.instance %} + {% if category.adding %} +

{% translate "New category" %}

+ {% else %} +

{{ category.icon|remix }}{{ category }}

+ {% endif %} +
+ {% csrf_token %} + {{ form }} +
+ {% if category.transactions %} +

{% translate "Transactions" %}

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

{% translate "History" %}

+ {% include "main/plot/history.html" %} + {% endif %} + {% endwith %} {% endblock %} diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/form_base.html similarity index 100% rename from nummi/main/templates/main/form/base.html rename to nummi/main/templates/main/form/form_base.html diff --git a/nummi/main/templates/main/form/invoice.html b/nummi/main/templates/main/form/invoice.html index 26c9dd1..95afb16 100644 --- a/nummi/main/templates/main/form/invoice.html +++ b/nummi/main/templates/main/form/invoice.html @@ -14,7 +14,11 @@ {% endblock %} {% block body %} {% with invoice=form.instance %} -

{{ invoice }}

+ {% if invoice.adding %} +

{% translate "New invoice" %}

+ {% else %} +

{{ invoice }}

+ {% endif %}

{{ invoice.transaction }}

diff --git a/nummi/main/templates/main/form/login.html b/nummi/main/templates/main/form/login.html index 841e941..8a74bc7 100644 --- a/nummi/main/templates/main/form/login.html +++ b/nummi/main/templates/main/form/login.html @@ -1,4 +1,4 @@ -{% extends "main/form/base.html" %} +{% extends "main/form/form_base.html" %} {% load i18n %} {% block buttons %} diff --git a/nummi/main/templates/main/form/search.html b/nummi/main/templates/main/form/search.html index 58d3a2f..af7b012 100644 --- a/nummi/main/templates/main/form/search.html +++ b/nummi/main/templates/main/form/search.html @@ -1,4 +1,4 @@ -{% extends "main/form/base.html" %} +{% extends "main/form/form_base.html" %} {% load i18n %} {% block buttons %} diff --git a/nummi/main/templates/main/form/snapshot.html b/nummi/main/templates/main/form/snapshot.html index 78ebae8..54c7c18 100644 --- a/nummi/main/templates/main/form/snapshot.html +++ b/nummi/main/templates/main/form/snapshot.html @@ -17,10 +17,14 @@ {% endblock %} {% block body %} {% with snapshot=form.instance %} -

- - {{ snapshot }} -

+ {% if snapshot.adding %} +

{% translate "New snapshot" %}

+ {% else %} +

+ + {{ snapshot }} +

+ {% endif %}
{% csrf_token %} {{ form }} diff --git a/nummi/main/templates/main/form/transaction.html b/nummi/main/templates/main/form/transaction.html index 9813d40..c70a855 100644 --- a/nummi/main/templates/main/form/transaction.html +++ b/nummi/main/templates/main/form/transaction.html @@ -13,15 +13,21 @@ type="text/css" /> {% endblock %} {% block body %} -

{{ form.instance }}

- {% spaceless %} - - {% csrf_token %} - {{ form }} -
- {% endspaceless %} - {% if not form.instance.adding %} -

{% translate "Invoices" %}

- {% include "main/table/invoice.html" %} - {% endif %} + {% with transaction=form.instance %} + {% if transaction.adding %} +

{% translate "New transaction" %}

+ {% else %} +

{{ form.instance }}

+ {% endif %} + {% spaceless %} +
+ {% csrf_token %} + {{ form }} +
+ {% endspaceless %} + {% if not form.instance.adding %} +

{% translate "Invoices" %}

+ {% include "main/table/invoice.html" %} + {% endif %} + {% endwith %} {% endblock %} From 37bb725f238a5182a3e0a6e8e29d9da966a3c834 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 12:01:22 +0200 Subject: [PATCH 025/276] Use form template --- nummi/main/locale/fr/LC_MESSAGES/django.mo | Bin 3165 -> 3223 bytes nummi/main/locale/fr/LC_MESSAGES/django.po | 103 +++++++++--------- nummi/main/templates/main/base.html | 8 +- nummi/main/templates/main/form/account.html | 50 +++------ nummi/main/templates/main/form/base.html | 40 +++++++ nummi/main/templates/main/form/category.html | 42 ++----- nummi/main/templates/main/form/invoice.html | 33 +----- nummi/main/templates/main/form/snapshot.html | 50 +++------ .../main/templates/main/form/transaction.html | 37 +------ nummi/main/templatetags/main_extras.py | 17 ++- 10 files changed, 154 insertions(+), 226 deletions(-) create mode 100644 nummi/main/templates/main/form/base.html diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.mo b/nummi/main/locale/fr/LC_MESSAGES/django.mo index e5a3e89157d0fcbae4c3c2a4fdad94383cd015e0..fc8224432a00a33cd115b79803e644229e41fcee 100644 GIT binary patch delta 1542 zcmYk6O-NKx6vwaTXfvgmWvN+drD;q|$Z3>FP zq=*)MECgvYZJL4>L%2|)A_^C_Xdy%{t4&7z{?nbo!~4JAyI<#=d+wc?DH$yt`cjlL zVz5?Jj&ib%>4I~McwtQ*V~Sui%!5baVt5=D!XBu#A(#utE#uHfI{`KS4b=L#a2fmr z3yc{uv$o@>r3WtxSPXMu4b;MV$YUCLErBgi0S-e2=z?C@ZQJLd*1J&aqqcp;@*2z~ zz8SU?Zb3O3NevkD(DJDrABW^d<4LH*CZQ7j40+5~UaH6(RE6f@ za_D0cXK%`V)L&;^M~59TAdr=)y zb!19@4^s8ErQ>U-%2uXqD^UfK>B4MA`_-_fo$R#Vs+KON{)A*}kuJ5W*?_j7D%6D5 zAenBzevVpg$15pL;hu*5SQL znB-U>yf93v?RR_oq5-`AZ#mKA7-52$o^aq|U?4e`p0F^?JMTm+;j4^)_l2|m0gH>O A8vpCmO<61~v(w~f&7{GZP=^UT{bGrJSTABy73d1*Hc zsfE}~{7NzA48Bk0M)IW_lZSrHz!qGKM=%plVJ>#zLF`8tPFwGzhx{C>-BVOQiqdHT;HZ_#55mqLT8tNC&1E)nS>n($-H$7i?^-=QY(74-o>P~)Uhn8x_V z%>!j72i33uRj~%uVIyiHt+xDxEkBEDciz75#X|DKs0m+1eaHlAz}vV1?_vSYVZ4p~ zeraE1Gpaf)M~aLOHK7J9!lQT?yHWL~Q1zx!6MTT0@Dt=R&$(#=uTb@tZ22;({%1G) zuZQopK&`c;SxAv7K{c#IE>maoO{fX9+WawV2WpQyQ7h169YAf-C~87iQ0IHx!}{m& zaGe4z%@pc1KSDKFur8riVg*(ICl;fJ*_UA@YT$O%z$a1lx{%BCaifWeVhP4k^>67x z74M-2eu(QPpF^i7t}y*ZhBvcT9FFW8Sta(wIMs49K0F-Pnv+vpE*G6 zAk@JY!bd3SKS7;qscMP6#5Q6dv6HAHl=MYjLMzck)DSvgN{nyz5c;@!Vk@Dfk0iyD ze=emwX|_t*|0-g?GB)Y(HP~luleS_tX&crPIxo8jKcUmEq@Q0kp{-PEBz6-z1OF1! z(Q;K1n~3d%wxgU_O%(}$dUHk~;zavGLy2?gmlHnMWPKnQbYhXH=?Go&1_RMhuQN27 z%!DH&PB<7!J`YC%gTsAJbav5ob4`A`6N!c+vCxor{*E`$@5G|?*W0_`M*{r~^~ diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.po b/nummi/main/locale/fr/LC_MESSAGES/django.po index 87315a2..15a6d59 100644 --- a/nummi/main/locale/fr/LC_MESSAGES/django.po +++ b/nummi/main/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-04-20 09:39+0200\n" +"POT-Creation-Date: 2023-04-20 12:00+0200\n" "PO-Revision-Date: 2022-12-21 17:30+0100\n" "Last-Translator: edpibu \n" "Language-Team: edpibu \n" @@ -32,19 +32,19 @@ msgstr "Rechercher" msgid "User" msgstr "Utilisateur" -#: .\models.py:38 .\models.py:72 .\models.py:80 .\models.py:230 +#: .\models.py:38 .\models.py:75 .\models.py:83 .\models.py:239 #: .\templates\main\table\snapshot.html:24 #: .\templates\main\table\transaction.html:35 msgid "Account" msgstr "Compte" -#: .\models.py:38 .\models.py:90 .\models.py:201 .\models.py:271 +#: .\models.py:38 .\models.py:93 .\models.py:210 .\models.py:283 #: .\templates\main\table\invoice.html:9 #: .\templates\main\table\transaction.html:28 msgid "Name" msgstr "Nom" -#: .\models.py:42 .\models.py:95 +#: .\models.py:42 .\models.py:98 msgid "Icon" msgstr "Icône" @@ -52,113 +52,113 @@ msgstr "Icône" msgid "Default" msgstr "Défaut" -#: .\models.py:73 .\templates\main\index.html:16 +#: .\models.py:76 .\templates\main\index.html:16 msgid "Accounts" msgstr "Comptes" -#: .\models.py:90 .\models.py:114 .\models.py:220 +#: .\models.py:93 .\models.py:120 .\models.py:229 #: .\templates\main\plot\category.html:14 #: .\templates\main\table\transaction.html:32 msgid "Category" msgstr "Catégorie" -#: .\models.py:97 +#: .\models.py:100 msgid "Budget" msgstr "Budget" -#: .\models.py:115 .\templates\main\form\snapshot.html:29 +#: .\models.py:121 .\templates\main\form\snapshot.html:10 #: .\templates\main\index.html:32 msgid "Categories" msgstr "Catégories" -#: .\models.py:120 +#: .\models.py:126 msgid "End date" msgstr "Date de fin" -#: .\models.py:122 +#: .\models.py:128 msgid "Start date" msgstr "Date de début" -#: .\models.py:125 +#: .\models.py:131 msgid "End value" msgstr "Valeur de fin" -#: .\models.py:128 +#: .\models.py:134 msgid "Start value" msgstr "Valeur de début" -#: .\models.py:134 .\templates\main\table\snapshot.html:27 +#: .\models.py:140 .\templates\main\table\snapshot.html:27 msgid "Difference" msgstr "Différence" -#: .\models.py:141 +#: .\models.py:147 msgid "Transaction difference" msgstr "Différence des transactions" -#: .\models.py:147 .\models.py:276 .\templates\main\form\fileinput.html:8 +#: .\models.py:153 .\models.py:288 .\templates\main\form\fileinput.html:8 #: .\templates\main\table\invoice.html:10 #: .\templates\main\table\invoice.html:21 msgid "File" msgstr "Fichier" -#: .\models.py:154 +#: .\models.py:160 #, python-format msgid "%(date)s statement" msgstr "Relevé du %(date)s" -#: .\models.py:194 .\models.py:225 +#: .\models.py:203 .\models.py:234 msgid "Statement" msgstr "Relevé" -#: .\models.py:195 .\templates\main\form\account.html:25 +#: .\models.py:204 .\templates\main\form\account.html:7 #: .\templates\main\list\snapshot.html:6 .\templates\main\list\snapshot.html:20 msgid "Statements" msgstr "Relevés" -#: .\models.py:201 .\models.py:264 +#: .\models.py:210 .\models.py:276 msgid "Transaction" msgstr "Transaction" -#: .\models.py:203 +#: .\models.py:212 msgid "Description" msgstr "Description" -#: .\models.py:205 .\templates\main\table\snapshot.html:26 +#: .\models.py:214 .\templates\main\table\snapshot.html:26 #: .\templates\main\table\transaction.html:29 msgid "Value" msgstr "Valeur" -#: .\models.py:207 .\templates\main\table\snapshot.html:22 +#: .\models.py:216 .\templates\main\table\snapshot.html:22 #: .\templates\main\table\transaction.html:27 msgid "Date" msgstr "Date" -#: .\models.py:208 +#: .\models.py:217 msgid "Real date" msgstr "Date réelle" -#: .\models.py:210 .\templates\main\table\transaction.html:30 +#: .\models.py:219 .\templates\main\table\transaction.html:30 msgid "Trader" msgstr "Commerçant" -#: .\models.py:213 +#: .\models.py:222 msgid "Payment" msgstr "Paiement" -#: .\models.py:265 .\templates\main\base.html:50 -#: .\templates\main\form\account.html:29 .\templates\main\form\category.html:25 -#: .\templates\main\form\snapshot.html:33 .\templates\main\index.html:26 +#: .\models.py:277 .\templates\main\base.html:50 +#: .\templates\main\form\account.html:11 .\templates\main\form\category.html:7 +#: .\templates\main\form\snapshot.html:14 .\templates\main\index.html:26 #: .\templates\main\list\transaction.html:6 #: .\templates\main\list\transaction.html:23 #: .\templates\main\table\snapshot.html:28 msgid "Transactions" msgstr "Transactions" -#: .\models.py:271 .\models.py:308 +#: .\models.py:283 .\models.py:323 msgid "Invoice" msgstr "Facture" -#: .\models.py:309 .\templates\main\form\transaction.html:24 +#: .\models.py:324 .\templates\main\form\transaction.html:5 msgid "Invoices" msgstr "Factures" @@ -175,26 +175,26 @@ msgid "Snapshots" msgstr "Relevés" #: .\templates\main\base.html:56 -msgid "New account" -msgstr "Nouveau compte" +msgid "Create account" +msgstr "Créer compte" #: .\templates\main\base.html:61 -msgid "New snapshot" -msgstr "Nouveau relevé" +msgid "Create snapshot" +msgstr "Créer relevé" #: .\templates\main\base.html:66 -msgid "New category" -msgstr "Nouvelle catégorie" +msgid "Create category" +msgstr "Créer catégorie" -#: .\templates\main\base.html:71 .\templates\main\table\transaction.html:5 -msgid "New transaction" -msgstr "Nouvelle transaction" +#: .\templates\main\base.html:71 +msgid "Create transaction" +msgstr "Créer transaction" #: .\templates\main\base.html:79 msgid "Log out" msgstr "Se déconnecter" -#: .\templates\main\base.html:84 .\templates\main\form\login.html:7 +#: .\templates\main\base.html:84 .\templates\main\form\login.html:6 msgid "Log in" msgstr "Se connecter" @@ -211,25 +211,27 @@ msgstr "Annuler" msgid "Confirm" msgstr "Confirmer" -#: .\templates\main\form\account.html:31 .\templates\main\form\category.html:27 +#: .\templates\main\form\account.html:13 .\templates\main\form\category.html:9 #: .\templates\main\index.html:28 msgid "History" msgstr "Historique" -#: .\templates\main\form\base.html:27 -msgid "Add another" -msgstr "Continuer à ajouter" +#: .\templates\main\form\base.html:7 .\templates\main\form\base.html:28 +#, python-format +msgid "Create %(name)s" +msgstr "Créer %(name)s" -#: .\templates\main\form\base.html:38 .\templates\main\table\invoice.html:11 +#: .\templates\main\form\form_base.html:29 +#: .\templates\main\table\invoice.html:11 #: .\templates\main\table\invoice.html:24 msgid "Delete" msgstr "Supprimer" -#: .\templates\main\form\base.html:40 +#: .\templates\main\form\form_base.html:31 msgid "Reset" msgstr "Réinitialiser" -#: .\templates\main\form\base.html:41 +#: .\templates\main\form\form_base.html:32 msgid "Save" msgstr "Enregistrer" @@ -273,9 +275,10 @@ msgstr "Nouveau relevé" msgid "View all statements" msgstr "Voir tous les relevés" +#: .\templates\main\table\transaction.html:5 +msgid "New transaction" +msgstr "Nouvelle transaction" + #: .\templates\main\table\transaction.html:73 msgid "View all transactions" msgstr "Voir toutes les transactions" - -#~ msgid "Valid" -#~ msgstr "Valide" diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index c9550e6..9eb489c 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -53,22 +53,22 @@
  • {% translate "New account" %} + accesskey="a">{% translate "Create account" %}
  • {% translate "New snapshot" %} + accesskey="s">{% translate "Create snapshot" %}
  • {% translate "New category" %} + accesskey="c">{% translate "Create category" %}
  • {% translate "New transaction" %} + accesskey="t">{% translate "Create transaction" %}
  • - - -{% endblock %} -{% block body %} - {% with account=form.instance %} - {% if account.adding %} -

    {% translate "New account" %}

    - {% else %} -

    {{ account.icon|remix }}{{ account }}

    - {% endif %} -
    - {% csrf_token %} - {{ form }} -
    - {% if not account.adding %} -

    {% translate "Statements" %}

    - {% include "main/table/snapshot.html" %} - {% endif %} - {% if transactions %} -

    {% translate "Transactions" %}

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

    {% translate "History" %}

    - {% include "main/plot/history.html" %} - {% endif %} - {% endwith %} +{% block h1 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} +{% block tables %} + {% if not form.instance.adding %} +

    {% translate "Statements" %}

    + {% include "main/table/snapshot.html" %} + {% endif %} + {% if transactions %} +

    {% translate "Transactions" %}

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

    {% translate "History" %}

    + {% include "main/plot/history.html" %} + {% endif %} {% endblock %} diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html new file mode 100644 index 0000000..2b71ee6 --- /dev/null +++ b/nummi/main/templates/main/form/base.html @@ -0,0 +1,40 @@ +{% extends "main/base.html" %} +{% load static %} +{% load main_extras %} +{% load i18n %} +{% block title %} + {% if form.instance.adding %} + {% blocktranslate with name=form.instance|verbose_name|lower %}Create {{ name }}{% endblocktranslate %} + {% else %} + {{ form.instance }} + {% endif %} + – Nummi +{% endblock %} +{% block link %} + {{ block.super }} + + + +{% endblock %} +{% block body %} + {% with instance=form.instance %} + {% if instance.adding %} +

    {% blocktranslate with name=instance|verbose_name|lower %}Create {{ name }}{% endblocktranslate %}

    + {% else %} +

    + {% block h1 %}{{ instance }}{% endblock %} +

    + {% endif %} +
    + {% csrf_token %} + {{ form }} +
    + {% block tables %}{% endblock %} + {% endwith %} +{% endblock %} diff --git a/nummi/main/templates/main/form/category.html b/nummi/main/templates/main/form/category.html index c5dd794..b07b45e 100644 --- a/nummi/main/templates/main/form/category.html +++ b/nummi/main/templates/main/form/category.html @@ -1,36 +1,12 @@ -{% extends "main/base.html" %} -{% load static %} +{% extends "main/form/base.html" %} {% load main_extras %} {% load i18n %} -{% block title %}{{ form.instance }} – Nummi{% endblock %} -{% block link %} - {{ block.super }} - - - -{% endblock %} -{% block body %} - {% with category=form.instance %} - {% if category.adding %} -

    {% translate "New category" %}

    - {% else %} -

    {{ category.icon|remix }}{{ category }}

    - {% endif %} -
    - {% csrf_token %} - {{ form }} -
    - {% if category.transactions %} -

    {% translate "Transactions" %}

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

    {% translate "History" %}

    - {% include "main/plot/history.html" %} - {% endif %} - {% endwith %} +{% block h1 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} +{% block tables %} + {% if form.instance.transactions %} +

    {% translate "Transactions" %}

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

    {% translate "History" %}

    + {% include "main/plot/history.html" %} + {% endif %} {% endblock %} diff --git a/nummi/main/templates/main/form/invoice.html b/nummi/main/templates/main/form/invoice.html index 95afb16..53171dc 100644 --- a/nummi/main/templates/main/form/invoice.html +++ b/nummi/main/templates/main/form/invoice.html @@ -1,32 +1 @@ -{% extends "main/base.html" %} -{% load static %} -{% load main_extras %} -{% load i18n %} -{% block title %}{{ form.instance }} – Nummi{% endblock %} -{% block link %} - {{ block.super }} - - -{% endblock %} -{% block body %} - {% with invoice=form.instance %} - {% if invoice.adding %} -

    {% translate "New invoice" %}

    - {% else %} -

    {{ invoice }}

    - {% endif %} -

    - {{ invoice.transaction }} -

    - {% spaceless %} -
    - {% csrf_token %} - {{ form }} -
    - {% endspaceless %} - {% endwith %} -{% endblock %} +{% extends "main/form/base.html" %} diff --git a/nummi/main/templates/main/form/snapshot.html b/nummi/main/templates/main/form/snapshot.html index 54c7c18..31e639a 100644 --- a/nummi/main/templates/main/form/snapshot.html +++ b/nummi/main/templates/main/form/snapshot.html @@ -1,41 +1,17 @@ -{% extends "main/base.html" %} -{% load static %} +{% extends "main/form/base.html" %} {% load main_extras %} {% load i18n %} -{% block title %}{{ form.instance }} – Nummi{% endblock %} -{% block link %} - {{ block.super }} - - - +{% block h1 %} + {{ form.instance.sum|check:form.instance.diff }} + {{ form.instance }} {% endblock %} -{% block body %} - {% with snapshot=form.instance %} - {% if snapshot.adding %} -

    {% translate "New snapshot" %}

    - {% else %} -

    - - {{ snapshot }} -

    - {% endif %} -
    - {% csrf_token %} - {{ form }} -
    - {% if categories %} -

    {% translate "Categories" %}

    - {% include "main/plot/category.html" %} - {% endif %} - {% if not snapshot.adding %} -

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

    - {% include "main/table/transaction.html" %} - {% endif %} - {% endwith %} +{% block tables %} + {% if categories %} +

    {% translate "Categories" %}

    + {% include "main/plot/category.html" %} + {% endif %} + {% if not form.instance.adding %} +

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

    + {% include "main/table/transaction.html" %} + {% endif %} {% endblock %} diff --git a/nummi/main/templates/main/form/transaction.html b/nummi/main/templates/main/form/transaction.html index c70a855..77d19b2 100644 --- a/nummi/main/templates/main/form/transaction.html +++ b/nummi/main/templates/main/form/transaction.html @@ -1,33 +1,8 @@ -{% extends "main/base.html" %} -{% load static %} -{% load main_extras %} +{% extends "main/form/base.html" %} {% load i18n %} -{% block title %}{{ form.instance }} – Nummi{% endblock %} -{% block link %} - {{ block.super }} - - -{% endblock %} -{% block body %} - {% with transaction=form.instance %} - {% if transaction.adding %} -

    {% translate "New transaction" %}

    - {% else %} -

    {{ form.instance }}

    - {% endif %} - {% spaceless %} -
    - {% csrf_token %} - {{ form }} -
    - {% endspaceless %} - {% if not form.instance.adding %} -

    {% translate "Invoices" %}

    - {% include "main/table/invoice.html" %} - {% endif %} - {% endwith %} +{% block tables %} + {% if not form.instance.adding %} +

    {% translate "Invoices" %}

    + {% include "main/table/invoice.html" %} + {% endif %} {% endblock %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 7558403..129c8a9 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -34,10 +34,23 @@ def pmrvalue(val): @register.filter -def remix(icon): - return mark_safe(f"""""") +def remix(icon, cls=""): + return mark_safe(f"""""") + + +@register.filter +def check(sum, diff): + if sum == diff: + return remix("check", "green") + else: + return remix("close", "red") @register.filter def extension(file): return file.name.split(".")[-1].upper() + + +@register.filter +def verbose_name(obj): + return obj._meta.verbose_name From df19c5586d2d6c46f29fec7c86d48b398b6357c2 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 12:03:30 +0200 Subject: [PATCH 026/276] Change save button to create on creation form --- nummi/main/locale/fr/LC_MESSAGES/django.po | 8 ++++++-- nummi/main/templates/main/form/form_base.html | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.po b/nummi/main/locale/fr/LC_MESSAGES/django.po index 15a6d59..d621743 100644 --- a/nummi/main/locale/fr/LC_MESSAGES/django.po +++ b/nummi/main/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-04-20 12:00+0200\n" +"POT-Creation-Date: 2023-04-20 12:02+0200\n" "PO-Revision-Date: 2022-12-21 17:30+0100\n" "Last-Translator: edpibu \n" "Language-Team: edpibu \n" @@ -231,7 +231,11 @@ msgstr "Supprimer" msgid "Reset" msgstr "Réinitialiser" -#: .\templates\main\form\form_base.html:32 +#: .\templates\main\form\form_base.html:33 +msgid "Create" +msgstr "Créer" + +#: .\templates\main\form\form_base.html:35 msgid "Save" msgstr "Enregistrer" diff --git a/nummi/main/templates/main/form/form_base.html b/nummi/main/templates/main/form/form_base.html index edb30f4..5a76aba 100644 --- a/nummi/main/templates/main/form/form_base.html +++ b/nummi/main/templates/main/form/form_base.html @@ -29,7 +29,11 @@ {% translate "Delete" %} {% endif %} - + {% if form.instance.adding %} + + {% else %} + + {% endif %} {% endblock %} From 45d12d0b0738089c3fa332d622a2843d14deec37 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 12:04:14 +0200 Subject: [PATCH 027/276] Remove lowercase from creation form title --- nummi/main/templates/main/form/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html index 2b71ee6..1a6fd93 100644 --- a/nummi/main/templates/main/form/base.html +++ b/nummi/main/templates/main/form/base.html @@ -4,7 +4,7 @@ {% load i18n %} {% block title %} {% if form.instance.adding %} - {% blocktranslate with name=form.instance|verbose_name|lower %}Create {{ name }}{% endblocktranslate %} + {% blocktranslate with name=form.instance|verbose_name %}Create {{ name }}{% endblocktranslate %} {% else %} {{ form.instance }} {% endif %} @@ -25,7 +25,7 @@ {% block body %} {% with instance=form.instance %} {% if instance.adding %} -

    {% blocktranslate with name=instance|verbose_name|lower %}Create {{ name }}{% endblocktranslate %}

    +

    {% blocktranslate with name=instance|verbose_name %}Create {{ name }}{% endblocktranslate %}

    {% else %}

    {% block h1 %}{{ instance }}{% endblock %} From e87893ae0cdeeafa750ae727f496d21a25419b71 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 12:06:01 +0200 Subject: [PATCH 028/276] Capitalize links to creation form --- nummi/main/locale/fr/LC_MESSAGES/django.mo | Bin 3223 -> 3253 bytes nummi/main/locale/fr/LC_MESSAGES/django.po | 18 +++++++++--------- nummi/main/templates/main/base.html | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.mo b/nummi/main/locale/fr/LC_MESSAGES/django.mo index fc8224432a00a33cd115b79803e644229e41fcee..fc30c45b81ed7411923231df086571f7a204816d 100644 GIT binary patch delta 1504 zcmYk6OGs2v7{|YSm7~4X9^<1lv$V`Cdzh73y0B;wjqQY(bVws-%(0^9rHvamk%U6E zGeVFeM1pV;&Cc3LSkywuASfuJO+*R({?obQ;r{RMob#RYecyT9I~W`fdNVm`ca2hy z3Q=~7SqT13uH_L%X;bPbdm%w(o0(QZJ@FvWF6OIWOpgjpSei~~2jI)1+%gsFd zMr9d>x&IxOjTa172o<;zrolr{4w@j39p{w=Lr{TELj^hqGhy7dZ$QoOgPPy(+5<^? zW;W$g7>05*>iWkWC!9S2$<!9*4J?L=Q_Cxp z_|`y04v#@OJOP<&r=TXbLapcml*4OK0sCD49oK&!YThGfe+*aCeh#zY6qNrNsCb{C zmrLafl~r&KHY_sBW%=rFA*vj9Kui{cYvE;h1m1S`H_rY6DzRCpM888G`^`&!Qt8yr z1fX^*Kfw8~q*6|Y?m!((UN)#qTcHAnp&WHW1&BL(j{Q&$2cgD2gqrsR^4K%iehHPx zq-#$F*nbUtPlwL@Bh*%Xb^HaDaT-yTP#)BW5rn$^MNsxCsH3WfT1W^6;TgCNUWf93 z56b^A)VQY}6&`!Ri&*v==EHYT6MjMs`~wv*z-%R40A(+Qn5@#V7V0x@f;<*>{ZXiu zUxm68x1h#(LsU5O5pD#frG>C31XC`r#9s7@bkw#J@FxHnQ|(UnZ#FXKY^>*s-ws8?-SLjtyuD?tC}TQR-V!5`o`v3$?Z#MFcW=bE b;w&+Ox6b*Vlace0ONkMGK+9N7pgrXuR@auw delta 1461 zcmX|>OGuPa7=}+ezvYzXB`eKptTfFmO{bhvu`CE`kysF6(J=m9AZKL!3+%$sB3lK; zAkwBqm&L%)W@?cIErw{Jw1_Uas6`7QaMxv%QO`RZ=i`6RbFSa{&Uent^Xkd!#McVn zgu%K|E%Fr@GXUocDX>n*mo`RbH25S8~xDtMX zrN$)8obC8!>EJ~hRzV+Zgj(1F`Am>vIqZVk;4st%1F#ql+V(l9^)aaRo^4;Zya|ig z-;CP{cc2_i>~X8W(%{##J%?%MH5xSIAOxB^Z=`F{&_fEic` z-@`-|l{qTCON?2t1KsQ@hy9Q)a}utFXW;=jX2)OI@hnte(@=qahJ5B5g)%Y^m7xV# z4*g8x?oF+q{Ohip=}?3{P#g9^IXD94_=IKHau~|t7}UJmQ0wkNK6Br;A444^ZQB{k zmrz%o^^_t!}ktxVC@p#YNU!E8nQ)Uc+LwAs3{rN^m1A=!GQN3CqOqiv`jb)t1h zrq{2ZWQjbJk=$Jep-k5y+lY1|J-xv)KyG^6a%ck6TAQBrM@!WhX>}6B9xtQ;XyO-Q6 O+0^0$FVpWID)\n" "Language-Team: edpibu \n" @@ -175,20 +175,20 @@ msgid "Snapshots" msgstr "Relevés" #: .\templates\main\base.html:56 -msgid "Create account" -msgstr "Créer compte" +msgid "Create Account" +msgstr "Créer Compte" #: .\templates\main\base.html:61 -msgid "Create snapshot" -msgstr "Créer relevé" +msgid "Create Snapshot" +msgstr "Créer Relevé" #: .\templates\main\base.html:66 -msgid "Create category" -msgstr "Créer catégorie" +msgid "Create Category" +msgstr "Créer Catégorie" #: .\templates\main\base.html:71 -msgid "Create transaction" -msgstr "Créer transaction" +msgid "Create Transaction" +msgstr "Créer Transaction" #: .\templates\main\base.html:79 msgid "Log out" diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 9eb489c..c40f32b 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -53,22 +53,22 @@
  • {% translate "Create account" %} + accesskey="a">{% translate "Create Account" %}
  • {% translate "Create snapshot" %} + accesskey="s">{% translate "Create Snapshot" %}
  • {% translate "Create category" %} + accesskey="c">{% translate "Create Category" %}
  • {% translate "Create transaction" %} + accesskey="t">{% translate "Create Transaction" %}
  • Date: Thu, 20 Apr 2023 12:14:25 +0200 Subject: [PATCH 029/276] Change create titles (again) --- nummi/main/locale/fr/LC_MESSAGES/django.mo | Bin 3253 -> 3431 bytes nummi/main/locale/fr/LC_MESSAGES/django.po | 90 ++++++++++++------ nummi/main/templates/main/base.html | 8 +- nummi/main/templates/main/form/account.html | 6 ++ nummi/main/templates/main/form/base.html | 6 +- nummi/main/templates/main/form/category.html | 6 ++ nummi/main/templates/main/form/invoice.html | 6 ++ nummi/main/templates/main/form/snapshot.html | 6 ++ .../main/templates/main/form/transaction.html | 6 ++ 9 files changed, 97 insertions(+), 37 deletions(-) diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.mo b/nummi/main/locale/fr/LC_MESSAGES/django.mo index fc30c45b81ed7411923231df086571f7a204816d..c753bf09e03a4db2c762d8ceab3fd2774faeeda9 100644 GIT binary patch delta 1671 zcmY+ETS$~q5XZ;7rQ6j@WoE6H?516{dudh_MVb&sM8vkNW!SE)yH+o4^i&T)wnR|S zQw2s6A`(OeWduD)6!;=x1%gp8MbJ}GzyI#pvf-Qgo$Jh;Ip6mU6phb~jpk)sGh`b& z53QJFb_iBwV2~@BW_hq1X2aWXGQ1CGzyY`gzJysY10#AcxiAlEeF@b5O2}i&G1JV- ztd_=f26iL|?11b0p#pbyf6R~3wm8NEZ$Npz3+1^F=D;=5 zjOM>U`T6d~e>$c!srlS&iy5c0JbH3G2P%;gI0Y_o{k5*&0H-qE1a-0_Pzj%c1@H`< z4KG6;yMxgIdZFSxgo^V7l8ZghjwLs~atlYH9DagIF_$_HMW~jCAof6Q1PkDNxEw0*KFDJ&7_IX|5AUCaY3Ky!;9PhKYQt@)g}qRLA48pZ$juK! zV%n(VXQ=P|2jsDQ%B6WPR7I+wO1}nb-5!`m{6uh-RHBpWhp`CN$IqI-vXqwI4J9dA zi>^+za1M3fI{5~4D_V^%MmM5M&{A{_nv!KS6h9%gSbaLmP@=0)Sta!eODW+(RAraw z#gc4Bb!ElqO0)#ki6uIzs#vEEvLcB@g=ij2d?qTZ?rI&Xl1a)@{+PB2twh(Ox&|*Q zsYt?Vpx$g0k$~5aIUWk1P4(M?U7YU!ny2&yF# zs)xV|A(9XY!iQ+zEQlp4>Osh$DCj~j5he8due0Jq!~ExW=9`)C`)1CZ^QmyKAUcwp zdCgGjP%+9$H^vA5X3$V}WEqnScS09zhEw1nI1{$RE$|%7hC`Nd=%zmmHUAaV`Vl++ z5za8C#eC&qIs;?l29tvq223$j;7XVYw?a8+gjBPe#shs&feu0iIs&J{uFtA}#93(DbM$Xc@>YGE_fykk%fyP*PJu=cCgeiLflJv;sY=F@)+b6^6>|A;F( z9`FN(JPe=V95@E+)3C99*_((ehi#A{6N2;L3Ai0zw&O4D_&cb?Mxhe@2C3#Zjr?R_ z)XBJ`Jm{zj;Vf7V_0DTy@^V3C+6)!g59KHT6(DRWKk8qE@^>9--W{lQ{g7%N+5S_g zM54pikg$9U_0Hcz9n}}hUr-rm5=9BkhWZc+pl*8!)OZ!ttEz+Ahz}OP7C0ZCf%1O? z;y-G7dCrbx>Ws-A<9$x2 sw;|Nt5pi%Z-t$M|eRvE^SoS$bovwIaYC==&g*y?8dx{3WdrqeP1CAk?^#A|> diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.po b/nummi/main/locale/fr/LC_MESSAGES/django.po index 71dbd28..64b2199 100644 --- a/nummi/main/locale/fr/LC_MESSAGES/django.po +++ b/nummi/main/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-04-20 12:05+0200\n" +"POT-Creation-Date: 2023-04-20 12:13+0200\n" "PO-Revision-Date: 2022-12-21 17:30+0100\n" "Last-Translator: edpibu \n" "Language-Team: edpibu \n" @@ -66,7 +66,7 @@ msgstr "Catégorie" msgid "Budget" msgstr "Budget" -#: .\models.py:121 .\templates\main\form\snapshot.html:10 +#: .\models.py:121 .\templates\main\form\snapshot.html:16 #: .\templates\main\index.html:32 msgid "Categories" msgstr "Catégories" @@ -110,7 +110,7 @@ msgstr "Relevé du %(date)s" msgid "Statement" msgstr "Relevé" -#: .\models.py:204 .\templates\main\form\account.html:7 +#: .\models.py:204 .\templates\main\form\account.html:13 #: .\templates\main\list\snapshot.html:6 .\templates\main\list\snapshot.html:20 msgid "Statements" msgstr "Relevés" @@ -146,8 +146,8 @@ msgid "Payment" msgstr "Paiement" #: .\models.py:277 .\templates\main\base.html:50 -#: .\templates\main\form\account.html:11 .\templates\main\form\category.html:7 -#: .\templates\main\form\snapshot.html:14 .\templates\main\index.html:26 +#: .\templates\main\form\account.html:17 .\templates\main\form\category.html:13 +#: .\templates\main\form\snapshot.html:20 .\templates\main\index.html:26 #: .\templates\main\list\transaction.html:6 #: .\templates\main\list\transaction.html:23 #: .\templates\main\table\snapshot.html:28 @@ -158,7 +158,7 @@ msgstr "Transactions" msgid "Invoice" msgstr "Facture" -#: .\models.py:324 .\templates\main\form\transaction.html:5 +#: .\models.py:324 .\templates\main\form\transaction.html:11 msgid "Invoices" msgstr "Factures" @@ -174,21 +174,21 @@ msgstr "Accueil" msgid "Snapshots" msgstr "Relevés" -#: .\templates\main\base.html:56 -msgid "Create Account" -msgstr "Créer Compte" +#: .\templates\main\base.html:56 .\templates\main\form\account.html:5 +msgid "Create account" +msgstr "Créer un compte" -#: .\templates\main\base.html:61 -msgid "Create Snapshot" -msgstr "Créer Relevé" +#: .\templates\main\base.html:61 .\templates\main\form\snapshot.html:5 +msgid "Create snapshot" +msgstr "Créer un relevé" -#: .\templates\main\base.html:66 -msgid "Create Category" -msgstr "Créer Catégorie" +#: .\templates\main\base.html:66 .\templates\main\form\category.html:5 +msgid "Create category" +msgstr "Créer une catégorie" -#: .\templates\main\base.html:71 -msgid "Create Transaction" -msgstr "Créer Transaction" +#: .\templates\main\base.html:71 .\templates\main\form\transaction.html:4 +msgid "Create transaction" +msgstr "Créer une transaction" #: .\templates\main\base.html:79 msgid "Log out" @@ -211,15 +211,18 @@ msgstr "Annuler" msgid "Confirm" msgstr "Confirmer" -#: .\templates\main\form\account.html:13 .\templates\main\form\category.html:9 +#: .\templates\main\form\account.html:8 +msgid "New account" +msgstr "Nouveau compte" + +#: .\templates\main\form\account.html:19 .\templates\main\form\category.html:15 #: .\templates\main\index.html:28 msgid "History" msgstr "Historique" -#: .\templates\main\form\base.html:7 .\templates\main\form\base.html:28 -#, python-format -msgid "Create %(name)s" -msgstr "Créer %(name)s" +#: .\templates\main\form\category.html:8 +msgid "New category" +msgstr "Nouvelle catégorie" #: .\templates\main\form\form_base.html:29 #: .\templates\main\table\invoice.html:11 @@ -239,6 +242,23 @@ msgstr "Créer" msgid "Save" msgstr "Enregistrer" +#: .\templates\main\form\invoice.html:3 +msgid "Create invoice" +msgstr "Créer une facture" + +#: .\templates\main\form\invoice.html:6 .\templates\main\table\invoice.html:37 +msgid "New invoice" +msgstr "Nouvelle facture" + +#: .\templates\main\form\snapshot.html:8 +msgid "New snapshot" +msgstr "Nouveau relevés" + +#: .\templates\main\form\transaction.html:7 +#: .\templates\main\table\transaction.html:5 +msgid "New transaction" +msgstr "Nouvelle transaction" + #: .\templates\main\list\snapshot.html:27 msgid "No snapshots to show" msgstr "Aucun relevé à afficher" @@ -267,10 +287,6 @@ msgstr "Mois" msgid "No invoice" msgstr "Aucune facture" -#: .\templates\main\table\invoice.html:37 -msgid "New invoice" -msgstr "Nouvelle facture" - #: .\templates\main\table\snapshot.html:5 msgid "New statement" msgstr "Nouveau relevé" @@ -279,10 +295,22 @@ msgstr "Nouveau relevé" msgid "View all statements" msgstr "Voir tous les relevés" -#: .\templates\main\table\transaction.html:5 -msgid "New transaction" -msgstr "Nouvelle transaction" - #: .\templates\main\table\transaction.html:73 msgid "View all transactions" msgstr "Voir toutes les transactions" + +#~ msgid "Create Account" +#~ msgstr "Créer Compte" + +#~ msgid "Create Snapshot" +#~ msgstr "Créer Relevé" + +#~ msgid "Create Category" +#~ msgstr "Créer Catégorie" + +#~ msgid "Create Transaction" +#~ msgstr "Créer Transaction" + +#, python-format +#~ msgid "Create %(name)s" +#~ msgstr "Créer %(name)s" diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index c40f32b..9eb489c 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -53,22 +53,22 @@
  • {% translate "Create Account" %} + accesskey="a">{% translate "Create account" %}
  • {% translate "Create Snapshot" %} + accesskey="s">{% translate "Create snapshot" %}
  • {% translate "Create Category" %} + accesskey="c">{% translate "Create category" %}
  • {% translate "Create Transaction" %} + accesskey="t">{% translate "Create transaction" %}
  • {% blocktranslate with name=instance|verbose_name %}Create {{ name }}{% endblocktranslate %}
  • +

    + {% block h1_new %}{% endblock %} +

    {% else %}

    {% block h1 %}{{ instance }}{% endblock %} diff --git a/nummi/main/templates/main/form/category.html b/nummi/main/templates/main/form/category.html index b07b45e..5dea5c2 100644 --- a/nummi/main/templates/main/form/category.html +++ b/nummi/main/templates/main/form/category.html @@ -1,6 +1,12 @@ {% extends "main/form/base.html" %} {% load main_extras %} {% load i18n %} +{% block title_new %} + {% translate "Create category" %} +{% endblock %} +{% block h1_new %} + {% translate "New category" %} +{% endblock %} {% block h1 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} {% block tables %} {% if form.instance.transactions %} diff --git a/nummi/main/templates/main/form/invoice.html b/nummi/main/templates/main/form/invoice.html index 53171dc..5b3b473 100644 --- a/nummi/main/templates/main/form/invoice.html +++ b/nummi/main/templates/main/form/invoice.html @@ -1 +1,7 @@ {% extends "main/form/base.html" %} +{% block title_new %} + {% translate "Create invoice" %} +{% endblock %} +{% block h1_new %} + {% translate "New invoice" %} +{% endblock %} diff --git a/nummi/main/templates/main/form/snapshot.html b/nummi/main/templates/main/form/snapshot.html index 31e639a..aa8aec2 100644 --- a/nummi/main/templates/main/form/snapshot.html +++ b/nummi/main/templates/main/form/snapshot.html @@ -1,6 +1,12 @@ {% extends "main/form/base.html" %} {% load main_extras %} {% load i18n %} +{% block title_new %} + {% translate "Create snapshot" %} +{% endblock %} +{% block h1_new %} + {% translate "New snapshot" %} +{% endblock %} {% block h1 %} {{ form.instance.sum|check:form.instance.diff }} {{ form.instance }} diff --git a/nummi/main/templates/main/form/transaction.html b/nummi/main/templates/main/form/transaction.html index 77d19b2..81e648b 100644 --- a/nummi/main/templates/main/form/transaction.html +++ b/nummi/main/templates/main/form/transaction.html @@ -1,5 +1,11 @@ {% extends "main/form/base.html" %} {% load i18n %} +{% block title_new %} + {% translate "Create transaction" %} +{% endblock %} +{% block h1_new %} + {% translate "New transaction" %} +{% endblock %} {% block tables %} {% if not form.instance.adding %}

    {% translate "Invoices" %}

    From b673950e99f16bccf92d08be72449437ecf03753 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 20 Apr 2023 12:15:06 +0200 Subject: [PATCH 030/276] Fix translation error --- nummi/main/locale/fr/LC_MESSAGES/django.mo | Bin 3431 -> 3430 bytes nummi/main/locale/fr/LC_MESSAGES/django.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.mo b/nummi/main/locale/fr/LC_MESSAGES/django.mo index c753bf09e03a4db2c762d8ceab3fd2774faeeda9..fb44dbbb5cc41724a6a67a77ddc32c70d196781a 100644 GIT binary patch delta 220 zcmXZU&k6xi6vy$OOfz!FpOA7BYstn#C}ryfCNJOtq^#^cf~|#`th|DjmNoVz0HeSFja?VCNOo%2uottSrRZ zcT^bWVP?PGy~X! Date: Thu, 20 Apr 2023 12:22:45 +0200 Subject: [PATCH 031/276] Use different urls for create pages --- nummi/main/models.py | 10 +++--- nummi/main/templates/main/base.html | 48 +++++++++++++---------------- nummi/main/urls.py | 14 ++++----- nummi/main/views.py | 4 +-- 4 files changed, 35 insertions(+), 41 deletions(-) diff --git a/nummi/main/models.py b/nummi/main/models.py index 1d77de0..50190cf 100644 --- a/nummi/main/models.py +++ b/nummi/main/models.py @@ -54,7 +54,7 @@ class Account(NummiModel): return str(self.name) def get_create_url(self): - return reverse("account") + return reverse("new_account") def get_absolute_url(self): return reverse("account", kwargs={"pk": self.pk}) @@ -103,7 +103,7 @@ class Category(NummiModel): return str(self.name) def get_create_url(self): - return reverse("category") + return reverse("new_category") def get_absolute_url(self): return reverse("category", kwargs={"pk": self.pk}) @@ -190,7 +190,7 @@ class Snapshot(AccountModel): super().delete(*args, **kwargs) def get_create_url(self): - return reverse("snapshot") + return reverse("new_snapshot") def get_absolute_url(self): return reverse("snapshot", kwargs={"pk": self.pk}) @@ -255,7 +255,7 @@ class Transaction(NummiModel): return f"{self.name}" def get_create_url(self): - return reverse("transaction") + return reverse("new_transaction") def get_absolute_url(self): return reverse("transaction", kwargs={"pk": self.pk}) @@ -307,7 +307,7 @@ class Invoice(NummiModel): super().delete(*args, **kwargs) def get_create_url(self): - return reverse("invoice", kwargs={"transaction_pk": self.transaction.pk}) + return reverse("new_invoice", kwargs={"transaction_pk": self.transaction.pk}) def get_absolute_url(self): return reverse( diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 9eb489c..406d810 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -9,15 +9,9 @@ {% block title %}Nummi{% endblock %} {% block link %} - - - + + + {% endblock %} @@ -25,7 +19,7 @@ {% spaceless %}
    {% endspaceless %} {% endblock %} From 82c7a03965087eb90a299eefb404d3ea2b33b866 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 09:48:11 +0200 Subject: [PATCH 054/276] Move adding property to filters --- nummi/main/models.py | 19 +++++-------------- nummi/main/templates/main/form/account.html | 2 +- nummi/main/templates/main/form/base.html | 6 +++--- nummi/main/templates/main/form/form_base.html | 5 +++-- nummi/main/templates/main/form/snapshot.html | 2 +- .../main/templates/main/form/transaction.html | 3 ++- nummi/main/templatetags/main_extras.py | 5 +++++ 7 files changed, 20 insertions(+), 22 deletions(-) diff --git a/nummi/main/models.py b/nummi/main/models.py index 223e544..9203718 100644 --- a/nummi/main/models.py +++ b/nummi/main/models.py @@ -23,16 +23,7 @@ class UserModel(models.Model): abstract = True -class NummiModel(UserModel): - @property - def adding(self): - return self._state.adding - - class Meta: - abstract = True - - -class Account(NummiModel): +class Account(UserModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name")) icon = models.SlugField( @@ -72,7 +63,7 @@ class Account(NummiModel): verbose_name_plural = _("Accounts") -class AccountModel(NummiModel): +class AccountModel(UserModel): account = models.ForeignKey( Account, on_delete=models.CASCADE, @@ -83,7 +74,7 @@ class AccountModel(NummiModel): abstract = True -class Category(NummiModel): +class Category(UserModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=64, default=_("Category"), verbose_name=_("Name") @@ -194,7 +185,7 @@ class Snapshot(AccountModel): verbose_name_plural = _("Statements") -class Transaction(NummiModel): +class Transaction(UserModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=256, default=_("Transaction"), verbose_name=_("Name") @@ -264,7 +255,7 @@ class Transaction(NummiModel): verbose_name_plural = _("Transactions") -class Invoice(NummiModel): +class Invoice(UserModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=256, default=_("Invoice"), verbose_name=_("Name") diff --git a/nummi/main/templates/main/form/account.html b/nummi/main/templates/main/form/account.html index 0125c77..5d16607 100644 --- a/nummi/main/templates/main/form/account.html +++ b/nummi/main/templates/main/form/account.html @@ -9,7 +9,7 @@ {% endblock %} {% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} {% block tables %} - {% if not form.instance.adding %} + {% if not form.instance|adding %}

    {% translate "Statements" %}

    {% include "main/table/snapshot.html" %} {% endif %} diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html index b79d2fe..def3515 100644 --- a/nummi/main/templates/main/form/base.html +++ b/nummi/main/templates/main/form/base.html @@ -3,7 +3,7 @@ {% load main_extras %} {% load i18n %} {% block title %} - {% if form.instance.adding %} + {% if form.instance|adding %} {% block title_new %}{% endblock %} {% else %} {{ form.instance }} @@ -24,7 +24,7 @@ {% endblock %} {% block body %} {% with instance=form.instance %} - {% if instance.adding %} + {% if instance|adding %}

    {% block h2_new %}{% endblock %}

    @@ -36,7 +36,7 @@ {% block pre %}{% endblock %} {% csrf_token %} - {% if instance.adding %}{% endif %} + {% if instance|adding %}{% endif %} {{ form }} {% block tables %}{% endblock %} diff --git a/nummi/main/templates/main/form/form_base.html b/nummi/main/templates/main/form/form_base.html index 5a76aba..7fb6952 100644 --- a/nummi/main/templates/main/form/form_base.html +++ b/nummi/main/templates/main/form/form_base.html @@ -1,4 +1,5 @@ {% load i18n %} +{% load main_extras %} {% block fields %} {% if form.non_field_errors %}
      @@ -25,11 +26,11 @@ {% block buttons %} - {% if not form.instance.adding %} + {% if not form.instance|adding %} {% translate "Delete" %} {% endif %} - {% if form.instance.adding %} + {% if form.instance|adding %} {% else %} diff --git a/nummi/main/templates/main/form/snapshot.html b/nummi/main/templates/main/form/snapshot.html index a76c300..feaf251 100644 --- a/nummi/main/templates/main/form/snapshot.html +++ b/nummi/main/templates/main/form/snapshot.html @@ -23,7 +23,7 @@

      {% translate "Categories" %}

      {% include "main/plot/category.html" %} {% endif %} - {% if not form.instance.adding %} + {% if not form.instance|adding %}

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

      {% include "main/table/transaction.html" %} {% endif %} diff --git a/nummi/main/templates/main/form/transaction.html b/nummi/main/templates/main/form/transaction.html index e586fc9..39e10fb 100644 --- a/nummi/main/templates/main/form/transaction.html +++ b/nummi/main/templates/main/form/transaction.html @@ -1,5 +1,6 @@ {% extends "main/form/base.html" %} {% load i18n %} +{% load main_extras %} {% block title_new %} {% translate "Create transaction" %} {% endblock %} @@ -14,7 +15,7 @@ {% endif %} {% endblock %} {% block tables %} - {% if not form.instance.adding %} + {% if not form.instance|adding %}

      {% translate "Invoices" %}

      {% include "main/table/invoice.html" %} {% endif %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 129c8a9..6b57178 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -54,3 +54,8 @@ def extension(file): @register.filter def verbose_name(obj): return obj._meta.verbose_name + + +@register.filter +def adding(obj): + return obj._state.adding From ad18226974d99116fcc2fc8d4c20670e9a4d7e31 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 09:49:17 +0200 Subject: [PATCH 055/276] Remove tests.py --- nummi/main/tests.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 nummi/main/tests.py diff --git a/nummi/main/tests.py b/nummi/main/tests.py deleted file mode 100644 index a39b155..0000000 --- a/nummi/main/tests.py +++ /dev/null @@ -1 +0,0 @@ -# Create your tests here. From a0d0b5d594f53c16d7deb2a3c2719631e115f0a0 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 09:58:38 +0200 Subject: [PATCH 056/276] Move media handling to new app --- .gitignore | 2 +- nummi/main/urls.py | 1 - nummi/main/views.py | 21 --------------------- nummi/media/__init__.py | 0 nummi/media/apps.py | 6 ++++++ nummi/media/urls.py | 7 +++++++ nummi/media/views.py | 22 ++++++++++++++++++++++ nummi/nummi/settings.py | 1 + nummi/nummi/urls.py | 1 + 9 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 nummi/media/__init__.py create mode 100644 nummi/media/apps.py create mode 100644 nummi/media/urls.py create mode 100644 nummi/media/views.py diff --git a/.gitignore b/.gitignore index 5b31142..542fc9c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ __pycache__ /nummi-git/ /pkg/ /src/ -/nummi/media +/media diff --git a/nummi/main/urls.py b/nummi/main/urls.py index 1db30b0..cb48dca 100644 --- a/nummi/main/urls.py +++ b/nummi/main/urls.py @@ -4,7 +4,6 @@ from . import views urlpatterns = [ path("", views.IndexView.as_view(), name="index"), - path("media/user//", views.MediaView.as_view(), name="media"), path("login", views.LoginView.as_view(), name="login"), path("logout", views.LogoutView.as_view(), name="logout"), path("transactions", views.TransactionListView.as_view(), name="transactions"), diff --git a/nummi/main/views.py b/nummi/main/views.py index b40c629..efadff2 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -1,4 +1,3 @@ -from django.conf import settings from django.contrib.auth import views as auth_views from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.postgres.search import ( @@ -7,12 +6,9 @@ from django.contrib.postgres.search import ( SearchVector, TrigramSimilarity, ) -from django.core.exceptions import PermissionDenied from django.db import models -from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse_lazy -from django.views import View from django.views.generic import ( CreateView, DeleteView, @@ -22,7 +18,6 @@ from django.views.generic import ( UpdateView, ) from django.views.generic.dates import MonthArchiveView -from django.views.static import serve from .forms import ( AccountForm, @@ -417,22 +412,6 @@ class SearchView(TransactionListView): return super().get_context_data(**kwargs) | {"search": self.kwargs["search"]} -class MediaView(LoginRequiredMixin, View): - def get(self, request, *args, **kwargs): - _username = kwargs.get("username") - _path = kwargs.get("path") - if request.user.get_username() != _username: - raise PermissionDenied - - if settings.DEBUG: - return serve(request, f"user/{_username}/{_path}", settings.MEDIA_ROOT) - - _res = HttpResponse() - _res["Content-Type"] = "" - _res["X-Accel-Redirect"] = f"/internal/media/user/{_username}/{_path}" - return _res - - class TransactionMonthView(UserMixin, MonthArchiveView): template_name = "main/month/transaction.html" model = Transaction diff --git a/nummi/media/__init__.py b/nummi/media/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/media/apps.py b/nummi/media/apps.py new file mode 100644 index 0000000..1afca3f --- /dev/null +++ b/nummi/media/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MediaConfig(AppConfig): + name = "media" + verbose_name = "Media" diff --git a/nummi/media/urls.py b/nummi/media/urls.py new file mode 100644 index 0000000..7537aed --- /dev/null +++ b/nummi/media/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("user//", views.MediaView.as_view(), name="media"), +] diff --git a/nummi/media/views.py b/nummi/media/views.py new file mode 100644 index 0000000..7164ef1 --- /dev/null +++ b/nummi/media/views.py @@ -0,0 +1,22 @@ +from django.conf import settings +from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.exceptions import PermissionDenied +from django.http import HttpResponse +from django.views import View +from django.views.static import serve + + +class MediaView(LoginRequiredMixin, View): + def get(self, request, *args, **kwargs): + _username = kwargs.get("username") + _path = kwargs.get("path") + if request.user.get_username() != _username: + raise PermissionDenied + + if settings.DEBUG: + return serve(request, f"user/{_username}/{_path}", settings.MEDIA_ROOT) + + _res = HttpResponse() + _res["Content-Type"] = "" + _res["X-Accel-Redirect"] = f"/internal/media/user/{_username}/{_path}" + return _res diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index c227a46..3acd55f 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -45,6 +45,7 @@ CSRF_TRUSTED_ORIGINS = CONFIG.get("trusted_origins", ["http://localhost"]) INSTALLED_APPS = [ "main.apps.MainConfig", + "media.apps.MediaConfig", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", diff --git a/nummi/nummi/urls.py b/nummi/nummi/urls.py index 683dc20..560f321 100644 --- a/nummi/nummi/urls.py +++ b/nummi/nummi/urls.py @@ -19,6 +19,7 @@ from django.urls import include, path urlpatterns = i18n_patterns( path("", include("main.urls")), + path("media/", include("media.urls")), path("admin/", admin.site.urls), prefix_default_language=False, ) From b05c3e67607b0b66ae3451710f01ed53fc8f6a3f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 11:16:42 +0200 Subject: [PATCH 057/276] Split backend in applications --- nummi/account/__init__.py | 0 nummi/account/apps.py | 6 + nummi/account/forms.py | 13 + nummi/account/migrations/0001_initial.py | 65 ++++ nummi/account/migrations/__init__.py | 0 nummi/account/models.py | 49 +++ nummi/account/urls.py | 33 ++ nummi/account/views.py | 68 ++++ nummi/category/__init__.py | 0 nummi/category/apps.py | 6 + nummi/category/forms.py | 13 + nummi/category/migrations/0001_initial.py | 64 ++++ nummi/category/migrations/__init__.py | 0 nummi/category/models.py | 33 ++ nummi/category/urls.py | 21 + nummi/category/views.py | 50 +++ nummi/main/forms.py | 101 ----- nummi/main/migrations/0001_v1.py | 6 +- nummi/main/migrations/0002_segmentation.py | 32 ++ nummi/main/models.py | 287 +------------- nummi/main/urls.py | 88 ----- nummi/main/views.py | 384 +------------------ nummi/media/utils.py | 10 + nummi/nummi/settings.py | 8 +- nummi/search/__init__.py | 0 nummi/search/apps.py | 6 + nummi/search/forms.py | 7 + nummi/search/migrations/__init__.py | 0 nummi/search/urls.py | 8 + nummi/search/views.py | 44 +++ nummi/statement/__init__.py | 0 nummi/statement/apps.py | 6 + nummi/statement/forms.py | 41 ++ nummi/statement/migrations/0001_initial.py | 127 ++++++ nummi/statement/migrations/__init__.py | 0 nummi/statement/models.py | 90 +++++ nummi/statement/urls.py | 24 ++ nummi/statement/views.py | 97 +++++ nummi/transaction/__init__.py | 0 nummi/transaction/apps.py | 6 + nummi/transaction/forms.py | 44 +++ nummi/transaction/migrations/0001_initial.py | 175 +++++++++ nummi/transaction/migrations/__init__.py | 0 nummi/transaction/models.py | 128 +++++++ nummi/transaction/urls.py | 34 ++ nummi/{main => transaction}/utils.py | 11 - nummi/transaction/views.py | 144 +++++++ 47 files changed, 1463 insertions(+), 866 deletions(-) create mode 100644 nummi/account/__init__.py create mode 100644 nummi/account/apps.py create mode 100644 nummi/account/forms.py create mode 100644 nummi/account/migrations/0001_initial.py create mode 100644 nummi/account/migrations/__init__.py create mode 100644 nummi/account/models.py create mode 100644 nummi/account/urls.py create mode 100644 nummi/account/views.py create mode 100644 nummi/category/__init__.py create mode 100644 nummi/category/apps.py create mode 100644 nummi/category/forms.py create mode 100644 nummi/category/migrations/0001_initial.py create mode 100644 nummi/category/migrations/__init__.py create mode 100644 nummi/category/models.py create mode 100644 nummi/category/urls.py create mode 100644 nummi/category/views.py create mode 100644 nummi/main/migrations/0002_segmentation.py create mode 100644 nummi/media/utils.py create mode 100644 nummi/search/__init__.py create mode 100644 nummi/search/apps.py create mode 100644 nummi/search/forms.py create mode 100644 nummi/search/migrations/__init__.py create mode 100644 nummi/search/urls.py create mode 100644 nummi/search/views.py create mode 100644 nummi/statement/__init__.py create mode 100644 nummi/statement/apps.py create mode 100644 nummi/statement/forms.py create mode 100644 nummi/statement/migrations/0001_initial.py create mode 100644 nummi/statement/migrations/__init__.py create mode 100644 nummi/statement/models.py create mode 100644 nummi/statement/urls.py create mode 100644 nummi/statement/views.py create mode 100644 nummi/transaction/__init__.py create mode 100644 nummi/transaction/apps.py create mode 100644 nummi/transaction/forms.py create mode 100644 nummi/transaction/migrations/0001_initial.py create mode 100644 nummi/transaction/migrations/__init__.py create mode 100644 nummi/transaction/models.py create mode 100644 nummi/transaction/urls.py rename nummi/{main => transaction}/utils.py (87%) create mode 100644 nummi/transaction/views.py diff --git a/nummi/account/__init__.py b/nummi/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/account/apps.py b/nummi/account/apps.py new file mode 100644 index 0000000..2c684a9 --- /dev/null +++ b/nummi/account/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "account" diff --git a/nummi/account/forms.py b/nummi/account/forms.py new file mode 100644 index 0000000..3225ab0 --- /dev/null +++ b/nummi/account/forms.py @@ -0,0 +1,13 @@ +from main.forms import NummiForm + +from .models import Account + + +class AccountForm(NummiForm): + class Meta: + model = Account + fields = [ + "name", + "icon", + "default", + ] diff --git a/nummi/account/migrations/0001_initial.py b/nummi/account/migrations/0001_initial.py new file mode 100644 index 0000000..bb9c8c8 --- /dev/null +++ b/nummi/account/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 4.1.4 on 2023-04-22 09:01 + +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("main", "0002_segmentation"), + ] + + state_operations = [ + migrations.CreateModel( + name="Account", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "name", + models.CharField( + default="Account", max_length=64, verbose_name="Name" + ), + ), + ( + "icon", + models.SlugField( + default="bank", max_length=24, verbose_name="Icon" + ), + ), + ("default", models.BooleanField(default=False, verbose_name="Default")), + ( + "user", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Account", + "verbose_name_plural": "Accounts", + "ordering": ["-default", "name"], + "db_table": "account_account", + }, + ), + ] + + operations = [ + migrations.SeparateDatabaseAndState(state_operations=state_operations) + ] diff --git a/nummi/account/migrations/__init__.py b/nummi/account/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/account/models.py b/nummi/account/models.py new file mode 100644 index 0000000..594f5e9 --- /dev/null +++ b/nummi/account/models.py @@ -0,0 +1,49 @@ +from uuid import uuid4 + +from django.db import models +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ +from main.models import UserModel + + +class Account(UserModel): + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) + name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name")) + icon = models.SlugField( + max_length=24, + default="bank", + verbose_name=_("Icon"), + ) + default = models.BooleanField(default=False, verbose_name=_("Default")) + + def save(self, *args, **kwargs): + if self.default: + for ac in Account.objects.filter(user=self.user, default=True): + ac.default = False + ac.save() + super().save(*args, **kwargs) + + def __str__(self): + return str(self.name) + + def get_absolute_url(self): + return reverse("account", kwargs={"pk": self.pk}) + + def get_delete_url(self): + return reverse("del_account", kwargs={"pk": self.pk}) + + class Meta: + ordering = ["-default", "name"] + verbose_name = _("Account") + verbose_name_plural = _("Accounts") + + +class AccountModel(UserModel): + account = models.ForeignKey( + Account, + on_delete=models.CASCADE, + verbose_name=_("Account"), + ) + + class Meta: + abstract = True diff --git a/nummi/account/urls.py b/nummi/account/urls.py new file mode 100644 index 0000000..b5158ea --- /dev/null +++ b/nummi/account/urls.py @@ -0,0 +1,33 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("account", views.AccountCreateView.as_view(), name="new_account"), + path("account/", views.AccountUpdateView.as_view(), name="account"), + path( + "account//transactions", + views.AccountTListView.as_view(), + name="account_transactions", + ), + path( + "account//statements", + views.AccountSListView.as_view(), + name="account_statements", + ), + path( + "account//statement", + views.StatementCreateView.as_view(), + name="new_statement", + ), + path( + "account//delete", + views.AccountDeleteView.as_view(), + name="del_account", + ), + path( + "account//history//", + views.TransactionMonthView.as_view(), + name="transaction_month", + ), +] diff --git a/nummi/account/views.py b/nummi/account/views.py new file mode 100644 index 0000000..576d153 --- /dev/null +++ b/nummi/account/views.py @@ -0,0 +1,68 @@ +from django.urls import reverse_lazy +from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView +from snapshot.views import SnapshotListView +from transaction.utils import history +from transaction.views import TransactionListView + +from .forms import AccountForm +from .models import Account + + +class AccountCreateView(NummiCreateView): + model = Account + form_class = AccountForm + template_name = "main/form/account.html" + + +class AccountUpdateView(NummiUpdateView): + model = Account + form_class = AccountForm + template_name = "main/form/account.html" + + def get_context_data(self, **kwargs): + _max = 8 + data = super().get_context_data(**kwargs) + account = data["form"].instance + + _transactions = account.transaction_set.all() + if _transactions.count() > _max: + data["transactions_url"] = reverse_lazy( + "account_transactions", args=(account.pk,) + ) + _snapshots = account.snapshot_set.all() + if _snapshots.count() > _max: + data["snapshots_url"] = reverse_lazy( + "account_snapshots", args=(account.pk,) + ) + + return data | { + "transactions": _transactions[:8], + "new_snapshot_url": reverse_lazy( + "new_snapshot", kwargs={"account": account.pk} + ), + "snapshots": _snapshots[:8], + "history": history(account.transaction_set), + } + + +class AccountDeleteView(NummiDeleteView): + model = Account + + +class AccountMixin: + def get_queryset(self): + return super().get_queryset().filter(account=self.kwargs.get("pk")) + + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) | { + "object": Account.objects.get(pk=self.kwargs.get("pk")), + "account": True, + } + + +class AccountTListView(AccountMixin, TransactionListView): + pass + + +class AccountSListView(AccountMixin, SnapshotListView): + pass diff --git a/nummi/category/__init__.py b/nummi/category/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/category/apps.py b/nummi/category/apps.py new file mode 100644 index 0000000..e953ee6 --- /dev/null +++ b/nummi/category/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CategoryConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "category" diff --git a/nummi/category/forms.py b/nummi/category/forms.py new file mode 100644 index 0000000..b0bbf27 --- /dev/null +++ b/nummi/category/forms.py @@ -0,0 +1,13 @@ +from main.forms import NummiForm + +from .models import Category + + +class CategoryForm(NummiForm): + class Meta: + model = Category + fields = [ + "name", + "icon", + "budget", + ] diff --git a/nummi/category/migrations/0001_initial.py b/nummi/category/migrations/0001_initial.py new file mode 100644 index 0000000..e4a71d2 --- /dev/null +++ b/nummi/category/migrations/0001_initial.py @@ -0,0 +1,64 @@ +# Generated by Django 4.1.4 on 2023-04-22 09:01 + +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + state_operations = [ + migrations.CreateModel( + name="Category", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "name", + models.CharField( + default="Category", max_length=64, verbose_name="Name" + ), + ), + ( + "icon", + models.SlugField( + default="folder", max_length=24, verbose_name="Icon" + ), + ), + ("budget", models.BooleanField(default=True, verbose_name="Budget")), + ( + "user", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Category", + "verbose_name_plural": "Categories", + "ordering": ["name"], + "db_table": "category_category", + }, + ), + ] + + operations = [ + migrations.SeparateDatabaseAndState(state_operations=state_operations) + ] diff --git a/nummi/category/migrations/__init__.py b/nummi/category/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/category/models.py b/nummi/category/models.py new file mode 100644 index 0000000..845600b --- /dev/null +++ b/nummi/category/models.py @@ -0,0 +1,33 @@ +from uuid import uuid4 + +from django.db import models +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ +from main.models import UserModel + + +class Category(UserModel): + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) + name = models.CharField( + max_length=64, default=_("Category"), verbose_name=_("Name") + ) + icon = models.SlugField( + max_length=24, + default="folder", + verbose_name=_("Icon"), + ) + budget = models.BooleanField(default=True, verbose_name=_("Budget")) + + def __str__(self): + return str(self.name) + + def get_absolute_url(self): + return reverse("category", kwargs={"pk": self.pk}) + + def get_delete_url(self): + return reverse("del_category", kwargs={"pk": self.pk}) + + class Meta: + ordering = ["name"] + verbose_name = _("Category") + verbose_name_plural = _("Categories") diff --git a/nummi/category/urls.py b/nummi/category/urls.py new file mode 100644 index 0000000..113aac9 --- /dev/null +++ b/nummi/category/urls.py @@ -0,0 +1,21 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("category", views.CategoryCreateView.as_view(), name="new_category"), + path("category/", views.CategoryUpdateView.as_view(), name="category"), + path( + "category//transactions", + views.CategoryTListView.as_view(), + name="category_transactions", + ), + path( + "category//delete", views.CategoryDeleteView.as_view(), name="del_category" + ), + path( + "category//history//", + views.TransactionMonthView.as_view(), + name="transaction_month", + ), +] diff --git a/nummi/category/views.py b/nummi/category/views.py new file mode 100644 index 0000000..e84ac96 --- /dev/null +++ b/nummi/category/views.py @@ -0,0 +1,50 @@ +from django.urls import reverse_lazy +from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView +from transaction.utils import history +from transaction.views import TransactionListView + +from .forms import CategoryForm +from .models import Category + + +class CategoryCreateView(NummiCreateView): + model = Category + form_class = CategoryForm + template_name = "main/form/category.html" + + +class CategoryUpdateView(NummiUpdateView): + model = Category + form_class = CategoryForm + template_name = "main/form/category.html" + + def get_context_data(self, **kwargs): + data = super().get_context_data(**kwargs) + category = data["form"].instance + + return data | { + "transactions": category.transaction_set.all()[:8], + "transactions_url": reverse_lazy( + "category_transactions", args=(category.pk,) + ), + "history": history(category.transaction_set), + } + + +class CategoryDeleteView(NummiDeleteView): + model = Category + + +class CategoryMixin: + def get_queryset(self): + return super().get_queryset().filter(category=self.kwargs.get("pk")) + + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) | { + "object": Category.objects.get(pk=self.kwargs.get("pk")), + "category": True, + } + + +class CategoryTListView(CategoryMixin, TransactionListView): + pass diff --git a/nummi/main/forms.py b/nummi/main/forms.py index 2fc2f8a..449720f 100644 --- a/nummi/main/forms.py +++ b/nummi/main/forms.py @@ -1,7 +1,4 @@ from django import forms -from django.utils.translation import gettext_lazy as _ - -from .models import Account, Category, Invoice, Snapshot, Transaction class NummiFileInput(forms.ClearableFileInput): @@ -13,101 +10,3 @@ class NummiForm(forms.ModelForm): def __init__(self, *args, user, **kwargs): super().__init__(*args, **kwargs) - - -class AccountForm(NummiForm): - class Meta: - model = Account - fields = [ - "name", - "icon", - "default", - ] - - -class CategoryForm(NummiForm): - class Meta: - model = Category - fields = [ - "name", - "icon", - "budget", - ] - - -class TransactionForm(NummiForm): - class Meta: - model = Transaction - fields = [ - "snapshot", - "name", - "value", - "date", - "real_date", - "category", - "trader", - "payment", - "description", - ] - - def __init__(self, *args, **kwargs): - _user = kwargs.get("user") - _disable_snapshot = kwargs.pop("disable_snapshot", False) - super().__init__(*args, **kwargs) - self.fields["category"].queryset = Category.objects.filter(user=_user) - self.fields["snapshot"].queryset = Snapshot.objects.filter(user=_user) - if _disable_snapshot: - self.fields["snapshot"].disabled = True - - -class InvoiceForm(NummiForm): - prefix = "invoice" - - class Meta: - model = Invoice - fields = [ - "name", - "file", - ] - widgets = { - "file": NummiFileInput, - } - - -class SnapshotForm(NummiForm): - class Meta: - model = Snapshot - fields = ["account", "start_date", "date", "start_value", "value", "file"] - widgets = { - "file": NummiFileInput, - } - - def __init__(self, *args, **kwargs): - _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["transactions"] = forms.MultipleChoiceField( - choices=( - ((_transaction.id), _transaction) - for _transaction in Transaction.objects.filter(user=_user) - ), - label=_("Add transactions"), - required=False, - ) - if _disable_account: - self.fields["account"].disabled = True - - def save(self, *args, **kwargs): - instance = super().save(*args, **kwargs) - new_transactions = Transaction.objects.filter( - id__in=self.cleaned_data["transactions"] - ) - - instance.transaction_set.add(*new_transactions, bulk=False) - return instance - - -class SearchForm(forms.Form): - template_name = "main/form/search.html" - search = forms.CharField(label=_("Search"), max_length=128) diff --git a/nummi/main/migrations/0001_v1.py b/nummi/main/migrations/0001_v1.py index 2eb2ac7..98b9585 100644 --- a/nummi/main/migrations/0001_v1.py +++ b/nummi/main/migrations/0001_v1.py @@ -6,7 +6,7 @@ import uuid import django.contrib.postgres.operations import django.core.validators import django.db.models.deletion -import main.utils +import media.utils from django.conf import settings from django.db import migrations, models @@ -136,7 +136,7 @@ class Migration(migrations.Migration): blank=True, default="", max_length=256, - upload_to=main.utils.get_path, + upload_to=media.utils.get_path, validators=[ django.core.validators.FileExtensionValidator(["pdf"]) ], @@ -315,7 +315,7 @@ class Migration(migrations.Migration): "file", models.FileField( max_length=128, - upload_to=main.utils.get_path, + upload_to=media.utils.get_path, validators=[ django.core.validators.FileExtensionValidator(["pdf"]) ], diff --git a/nummi/main/migrations/0002_segmentation.py b/nummi/main/migrations/0002_segmentation.py new file mode 100644 index 0000000..ba9052b --- /dev/null +++ b/nummi/main/migrations/0002_segmentation.py @@ -0,0 +1,32 @@ +# Generated by Django 4.1.4 on 2023-04-22 09:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("main", "0001_v1"), + ] + + database_operations = [ + migrations.AlterModelTable("Account", "account_account"), + migrations.AlterModelTable("Category", "category_category"), + migrations.AlterModelTable("Snapshot", "statement_statement"), + migrations.AlterModelTable("Transaction", "transaction_transaction"), + migrations.AlterModelTable("Invoice", "transaction_invoice"), + ] + + state_operations = [ + migrations.DeleteModel("Account"), + migrations.DeleteModel("Category"), + migrations.DeleteModel("Snapshot"), + migrations.DeleteModel("Transaction"), + migrations.DeleteModel("Invoice"), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + database_operations=database_operations, + state_operations=state_operations, + ), + ] diff --git a/nummi/main/models.py b/nummi/main/models.py index 9203718..5ae4f2c 100644 --- a/nummi/main/models.py +++ b/nummi/main/models.py @@ -1,15 +1,7 @@ -import datetime -import pathlib -import uuid - from django.conf import settings -from django.core.validators import FileExtensionValidator -from django.db import models, transaction -from django.urls import reverse +from django.db import models from django.utils.translation import gettext_lazy as _ -from .utils import get_path - class UserModel(models.Model): user = models.ForeignKey( @@ -21,280 +13,3 @@ class UserModel(models.Model): class Meta: abstract = True - - -class Account(UserModel): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name")) - icon = models.SlugField( - max_length=24, - default="bank", - verbose_name=_("Icon"), - ) - default = models.BooleanField(default=False, verbose_name=_("Default")) - - def save(self, *args, **kwargs): - if self.default: - for ac in Account.objects.filter(user=self.user, default=True): - ac.default = False - ac.save() - super().save(*args, **kwargs) - - def __str__(self): - return str(self.name) - - def get_absolute_url(self): - return reverse("account", kwargs={"pk": self.pk}) - - def get_delete_url(self): - return reverse("del_account", kwargs={"pk": self.pk}) - - @property - def transactions(self): - return Transaction.objects.filter(account=self) - - @property - def snapshots(self): - return Snapshot.objects.filter(account=self) - - class Meta: - ordering = ["-default", "name"] - verbose_name = _("Account") - verbose_name_plural = _("Accounts") - - -class AccountModel(UserModel): - account = models.ForeignKey( - Account, - on_delete=models.CASCADE, - verbose_name=_("Account"), - ) - - class Meta: - abstract = True - - -class Category(UserModel): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - name = models.CharField( - max_length=64, default=_("Category"), verbose_name=_("Name") - ) - icon = models.SlugField( - max_length=24, - default="folder", - verbose_name=_("Icon"), - ) - budget = models.BooleanField(default=True, verbose_name=_("Budget")) - - def __str__(self): - return str(self.name) - - def get_absolute_url(self): - return reverse("category", kwargs={"pk": self.pk}) - - def get_delete_url(self): - return reverse("del_category", kwargs={"pk": self.pk}) - - @property - def transactions(self): - return Transaction.objects.filter(category=self) - - class Meta: - ordering = ["name"] - verbose_name = _("Category") - verbose_name_plural = _("Categories") - - -class Snapshot(AccountModel): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - date = models.DateField(default=datetime.date.today, verbose_name=_("End date")) - start_date = models.DateField( - default=datetime.date.today, verbose_name=_("Start date") - ) - value = models.DecimalField( - max_digits=12, decimal_places=2, default=0, verbose_name=_("End value") - ) - 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"])], - verbose_name=_("File"), - max_length=256, - blank=True, - default="", - ) - - def __str__(self): - desc = _("%(date)s statement") % {"date": self.date} - if hasattr(self, "account"): - return f"{desc} – {self.account}" - return desc - - def save(self, *args, **kwargs): - if Snapshot.objects.filter(id=self.id).exists(): - _prever = Snapshot.objects.get(id=self.id) - if _prever.file and _prever.file != self.file: - pathlib.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 - ) - super().save() - - def delete(self, *args, **kwargs): - if self.file: - self.file.delete() - super().delete(*args, **kwargs) - - def get_absolute_url(self): - return reverse("snapshot", kwargs={"pk": self.pk}) - - def get_delete_url(self): - return reverse("del_snapshot", kwargs={"pk": self.pk}) - - class Meta: - ordering = ["-date", "account"] - verbose_name = _("Statement") - verbose_name_plural = _("Statements") - - -class Transaction(UserModel): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - name = models.CharField( - max_length=256, default=_("Transaction"), verbose_name=_("Name") - ) - description = models.TextField(null=True, blank=True, verbose_name=_("Description")) - value = models.DecimalField( - max_digits=12, decimal_places=2, default=0, verbose_name=_("Value") - ) - date = models.DateField(default=datetime.date.today, verbose_name=_("Date")) - real_date = models.DateField(blank=True, null=True, verbose_name=_("Real date")) - trader = models.CharField( - max_length=128, blank=True, null=True, verbose_name=_("Trader") - ) - payment = models.CharField( - max_length=128, blank=True, null=True, verbose_name=_("Payment") - ) - category = models.ForeignKey( - Category, - on_delete=models.SET_NULL, - blank=True, - null=True, - verbose_name=_("Category"), - ) - snapshot = models.ForeignKey( - Snapshot, - on_delete=models.CASCADE, - verbose_name=_("Statement"), - ) - account = models.ForeignKey( - Account, - on_delete=models.CASCADE, - 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 - self.account = self.snapshot.account - super().save(*args, **kwargs) - if prev_self is not None: - prev_self.snapshot.update_sum() - self.snapshot.update_sum() - - def __str__(self): - return f"{self.name}" - - def get_absolute_url(self): - return reverse("transaction", kwargs={"pk": self.pk}) - - def get_delete_url(self): - return reverse("del_transaction", kwargs={"pk": self.pk}) - - @property - def invoices(self): - return Invoice.objects.filter(transaction=self) - - @property - def has_invoice(self): - return self.invoices.count() > 0 - - class Meta: - ordering = ["-date", "snapshot"] - verbose_name = _("Transaction") - verbose_name_plural = _("Transactions") - - -class Invoice(UserModel): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - name = models.CharField( - max_length=256, default=_("Invoice"), verbose_name=_("Name") - ) - file = models.FileField( - upload_to=get_path, - validators=[FileExtensionValidator(["pdf"])], - verbose_name=_("File"), - max_length=128, - ) - transaction = models.ForeignKey( - Transaction, on_delete=models.CASCADE, editable=False - ) - - def save(self, *args, **kwargs): - if Invoice.objects.filter(id=self.id).exists(): - _prever = Invoice.objects.get(id=self.id) - if _prever.file and _prever.file != self.file: - pathlib.Path(_prever.file.path).unlink(missing_ok=True) - super().save(*args, **kwargs) - - def __str__(self): - return str(self.name) - - def delete(self, *args, **kwargs): - self.file.delete() - super().delete(*args, **kwargs) - - def get_absolute_url(self): - return reverse( - "invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk} - ) - - def get_delete_url(self): - return reverse( - "del_invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk} - ) - - class Meta: - verbose_name = _("Invoice") - verbose_name_plural = _("Invoices") - ordering = ["transaction", "name"] diff --git a/nummi/main/urls.py b/nummi/main/urls.py index cb48dca..3b911ad 100644 --- a/nummi/main/urls.py +++ b/nummi/main/urls.py @@ -6,92 +6,4 @@ urlpatterns = [ path("", views.IndexView.as_view(), name="index"), path("login", views.LoginView.as_view(), name="login"), path("logout", views.LogoutView.as_view(), name="logout"), - path("transactions", views.TransactionListView.as_view(), name="transactions"), - path("snapshots", views.SnapshotListView.as_view(), name="snapshots"), - path("account", views.AccountCreateView.as_view(), name="new_account"), - path("transaction", views.TransactionCreateView.as_view(), name="new_transaction"), - path( - "transaction//invoice", - views.InvoiceCreateView.as_view(), - name="new_invoice", - ), - path("category", views.CategoryCreateView.as_view(), name="new_category"), - path("snapshot", views.SnapshotCreateView.as_view(), name="new_snapshot"), - path("account/", views.AccountUpdateView.as_view(), name="account"), - path( - "account//transactions", - views.AccountTListView.as_view(), - name="account_transactions", - ), - path( - "account//snapshots", - views.AccountSListView.as_view(), - name="account_snapshots", - ), - path( - "account//snapshot", - views.SnapshotCreateView.as_view(), - name="new_snapshot", - ), - path("transaction/", views.TransactionUpdateView.as_view(), name="transaction"), - path( - "transaction//invoice/", - views.InvoiceUpdateView.as_view(), - name="invoice", - ), - path("category/", views.CategoryUpdateView.as_view(), name="category"), - path( - "category//transactions", - views.CategoryTListView.as_view(), - name="category_transactions", - ), - path("snapshot/", views.SnapshotUpdateView.as_view(), name="snapshot"), - path( - "snapshot//transactions", - views.SnapshotTListView.as_view(), - name="snapshot_transactions", - ), - path( - "snapshot//transaction", - views.TransactionCreateView.as_view(), - name="new_transaction", - ), - path( - "account//delete", - views.AccountDeleteView.as_view(), - name="del_account", - ), - path( - "transaction//delete", - views.TransactionDeleteView.as_view(), - name="del_transaction", - ), - path( - "transaction//invoice//delete", - views.InvoiceDeleteView.as_view(), - name="del_invoice", - ), - path( - "category//delete", views.CategoryDeleteView.as_view(), name="del_category" - ), - path( - "snapshot//delete", views.SnapshotDeleteView.as_view(), name="del_snapshot" - ), - path("search", views.SearchFormView.as_view(), name="search"), - path("search/", views.SearchView.as_view(), name="search"), - path( - "history//", - views.TransactionMonthView.as_view(), - name="transaction_month", - ), - path( - "account//history//", - views.TransactionMonthView.as_view(), - name="transaction_month", - ), - path( - "category//history//", - views.TransactionMonthView.as_view(), - name="transaction_month", - ), ] diff --git a/nummi/main/views.py b/nummi/main/views.py index efadff2..7ede354 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -1,34 +1,18 @@ +from account.models import Account +from category.models import Category from django.contrib.auth import views as auth_views from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.postgres.search import ( - SearchQuery, - SearchRank, - SearchVector, - TrigramSimilarity, -) -from django.db import models -from django.shortcuts import get_object_or_404, redirect from django.urls import reverse_lazy from django.views.generic import ( CreateView, DeleteView, - FormView, ListView, TemplateView, UpdateView, ) -from django.views.generic.dates import MonthArchiveView - -from .forms import ( - AccountForm, - CategoryForm, - InvoiceForm, - SearchForm, - SnapshotForm, - TransactionForm, -) -from .models import Account, Category, Invoice, Snapshot, Transaction -from .utils import history +from statement.models import Statement +from transaction.models import Transaction +from transaction.utils import history class IndexView(LoginRequiredMixin, TemplateView): @@ -37,19 +21,19 @@ class IndexView(LoginRequiredMixin, TemplateView): def get_context_data(self, **kwargs): _max = 8 _transactions = Transaction.objects.filter(user=self.request.user) - _snapshots = Snapshot.objects.filter(user=self.request.user) + _statements = Statement.objects.filter(user=self.request.user) res = { "accounts": Account.objects.filter(user=self.request.user), "transactions": _transactions[:_max], "categories": Category.objects.filter(user=self.request.user), - "snapshots": _snapshots[:_max], + "statements": _statements[:_max], "history": history(_transactions.exclude(category__budget=False)), } if _transactions.count() > _max: res["transactions_url"] = reverse_lazy("transactions") - if _snapshots.count() > _max: - res["snapshots_url"] = reverse_lazy("snapshots") + if _statements.count() > _max: + res["statements_url"] = reverse_lazy("statements") return super().get_context_data(**kwargs) | res @@ -92,355 +76,5 @@ class LogoutView(auth_views.LogoutView): next_page = "login" -class AccountCreateView(NummiCreateView): - model = Account - form_class = AccountForm - template_name = "main/form/account.html" - - -class TransactionCreateView(NummiCreateView): - model = Transaction - form_class = TransactionForm - template_name = "main/form/transaction.html" - - def get_initial(self): - _queryset = Snapshot.objects.filter(user=self.request.user) - if "snapshot" in self.kwargs: - self.snapshot = get_object_or_404(_queryset, pk=self.kwargs["snapshot"]) - else: - self.snapshot = _queryset.first() - return {"snapshot": self.snapshot} - - def get_form_kwargs(self): - if "snapshot" in self.kwargs: - return super().get_form_kwargs() | {"disable_snapshot": True} - return super().get_form_kwargs() - - def get_context_data(self, **kwargs): - if "snapshot" in self.kwargs: - return super().get_context_data(**kwargs) | {"snapshot": self.snapshot} - return super().get_context_data(**kwargs) - - -class InvoiceCreateView(NummiCreateView): - model = Invoice - form_class = InvoiceForm - template_name = "main/form/invoice.html" - - def form_valid(self, form): - form.instance.transaction = get_object_or_404( - Transaction.objects.filter(user=self.request.user), - pk=self.kwargs["transaction_pk"], - ) - return super().form_valid(form) - - def get_success_url(self): - return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk}) - - -class CategoryCreateView(NummiCreateView): - model = Category - form_class = CategoryForm - template_name = "main/form/category.html" - - -class SnapshotCreateView(NummiCreateView): - model = Snapshot - form_class = SnapshotForm - template_name = "main/form/snapshot.html" - - def get_initial(self): - _queryset = Account.objects.filter(user=self.request.user) - if "account" in self.kwargs: - self.account = get_object_or_404(_queryset, pk=self.kwargs["account"]) - else: - self.account = _queryset.first() - return {"account": self.account} - - def get_form_kwargs(self): - if "account" in self.kwargs: - return super().get_form_kwargs() | {"disable_account": True} - return super().get_form_kwargs() - - def get_context_data(self, **kwargs): - if "account" in self.kwargs: - return super().get_context_data(**kwargs) | {"account": self.account} - return super().get_context_data(**kwargs) - - -class AccountUpdateView(NummiUpdateView): - model = Account - form_class = AccountForm - template_name = "main/form/account.html" - - def get_context_data(self, **kwargs): - _max = 8 - data = super().get_context_data(**kwargs) - account = data["form"].instance - - _transactions = account.transaction_set.all() - if _transactions.count() > _max: - data["transactions_url"] = reverse_lazy( - "account_transactions", args=(account.pk,) - ) - _snapshots = account.snapshot_set.all() - if _snapshots.count() > _max: - data["snapshots_url"] = reverse_lazy( - "account_snapshots", args=(account.pk,) - ) - - return data | { - "transactions": _transactions[:8], - "new_snapshot_url": reverse_lazy( - "new_snapshot", kwargs={"account": account.pk} - ), - "snapshots": _snapshots[:8], - "history": history(account.transaction_set), - } - - -class TransactionUpdateView(NummiUpdateView): - model = Transaction - form_class = TransactionForm - template_name = "main/form/transaction.html" - - -class InvoiceUpdateView(NummiUpdateView): - model = Invoice - form_class = InvoiceForm - template_name = "main/form/invoice.html" - - def get_success_url(self): - return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk}) - - def get_queryset(self): - return ( - super() - .get_queryset() - .filter( - transaction=get_object_or_404( - Transaction, pk=self.kwargs["transaction_pk"] - ) - ) - ) - - -class CategoryUpdateView(NummiUpdateView): - model = Category - form_class = CategoryForm - template_name = "main/form/category.html" - - def get_context_data(self, **kwargs): - data = super().get_context_data(**kwargs) - category = data["form"].instance - - return data | { - "transactions": category.transaction_set.all()[:8], - "transactions_url": reverse_lazy( - "category_transactions", args=(category.pk,) - ), - "history": history(category.transaction_set), - } - - -class SnapshotUpdateView(NummiUpdateView): - model = Snapshot - form_class = SnapshotForm - template_name = "main/form/snapshot.html" - - def get_context_data(self, **kwargs): - data = super().get_context_data(**kwargs) - snapshot = data["form"].instance - - _transactions = snapshot.transaction_set.all() - if _transactions: - _categories = ( - _transactions.values("category", "category__name", "category__icon") - .annotate( - sum=models.Sum("value"), - sum_m=models.Sum("value", filter=models.Q(value__lt=0)), - sum_p=models.Sum("value", filter=models.Q(value__gt=0)), - ) - .order_by("-sum") - ) - data["categories"] = { - "data": _categories, - "max": max( - _categories.aggregate( - max=models.Max("sum_p", default=0), - min=models.Min("sum_m", default=0), - ).values(), - ), - } - - return data | { - "account": snapshot.account, - "new_transaction_url": reverse_lazy( - "new_transaction", kwargs={"snapshot": snapshot.pk} - ), - "transactions": _transactions, - } - - -class AccountDeleteView(NummiDeleteView): - model = Account - - -class TransactionDeleteView(NummiDeleteView): - model = Transaction - - -class InvoiceDeleteView(NummiDeleteView): - model = Invoice - - def get_success_url(self): - return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk}) - - def get_queryset(self): - return ( - super() - .get_queryset() - .filter( - transaction=get_object_or_404( - Transaction, pk=self.kwargs["transaction_pk"] - ) - ) - ) - - -class CategoryDeleteView(NummiDeleteView): - model = Category - - -class SnapshotDeleteView(NummiDeleteView): - model = Snapshot - - class NummiListView(UserMixin, ListView): paginate_by = 96 - - -class TransactionListView(NummiListView): - model = Transaction - template_name = "main/list/transaction.html" - context_object_name = "transactions" - - -class SnapshotListView(NummiListView): - model = Snapshot - template_name = "main/list/snapshot.html" - context_object_name = "snapshots" - - -class AccountMixin: - def get_queryset(self): - return super().get_queryset().filter(account=self.kwargs.get("pk")) - - def get_context_data(self, **kwargs): - return super().get_context_data(**kwargs) | { - "object": Account.objects.get(pk=self.kwargs.get("pk")), - "account": True, - } - - -class SnapshotMixin: - def get_queryset(self): - return super().get_queryset().filter(snapshot=self.kwargs.get("pk")) - - def get_context_data(self, **kwargs): - return super().get_context_data(**kwargs) | { - "object": Snapshot.objects.get(pk=self.kwargs.get("pk")), - "snapshot": True, - } - - -class CategoryMixin: - def get_queryset(self): - return super().get_queryset().filter(category=self.kwargs.get("pk")) - - def get_context_data(self, **kwargs): - return super().get_context_data(**kwargs) | { - "object": Category.objects.get(pk=self.kwargs.get("pk")), - "category": True, - } - - -class AccountTListView(AccountMixin, TransactionListView): - pass - - -class AccountSListView(AccountMixin, SnapshotListView): - pass - - -class SnapshotTListView(SnapshotMixin, TransactionListView): - pass - - -class CategoryTListView(CategoryMixin, TransactionListView): - pass - - -class SearchFormView(LoginRequiredMixin, FormView): - template_name = "main/search.html" - form_class = SearchForm - - def form_valid(self, form): - return redirect("search", search=form.cleaned_data.get("search")) - - -class SearchView(TransactionListView): - def get_queryset(self): - self.search = self.kwargs["search"] - return ( - super() - .get_queryset() - .annotate( - rank=SearchRank( - SearchVector("name", weight="A") - + SearchVector("description", weight="B") - + SearchVector("trader", weight="B"), - SearchQuery(self.search, search_type="websearch"), - ), - similarity=TrigramSimilarity("name", self.search), - ) - .filter(models.Q(rank__gte=0.1) | models.Q(similarity__gte=0.3)) - .order_by("-rank", "-date") - ) - - def get_context_data(self, **kwargs): - return super().get_context_data(**kwargs) | {"search": self.kwargs["search"]} - - -class TransactionMonthView(UserMixin, MonthArchiveView): - template_name = "main/month/transaction.html" - model = Transaction - date_field = "date" - context_object_name = "transactions" - month_format = "%m" - - account = None - category = None - - def get_queryset(self): - if "account" in self.kwargs: - self.account = get_object_or_404( - Account.objects.filter(user=self.request.user), - pk=self.kwargs["account"], - ) - return super().get_queryset().filter(account=self.account) - if "category" in self.kwargs: - self.category = get_object_or_404( - Category.objects.filter(user=self.request.user), - pk=self.kwargs["category"], - ) - return super().get_queryset().filter(category=self.category) - - return super().get_queryset() - - def get_context_data(self, **kwargs): - if "account" in self.kwargs: - return super().get_context_data(**kwargs) | {"account": self.account} - if "category" in self.kwargs: - return super().get_context_data(**kwargs) | {"category": self.category} - return super().get_context_data(**kwargs) diff --git a/nummi/media/utils.py b/nummi/media/utils.py new file mode 100644 index 0000000..533f017 --- /dev/null +++ b/nummi/media/utils.py @@ -0,0 +1,10 @@ +import pathlib + + +def get_path(instance, filename): + return pathlib.Path( + "user", + str(instance.user.username), + instance._meta.model_name, + str(instance.pk), + ).with_suffix(".pdf") diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index 3acd55f..a2cc1e9 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -44,8 +44,12 @@ CSRF_TRUSTED_ORIGINS = CONFIG.get("trusted_origins", ["http://localhost"]) # Application definition INSTALLED_APPS = [ - "main.apps.MainConfig", - "media.apps.MediaConfig", + "main", + "media", + "account", + "category", + "statement", + "transaction", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", diff --git a/nummi/search/__init__.py b/nummi/search/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/search/apps.py b/nummi/search/apps.py new file mode 100644 index 0000000..1c3a606 --- /dev/null +++ b/nummi/search/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SearchConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "search" diff --git a/nummi/search/forms.py b/nummi/search/forms.py new file mode 100644 index 0000000..70d6e60 --- /dev/null +++ b/nummi/search/forms.py @@ -0,0 +1,7 @@ +from django import forms +from django.utils.translation import gettext_lazy as _ + + +class SearchForm(forms.Form): + template_name = "main/form/search.html" + search = forms.CharField(label=_("Search"), max_length=128) diff --git a/nummi/search/migrations/__init__.py b/nummi/search/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/search/urls.py b/nummi/search/urls.py new file mode 100644 index 0000000..fb383ab --- /dev/null +++ b/nummi/search/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("search", views.SearchFormView.as_view(), name="search"), + path("search/", views.SearchView.as_view(), name="search"), +] diff --git a/nummi/search/views.py b/nummi/search/views.py new file mode 100644 index 0000000..c41965c --- /dev/null +++ b/nummi/search/views.py @@ -0,0 +1,44 @@ +from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.postgres.search import ( + SearchQuery, + SearchRank, + SearchVector, + TrigramSimilarity, +) +from django.db import models +from django.shortcuts import redirect +from django.views.generic.edit import FormView +from transaction.views import TransactionListView + +from .forms import SearchForm + + +class SearchFormView(LoginRequiredMixin, FormView): + template_name = "main/search.html" + form_class = SearchForm + + def form_valid(self, form): + return redirect("search", search=form.cleaned_data.get("search")) + + +class SearchView(TransactionListView): + def get_queryset(self): + self.search = self.kwargs["search"] + return ( + super() + .get_queryset() + .annotate( + rank=SearchRank( + SearchVector("name", weight="A") + + SearchVector("description", weight="B") + + SearchVector("trader", weight="B"), + SearchQuery(self.search, search_type="websearch"), + ), + similarity=TrigramSimilarity("name", self.search), + ) + .filter(models.Q(rank__gte=0.1) | models.Q(similarity__gte=0.3)) + .order_by("-rank", "-date") + ) + + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) | {"search": self.kwargs["search"]} diff --git a/nummi/statement/__init__.py b/nummi/statement/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/statement/apps.py b/nummi/statement/apps.py new file mode 100644 index 0000000..b94c1a5 --- /dev/null +++ b/nummi/statement/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class StatementConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "statement" diff --git a/nummi/statement/forms.py b/nummi/statement/forms.py new file mode 100644 index 0000000..328d921 --- /dev/null +++ b/nummi/statement/forms.py @@ -0,0 +1,41 @@ +from account.models import Account +from django import forms +from django.utils.translation import gettext_lazy as _ +from main.forms import NummiFileInput, NummiForm +from transaction.models import Transaction + +from .models import Statement + + +class StatementForm(NummiForm): + class Meta: + model = Statement + fields = ["account", "start_date", "date", "start_value", "value", "file"] + widgets = { + "file": NummiFileInput, + } + + def __init__(self, *args, **kwargs): + _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["transactions"] = forms.MultipleChoiceField( + choices=( + ((_transaction.id), _transaction) + for _transaction in Transaction.objects.filter(user=_user) + ), + label=_("Add transactions"), + required=False, + ) + if _disable_account: + self.fields["account"].disabled = True + + def save(self, *args, **kwargs): + instance = super().save(*args, **kwargs) + new_transactions = Transaction.objects.filter( + id__in=self.cleaned_data["transactions"] + ) + + instance.transaction_set.add(*new_transactions, bulk=False) + return instance diff --git a/nummi/statement/migrations/0001_initial.py b/nummi/statement/migrations/0001_initial.py new file mode 100644 index 0000000..b2356d8 --- /dev/null +++ b/nummi/statement/migrations/0001_initial.py @@ -0,0 +1,127 @@ +# Generated by Django 4.1.4 on 2023-04-22 09:01 + +import datetime +import uuid + +import django.core.validators +import django.db.models.deletion +import media.utils +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("main", "0002_segmentation"), + ("account", "0001_initial"), + ] + + state_operations = [ + migrations.CreateModel( + name="Statement", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "date", + models.DateField( + default=datetime.date.today, verbose_name="End date" + ), + ), + ( + "start_date", + models.DateField( + default=datetime.date.today, verbose_name="Start date" + ), + ), + ( + "value", + models.DecimalField( + decimal_places=2, + default=0, + max_digits=12, + verbose_name="End value", + ), + ), + ( + "start_value", + models.DecimalField( + decimal_places=2, + default=0, + max_digits=12, + verbose_name="Start value", + ), + ), + ( + "diff", + models.DecimalField( + decimal_places=2, + default=0, + editable=False, + max_digits=12, + verbose_name="Difference", + ), + ), + ( + "sum", + models.DecimalField( + decimal_places=2, + default=0, + editable=False, + max_digits=12, + verbose_name="Transaction difference", + ), + ), + ( + "file", + models.FileField( + blank=True, + default="", + max_length=256, + upload_to=media.utils.get_path, + validators=[ + django.core.validators.FileExtensionValidator(["pdf"]) + ], + verbose_name="File", + ), + ), + ( + "account", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="account.account", + verbose_name="Account", + ), + ), + ( + "user", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Statement", + "verbose_name_plural": "Statements", + "ordering": ["-date", "account"], + "db_table": "statement_statement", + }, + ), + ] + + operations = [ + migrations.SeparateDatabaseAndState(state_operations=state_operations) + ] diff --git a/nummi/statement/migrations/__init__.py b/nummi/statement/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/statement/models.py b/nummi/statement/models.py new file mode 100644 index 0000000..8e91bcb --- /dev/null +++ b/nummi/statement/models.py @@ -0,0 +1,90 @@ +import datetime +from pathlib import Path +from uuid import uuid4 + +from account.models import AccountModel +from django.core.validators import FileExtensionValidator +from django.db import models, transaction +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ +from media.utils import get_path + + +class Statement(AccountModel): + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) + date = models.DateField(default=datetime.date.today, verbose_name=_("End date")) + start_date = models.DateField( + default=datetime.date.today, verbose_name=_("Start date") + ) + value = models.DecimalField( + max_digits=12, decimal_places=2, default=0, verbose_name=_("End value") + ) + 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"])], + verbose_name=_("File"), + max_length=256, + blank=True, + default="", + ) + + def __str__(self): + desc = _("%(date)s statement") % {"date": self.date} + if hasattr(self, "account"): + return f"{desc} – {self.account}" + return desc + + def save(self, *args, **kwargs): + if Statement.objects.filter(id=self.id).exists(): + _prever = Statement.objects.get(id=self.id) + 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 + ) + super().save() + + def delete(self, *args, **kwargs): + if self.file: + self.file.delete() + super().delete(*args, **kwargs) + + def get_absolute_url(self): + return reverse("statement", kwargs={"pk": self.pk}) + + def get_delete_url(self): + return reverse("del_statement", kwargs={"pk": self.pk}) + + class Meta: + ordering = ["-date", "account"] + verbose_name = _("Statement") + verbose_name_plural = _("Statements") diff --git a/nummi/statement/urls.py b/nummi/statement/urls.py new file mode 100644 index 0000000..65ac4ec --- /dev/null +++ b/nummi/statement/urls.py @@ -0,0 +1,24 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("statements", views.SnapshotListView.as_view(), name="statements"), + path("statement", views.StatementCreateView.as_view(), name="new_statement"), + path("statement/", views.StatementUpdateView.as_view(), name="statement"), + path( + "statement//transactions", + views.StatementTListView.as_view(), + name="statement_transactions", + ), + path( + "statement//transaction", + views.TransactionCreateView.as_view(), + name="new_transaction", + ), + path( + "statement//delete", + views.StatementDeleteView.as_view(), + name="del_statement", + ), +] diff --git a/nummi/statement/views.py b/nummi/statement/views.py new file mode 100644 index 0000000..68cdc43 --- /dev/null +++ b/nummi/statement/views.py @@ -0,0 +1,97 @@ +from account.models import Account +from django.db import models +from django.shortcuts import get_object_or_404 +from django.urls import reverse_lazy +from main.views import NummiCreateView, NummiDeleteView, NummiListView, NummiUpdateView +from transaction.views import TransactionListView + +from .forms import StatementForm +from .models import Statement + + +class StatementCreateView(NummiCreateView): + model = Statement + form_class = StatementForm + template_name = "main/form/statement.html" + + def get_initial(self): + _queryset = Account.objects.filter(user=self.request.user) + if "account" in self.kwargs: + self.account = get_object_or_404(_queryset, pk=self.kwargs["account"]) + else: + self.account = _queryset.first() + return {"account": self.account} + + def get_form_kwargs(self): + if "account" in self.kwargs: + return super().get_form_kwargs() | {"disable_account": True} + return super().get_form_kwargs() + + def get_context_data(self, **kwargs): + if "account" in self.kwargs: + return super().get_context_data(**kwargs) | {"account": self.account} + return super().get_context_data(**kwargs) + + +class StatementUpdateView(NummiUpdateView): + model = Statement + form_class = StatementForm + template_name = "main/form/statement.html" + + def get_context_data(self, **kwargs): + data = super().get_context_data(**kwargs) + statement = data["form"].instance + + _transactions = statement.transaction_set.all() + if _transactions: + _categories = ( + _transactions.values("category", "category__name", "category__icon") + .annotate( + sum=models.Sum("value"), + sum_m=models.Sum("value", filter=models.Q(value__lt=0)), + sum_p=models.Sum("value", filter=models.Q(value__gt=0)), + ) + .order_by("-sum") + ) + data["categories"] = { + "data": _categories, + "max": max( + _categories.aggregate( + max=models.Max("sum_p", default=0), + min=models.Min("sum_m", default=0), + ).values(), + ), + } + + return data | { + "account": statement.account, + "new_transaction_url": reverse_lazy( + "new_transaction", kwargs={"statement": statement.pk} + ), + "transactions": _transactions, + } + + +class StatementDeleteView(NummiDeleteView): + model = Statement + + +class StatementListView(NummiListView): + model = Statement + template_name = "main/list/statement.html" + context_object_name = "statements" + + +class StatementMixin: + def get_queryset(self): + return super().get_queryset().filter(statement=self.kwargs.get("pk")) + + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) | { + "object": Statement.objects.get(pk=self.kwargs.get("pk")), + "statement": True, + } + + +class StatementTListView(StatementMixin, TransactionListView): + pass diff --git a/nummi/transaction/__init__.py b/nummi/transaction/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/transaction/apps.py b/nummi/transaction/apps.py new file mode 100644 index 0000000..55b277b --- /dev/null +++ b/nummi/transaction/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TransactionConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "transaction" diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py new file mode 100644 index 0000000..f492e21 --- /dev/null +++ b/nummi/transaction/forms.py @@ -0,0 +1,44 @@ +from category.models import Category +from main.forms import NummiFileInput, NummiForm +from Statement.models import Statement + +from .models import Invoice, Transaction + + +class TransactionForm(NummiForm): + class Meta: + model = Transaction + fields = [ + "statement", + "name", + "value", + "date", + "real_date", + "category", + "trader", + "payment", + "description", + ] + + def __init__(self, *args, **kwargs): + _user = kwargs.get("user") + _disable_statement = kwargs.pop("disable_statement", False) + super().__init__(*args, **kwargs) + self.fields["category"].queryset = Category.objects.filter(user=_user) + self.fields["statement"].queryset = Statement.objects.filter(user=_user) + if _disable_statement: + self.fields["statement"].disabled = True + + +class InvoiceForm(NummiForm): + prefix = "invoice" + + class Meta: + model = Invoice + fields = [ + "name", + "file", + ] + widgets = { + "file": NummiFileInput, + } diff --git a/nummi/transaction/migrations/0001_initial.py b/nummi/transaction/migrations/0001_initial.py new file mode 100644 index 0000000..60448ad --- /dev/null +++ b/nummi/transaction/migrations/0001_initial.py @@ -0,0 +1,175 @@ +# Generated by Django 4.1.4 on 2023-04-22 09:01 + +import datetime +import uuid + +import django.core.validators +import django.db.models.deletion +import media.utils +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("main", "0002_segmentation"), + ("category", "0001_initial"), + ("account", "0001_initial"), + ("statement", "0001_initial"), + ] + + state_operations = [ + migrations.CreateModel( + name="Transaction", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "name", + models.CharField( + default="Transaction", max_length=256, verbose_name="Name" + ), + ), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="Description"), + ), + ( + "value", + models.DecimalField( + decimal_places=2, default=0, max_digits=12, verbose_name="Value" + ), + ), + ( + "date", + models.DateField(default=datetime.date.today, verbose_name="Date"), + ), + ( + "real_date", + models.DateField(blank=True, null=True, verbose_name="Real date"), + ), + ( + "trader", + models.CharField( + blank=True, max_length=128, null=True, verbose_name="Trader" + ), + ), + ( + "payment", + models.CharField( + blank=True, max_length=128, null=True, verbose_name="Payment" + ), + ), + ( + "account", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to="account.account", + verbose_name="Account", + ), + ), + ( + "category", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="category.category", + verbose_name="Category", + ), + ), + ( + "statement", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="statement.statement", + verbose_name="Statement", + ), + ), + ( + "user", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Transaction", + "verbose_name_plural": "Transactions", + "ordering": ["-date", "statement"], + "db_table": "transaction_transaction", + }, + ), + migrations.CreateModel( + name="Invoice", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "name", + models.CharField( + default="Invoice", max_length=256, verbose_name="Name" + ), + ), + ( + "file", + models.FileField( + max_length=128, + upload_to=media.utils.get_path, + validators=[ + django.core.validators.FileExtensionValidator(["pdf"]) + ], + verbose_name="File", + ), + ), + ( + "transaction", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to="transaction.transaction", + ), + ), + ( + "user", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Invoice", + "verbose_name_plural": "Invoices", + "ordering": ["transaction", "name"], + "db_table": "transaction_invoice", + }, + ), + ] + + operations = [ + migrations.SeparateDatabaseAndState(state_operations=state_operations) + ] diff --git a/nummi/transaction/migrations/__init__.py b/nummi/transaction/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/transaction/models.py b/nummi/transaction/models.py new file mode 100644 index 0000000..3238a76 --- /dev/null +++ b/nummi/transaction/models.py @@ -0,0 +1,128 @@ +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 +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ +from main.models import UserModel +from media.utils import get_path +from statement.models import Statement + + +class Transaction(UserModel): + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) + name = models.CharField( + max_length=256, default=_("Transaction"), verbose_name=_("Name") + ) + description = models.TextField(null=True, blank=True, verbose_name=_("Description")) + value = models.DecimalField( + max_digits=12, decimal_places=2, default=0, verbose_name=_("Value") + ) + date = models.DateField(default=datetime.date.today, verbose_name=_("Date")) + real_date = models.DateField(blank=True, null=True, verbose_name=_("Real date")) + trader = models.CharField( + max_length=128, blank=True, null=True, verbose_name=_("Trader") + ) + payment = models.CharField( + max_length=128, blank=True, null=True, verbose_name=_("Payment") + ) + category = models.ForeignKey( + Category, + on_delete=models.SET_NULL, + blank=True, + null=True, + verbose_name=_("Category"), + ) + statement = models.ForeignKey( + Statement, + on_delete=models.CASCADE, + verbose_name=_("Statement"), + ) + account = models.ForeignKey( + Account, + on_delete=models.CASCADE, + 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 + self.account = self.statement.account + super().save(*args, **kwargs) + if prev_self is not None: + prev_self.statement.update_sum() + self.statement.update_sum() + + def __str__(self): + return f"{self.name}" + + def get_absolute_url(self): + return reverse("transaction", kwargs={"pk": self.pk}) + + def get_delete_url(self): + return reverse("del_transaction", kwargs={"pk": self.pk}) + + @property + def invoices(self): + return Invoice.objects.filter(transaction=self) + + @property + def has_invoice(self): + return self.invoices.count() > 0 + + class Meta: + ordering = ["-date", "statement"] + verbose_name = _("Transaction") + verbose_name_plural = _("Transactions") + + +class Invoice(UserModel): + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) + name = models.CharField( + max_length=256, default=_("Invoice"), verbose_name=_("Name") + ) + file = models.FileField( + upload_to=get_path, + validators=[FileExtensionValidator(["pdf"])], + verbose_name=_("File"), + max_length=128, + ) + transaction = models.ForeignKey( + Transaction, on_delete=models.CASCADE, editable=False + ) + + def save(self, *args, **kwargs): + if Invoice.objects.filter(id=self.id).exists(): + _prever = Invoice.objects.get(id=self.id) + if _prever.file and _prever.file != self.file: + Path(_prever.file.path).unlink(missing_ok=True) + super().save(*args, **kwargs) + + def __str__(self): + return str(self.name) + + def delete(self, *args, **kwargs): + self.file.delete() + super().delete(*args, **kwargs) + + def get_absolute_url(self): + return reverse( + "invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk} + ) + + def get_delete_url(self): + return reverse( + "del_invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk} + ) + + class Meta: + verbose_name = _("Invoice") + verbose_name_plural = _("Invoices") + ordering = ["transaction", "name"] diff --git a/nummi/transaction/urls.py b/nummi/transaction/urls.py new file mode 100644 index 0000000..31866e9 --- /dev/null +++ b/nummi/transaction/urls.py @@ -0,0 +1,34 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("transactions", views.TransactionListView.as_view(), name="transactions"), + path("transaction", views.TransactionCreateView.as_view(), name="new_transaction"), + path( + "transaction//invoice", + views.InvoiceCreateView.as_view(), + name="new_invoice", + ), + path("transaction/", views.TransactionUpdateView.as_view(), name="transaction"), + path( + "transaction//invoice/", + views.InvoiceUpdateView.as_view(), + name="invoice", + ), + path( + "transaction//delete", + views.TransactionDeleteView.as_view(), + name="del_transaction", + ), + path( + "transaction//invoice//delete", + views.InvoiceDeleteView.as_view(), + name="del_invoice", + ), + path( + "history//", + views.TransactionMonthView.as_view(), + name="transaction_month", + ), +] diff --git a/nummi/main/utils.py b/nummi/transaction/utils.py similarity index 87% rename from nummi/main/utils.py rename to nummi/transaction/utils.py index 467dde3..ef0a353 100644 --- a/nummi/main/utils.py +++ b/nummi/transaction/utils.py @@ -1,19 +1,8 @@ -import pathlib - from django.db import models from django.db.models import Func, Max, Min, Q, Sum, Value from django.db.models.functions import Now, TruncMonth -def get_path(instance, filename): - return pathlib.Path( - "user", - str(instance.user.username), - instance._meta.model_name, - str(instance.pk), - ).with_suffix(".pdf") - - class GenerateMonth(Func): function = "generate_series" template = "%(function)s(%(expressions)s, '1 month')::date" diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py new file mode 100644 index 0000000..42bf7e1 --- /dev/null +++ b/nummi/transaction/views.py @@ -0,0 +1,144 @@ +from account.models import Account +from category.models import Category +from django.shortcuts import get_object_or_404 +from django.urls import reverse_lazy +from django.views.generic.dates import MonthArchiveView +from main.views import ( + NummiCreateView, + NummiDeleteView, + NummiListView, + NummiUpdateView, + UserMixin, +) +from statement.models import Statement + +from .forms import InvoiceForm, TransactionForm +from .models import Invoice, Transaction + + +class TransactionCreateView(NummiCreateView): + model = Transaction + form_class = TransactionForm + template_name = "main/form/transaction.html" + + def get_initial(self): + _queryset = Statement.objects.filter(user=self.request.user) + 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} + + 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 + form_class = InvoiceForm + template_name = "main/form/invoice.html" + + def form_valid(self, form): + form.instance.transaction = get_object_or_404( + Transaction.objects.filter(user=self.request.user), + pk=self.kwargs["transaction_pk"], + ) + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk}) + + +class TransactionUpdateView(NummiUpdateView): + model = Transaction + form_class = TransactionForm + template_name = "main/form/transaction.html" + + +class InvoiceUpdateView(NummiUpdateView): + model = Invoice + form_class = InvoiceForm + template_name = "main/form/invoice.html" + + def get_success_url(self): + return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk}) + + def get_queryset(self): + return ( + super() + .get_queryset() + .filter( + transaction=get_object_or_404( + Transaction, pk=self.kwargs["transaction_pk"] + ) + ) + ) + + +class TransactionDeleteView(NummiDeleteView): + model = Transaction + + +class InvoiceDeleteView(NummiDeleteView): + model = Invoice + + def get_success_url(self): + return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk}) + + def get_queryset(self): + return ( + super() + .get_queryset() + .filter( + transaction=get_object_or_404( + Transaction, pk=self.kwargs["transaction_pk"] + ) + ) + ) + + +class TransactionListView(NummiListView): + model = Transaction + template_name = "main/list/transaction.html" + context_object_name = "transactions" + + +class TransactionMonthView(UserMixin, MonthArchiveView): + template_name = "main/month/transaction.html" + model = Transaction + date_field = "date" + context_object_name = "transactions" + month_format = "%m" + + account = None + category = None + + def get_queryset(self): + if "account" in self.kwargs: + self.account = get_object_or_404( + Account.objects.filter(user=self.request.user), + pk=self.kwargs["account"], + ) + return super().get_queryset().filter(account=self.account) + if "category" in self.kwargs: + self.category = get_object_or_404( + Category.objects.filter(user=self.request.user), + pk=self.kwargs["category"], + ) + return super().get_queryset().filter(category=self.category) + + return super().get_queryset() + + def get_context_data(self, **kwargs): + if "account" in self.kwargs: + return super().get_context_data(**kwargs) | {"account": self.account} + if "category" in self.kwargs: + return super().get_context_data(**kwargs) | {"category": self.category} + return super().get_context_data(**kwargs) From bb4a8c5db1bfd7cbfe4718bd3c306f993537527a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 11:40:12 +0200 Subject: [PATCH 058/276] Fixed migrations --- .../migrations/0002_alter_account_table.py | 16 +++++++++++++++ .../migrations/0002_alter_category_table.py | 16 +++++++++++++++ nummi/main/migrations/0002_segmentation.py | 1 + .../migrations/0002_alter_statement_table.py | 16 +++++++++++++++ ...r_invoice_table_alter_transaction_table.py | 20 +++++++++++++++++++ 5 files changed, 69 insertions(+) create mode 100644 nummi/account/migrations/0002_alter_account_table.py create mode 100644 nummi/category/migrations/0002_alter_category_table.py create mode 100644 nummi/statement/migrations/0002_alter_statement_table.py create mode 100644 nummi/transaction/migrations/0002_alter_invoice_table_alter_transaction_table.py diff --git a/nummi/account/migrations/0002_alter_account_table.py b/nummi/account/migrations/0002_alter_account_table.py new file mode 100644 index 0000000..b622ffa --- /dev/null +++ b/nummi/account/migrations/0002_alter_account_table.py @@ -0,0 +1,16 @@ +# Generated by Django 4.1.4 on 2023-04-22 09:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("account", "0001_initial"), + ] + + operations = [ + migrations.AlterModelTable( + name="account", + table=None, + ), + ] diff --git a/nummi/category/migrations/0002_alter_category_table.py b/nummi/category/migrations/0002_alter_category_table.py new file mode 100644 index 0000000..cf13d3c --- /dev/null +++ b/nummi/category/migrations/0002_alter_category_table.py @@ -0,0 +1,16 @@ +# Generated by Django 4.1.4 on 2023-04-22 09:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("category", "0001_initial"), + ] + + operations = [ + migrations.AlterModelTable( + name="category", + table=None, + ), + ] diff --git a/nummi/main/migrations/0002_segmentation.py b/nummi/main/migrations/0002_segmentation.py index ba9052b..5886654 100644 --- a/nummi/main/migrations/0002_segmentation.py +++ b/nummi/main/migrations/0002_segmentation.py @@ -25,6 +25,7 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RenameField("Transaction", "snapshot", "statement"), migrations.SeparateDatabaseAndState( database_operations=database_operations, state_operations=state_operations, diff --git a/nummi/statement/migrations/0002_alter_statement_table.py b/nummi/statement/migrations/0002_alter_statement_table.py new file mode 100644 index 0000000..bdab6f3 --- /dev/null +++ b/nummi/statement/migrations/0002_alter_statement_table.py @@ -0,0 +1,16 @@ +# Generated by Django 4.1.4 on 2023-04-22 09:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("statement", "0001_initial"), + ] + + operations = [ + migrations.AlterModelTable( + name="statement", + table=None, + ), + ] diff --git a/nummi/transaction/migrations/0002_alter_invoice_table_alter_transaction_table.py b/nummi/transaction/migrations/0002_alter_invoice_table_alter_transaction_table.py new file mode 100644 index 0000000..40d96ec --- /dev/null +++ b/nummi/transaction/migrations/0002_alter_invoice_table_alter_transaction_table.py @@ -0,0 +1,20 @@ +# Generated by Django 4.1.4 on 2023-04-22 09:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("transaction", "0001_initial"), + ] + + operations = [ + migrations.AlterModelTable( + name="invoice", + table=None, + ), + migrations.AlterModelTable( + name="transaction", + table=None, + ), + ] From 719436f9ad60131da8ab65113d20cdaf1674ecc8 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 12:03:08 +0200 Subject: [PATCH 059/276] Implemented frontend for account --- nummi/account/models.py | 4 +-- .../templates/account/account_form.html} | 2 +- nummi/account/urls.py | 20 ++++++----- nummi/account/views.py | 34 ++++++++++--------- nummi/category/urls.py | 3 +- nummi/main/templates/main/base.html | 12 +++---- .../form/{snapshot.html => statement.html} | 0 nummi/main/templates/main/index.html | 6 ++-- .../list/{snapshot.html => statement.html} | 20 ++++++++--- .../main/templates/main/list/transaction.html | 14 ++++++-- .../table/{snapshot.html => statement.html} | 18 +++++----- .../templates/main/table/transaction.html | 6 ++-- nummi/main/urls.py | 7 +++- nummi/main/views.py | 5 +++ nummi/statement/urls.py | 5 +-- nummi/transaction/forms.py | 2 +- 16 files changed, 97 insertions(+), 61 deletions(-) rename nummi/{main/templates/main/form/account.html => account/templates/account/account_form.html} (92%) rename nummi/main/templates/main/form/{snapshot.html => statement.html} (100%) rename nummi/main/templates/main/list/{snapshot.html => statement.html} (53%) rename nummi/main/templates/main/table/{snapshot.html => statement.html} (76%) diff --git a/nummi/account/models.py b/nummi/account/models.py index 594f5e9..4709ef5 100644 --- a/nummi/account/models.py +++ b/nummi/account/models.py @@ -27,10 +27,10 @@ class Account(UserModel): return str(self.name) def get_absolute_url(self): - return reverse("account", kwargs={"pk": self.pk}) + return reverse("account", args=(self.pk,)) def get_delete_url(self): - return reverse("del_account", kwargs={"pk": self.pk}) + return reverse("del_account", args=(self.pk,)) class Meta: ordering = ["-default", "name"] diff --git a/nummi/main/templates/main/form/account.html b/nummi/account/templates/account/account_form.html similarity index 92% rename from nummi/main/templates/main/form/account.html rename to nummi/account/templates/account/account_form.html index 5d16607..0e18fbb 100644 --- a/nummi/main/templates/main/form/account.html +++ b/nummi/account/templates/account/account_form.html @@ -11,7 +11,7 @@ {% block tables %} {% if not form.instance|adding %}

      {% translate "Statements" %}

      - {% include "main/table/snapshot.html" %} + {% include "main/table/statement.html" %} {% endif %} {% if transactions %}

      {% translate "Transactions" %}

      diff --git a/nummi/account/urls.py b/nummi/account/urls.py index b5158ea..43bc302 100644 --- a/nummi/account/urls.py +++ b/nummi/account/urls.py @@ -1,33 +1,35 @@ from django.urls import path +from statement.views import StatementCreateView +from transaction.views import TransactionMonthView from . import views urlpatterns = [ - path("account", views.AccountCreateView.as_view(), name="new_account"), - path("account/", views.AccountUpdateView.as_view(), name="account"), + path("", views.AccountCreateView.as_view(), name="new_account"), + path("", views.AccountUpdateView.as_view(), name="account"), path( - "account//transactions", + "/transactions", views.AccountTListView.as_view(), name="account_transactions", ), path( - "account//statements", + "/statements", views.AccountSListView.as_view(), name="account_statements", ), path( - "account//statement", - views.StatementCreateView.as_view(), + "/statement", + StatementCreateView.as_view(), name="new_statement", ), path( - "account//delete", + "/delete", views.AccountDeleteView.as_view(), name="del_account", ), path( - "account//history//", - views.TransactionMonthView.as_view(), + "/history//", + TransactionMonthView.as_view(), name="transaction_month", ), ] diff --git a/nummi/account/views.py b/nummi/account/views.py index 576d153..a73c7a8 100644 --- a/nummi/account/views.py +++ b/nummi/account/views.py @@ -1,6 +1,7 @@ +from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView -from snapshot.views import SnapshotListView +from statement.views import StatementListView from transaction.utils import history from transaction.views import TransactionListView @@ -11,13 +12,12 @@ from .models import Account class AccountCreateView(NummiCreateView): model = Account form_class = AccountForm - template_name = "main/form/account.html" class AccountUpdateView(NummiUpdateView): model = Account form_class = AccountForm - template_name = "main/form/account.html" + pk_url_kwarg = "account" def get_context_data(self, **kwargs): _max = 8 @@ -29,40 +29,42 @@ class AccountUpdateView(NummiUpdateView): data["transactions_url"] = reverse_lazy( "account_transactions", args=(account.pk,) ) - _snapshots = account.snapshot_set.all() - if _snapshots.count() > _max: - data["snapshots_url"] = reverse_lazy( - "account_snapshots", args=(account.pk,) + _statements = account.statement_set.all() + if _statements.count() > _max: + data["statements_url"] = reverse_lazy( + "account_statements", args=(account.pk,) ) return data | { "transactions": _transactions[:8], - "new_snapshot_url": reverse_lazy( - "new_snapshot", kwargs={"account": account.pk} + "new_statement_url": reverse_lazy( + "new_statement", kwargs={"account": account.pk} ), - "snapshots": _snapshots[:8], + "statements": _statements[:8], "history": history(account.transaction_set), } class AccountDeleteView(NummiDeleteView): model = Account + pk_url_kwarg = "account" class AccountMixin: def get_queryset(self): - return super().get_queryset().filter(account=self.kwargs.get("pk")) + self.account = get_object_or_404( + Account.objects.filter(user=self.request.user), + pk=self.kwargs.get("account"), + ) + return super().get_queryset().filter(account=self.account) def get_context_data(self, **kwargs): - return super().get_context_data(**kwargs) | { - "object": Account.objects.get(pk=self.kwargs.get("pk")), - "account": True, - } + return super().get_context_data(**kwargs) | {"account": self.account} class AccountTListView(AccountMixin, TransactionListView): pass -class AccountSListView(AccountMixin, SnapshotListView): +class AccountSListView(AccountMixin, StatementListView): pass diff --git a/nummi/category/urls.py b/nummi/category/urls.py index 113aac9..601e925 100644 --- a/nummi/category/urls.py +++ b/nummi/category/urls.py @@ -1,4 +1,5 @@ from django.urls import path +from transaction.views import TransactionMonthView from . import views @@ -15,7 +16,7 @@ urlpatterns = [ ), path( "category//history//", - views.TransactionMonthView.as_view(), + TransactionMonthView.as_view(), name="transaction_month", ), ] diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index c58e059..274bfd4 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -33,9 +33,9 @@ accesskey="h">{% translate "Home" %}
    • - - {% translate "Snapshots" %} + + {% translate "Statements" %}
    • @@ -50,9 +50,9 @@ accesskey="a">{% translate "Create account" %}
    • - {% translate "Create snapshot" %} + {% translate "Create statement" %}
    • {% endspaceless %} {% endif %} - {% if snapshots %} -

      {% translate "Snapshots" %}

      - {% include "main/table/snapshot.html" %} + {% if statements %} +

      {% translate "Statements" %}

      + {% include "main/table/statement.html" %} {% endif %} {% if history.data %}

      {% translate "History" %}

      diff --git a/nummi/main/templates/main/list/snapshot.html b/nummi/main/templates/main/list/statement.html similarity index 53% rename from nummi/main/templates/main/list/snapshot.html rename to nummi/main/templates/main/list/statement.html index b7ce44a..f6b31a9 100644 --- a/nummi/main/templates/main/list/snapshot.html +++ b/nummi/main/templates/main/list/statement.html @@ -4,7 +4,8 @@ {% load i18n %} {% block title %} {% translate "Statements" %} - {% if object %}– {{ object }}{% endif %} + {% if account %}– {{ account }}{% endif %} + {% if category %}– {{ category }}{% endif %} – Nummi {% endblock %} {% block link %} @@ -18,12 +19,21 @@ {% endblock %} {% block body %}

      {% translate "Statements" %}

      - {% if object %}
      {{ object }}{% endif %} - {% if snapshots %} + {% if account %} +

      + {{ account.icon|remix }}{{ account }} +

      + {% endif %} + {% if category %} +

      + {{ category.icon|remix }}{{ category }} +

      + {% endif %} + {% if statements %} {% include "main/list/pagination.html" %} - {% include "main/table/snapshot.html" %} + {% include "main/table/statement.html" %} {% include "main/list/pagination.html" %} {% else %} -

      {% translate "No snapshots to show" %}

      +

      {% translate "No statements to show" %}

      {% endif %} {% endblock %} diff --git a/nummi/main/templates/main/list/transaction.html b/nummi/main/templates/main/list/transaction.html index 70a0c2e..de2f7b0 100644 --- a/nummi/main/templates/main/list/transaction.html +++ b/nummi/main/templates/main/list/transaction.html @@ -4,7 +4,8 @@ {% load i18n %} {% block title %} {% translate "Transactions" %} - {% if object %}– {{ object }}{% endif %} + {% if account %}– {{ account }}{% endif %} + {% if category %}– {{ category }}{% endif %} {% if search %} – {% translate "Search" %} {% endif %} @@ -21,7 +22,16 @@ {% endblock %} {% block body %}

      {% translate "Transactions" %}

      - {% if object %}{{ object }}{% endif %} + {% if account %} +

      + {{ account.icon|remix }}{{ account }} +

      + {% endif %} + {% if category %} +

      + {{ category.icon|remix }}{{ category }} +

      + {% endif %} {% if search %} {% translate "Search" %} {% endif %} diff --git a/nummi/main/templates/main/table/snapshot.html b/nummi/main/templates/main/table/statement.html similarity index 76% rename from nummi/main/templates/main/table/snapshot.html rename to nummi/main/templates/main/table/statement.html index 23cc6d5..ebe81b7 100644 --- a/nummi/main/templates/main/table/snapshot.html +++ b/nummi/main/templates/main/table/statement.html @@ -1,12 +1,12 @@ {% load main_extras %} {% load i18n %} -{% if new_snapshot_url %} +{% if new_statement_url %}

      - {% translate "Create statement" %} + {% translate "Create statement" %}

      {% endif %} -
      - +
      +
      @@ -28,7 +28,7 @@ - {% for snap in snapshots %} + {% for snap in statements %} {% if snap.sum == snap.diff %} @@ -39,12 +39,12 @@ {% if snap.file %}{{ "attachment"|remix }}{% endif %} {% if not account %} {% endif %} @@ -55,8 +55,8 @@
      {% translate "Transactions" %}
      {{ "check"|remix }} - {{ snap.date|date:"Y-m-d" }} + {{ snap.date|date:"Y-m-d" }} {{ snap.account.icon|remix }} - {{ snap.account }} + {{ snap.account }} {{ snap.value|value }}
      -{% if snapshots_url %} +{% if statements_url %}

      - {% translate "View all statements" %} + {% translate "View all statements" %}

      {% endif %} diff --git a/nummi/main/templates/main/table/transaction.html b/nummi/main/templates/main/table/transaction.html index da11aa8..fe7c34b 100644 --- a/nummi/main/templates/main/table/transaction.html +++ b/nummi/main/templates/main/table/transaction.html @@ -43,7 +43,7 @@ {{ trans.date|date:"Y-m-d" }} - {{ trans.name }} + {{ trans.name }} {{ trans.value|pmvalue }} {{ trans.trader|default_if_none:"" }} @@ -51,7 +51,7 @@ {% if trans.category %} {{ trans.category.icon|remix }} - {{ trans.category }} + {{ trans.category }} {% else %} @@ -60,7 +60,7 @@ {% if not account %} {{ trans.account.icon|remix }} - {{ trans.account }} + {{ trans.account }} {% endif %} diff --git a/nummi/main/urls.py b/nummi/main/urls.py index 3b911ad..0b5893d 100644 --- a/nummi/main/urls.py +++ b/nummi/main/urls.py @@ -1,4 +1,4 @@ -from django.urls import path +from django.urls import include, path from . import views @@ -6,4 +6,9 @@ urlpatterns = [ path("", views.IndexView.as_view(), name="index"), path("login", views.LoginView.as_view(), name="login"), path("logout", views.LogoutView.as_view(), name="logout"), + path("account/", include("account.urls")), + path("category/", include("category.urls")), + path("statement/", include("statement.urls")), + path("transaction/", include("transaction.urls")), + path("search/", include("search.urls")), ] diff --git a/nummi/main/views.py b/nummi/main/views.py index 7ede354..e7fbea9 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -66,6 +66,11 @@ class NummiDeleteView(UserMixin, DeleteView): template_name = "main/confirm_delete.html" success_url = reverse_lazy("index") + def get_form_kwargs(self): + _res = super().get_form_kwargs() + _res.pop("user") + return _res + class LoginView(auth_views.LoginView): template_name = "main/login.html" diff --git a/nummi/statement/urls.py b/nummi/statement/urls.py index 65ac4ec..c5d7442 100644 --- a/nummi/statement/urls.py +++ b/nummi/statement/urls.py @@ -1,9 +1,10 @@ from django.urls import path +from transaction.views import TransactionCreateView from . import views urlpatterns = [ - path("statements", views.SnapshotListView.as_view(), name="statements"), + path("statements", views.StatementListView.as_view(), name="statements"), path("statement", views.StatementCreateView.as_view(), name="new_statement"), path("statement/", views.StatementUpdateView.as_view(), name="statement"), path( @@ -13,7 +14,7 @@ urlpatterns = [ ), path( "statement//transaction", - views.TransactionCreateView.as_view(), + TransactionCreateView.as_view(), name="new_transaction", ), path( diff --git a/nummi/transaction/forms.py b/nummi/transaction/forms.py index f492e21..0d661e0 100644 --- a/nummi/transaction/forms.py +++ b/nummi/transaction/forms.py @@ -1,6 +1,6 @@ from category.models import Category from main.forms import NummiFileInput, NummiForm -from Statement.models import Statement +from statement.models import Statement from .models import Invoice, Transaction From f6e8b583ccd0c7ebe047da5023cd84fd1bd4e76d Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 12:15:48 +0200 Subject: [PATCH 060/276] Implemented frontend for category --- nummi/category/models.py | 4 ++-- .../templates/category/category_form.html} | 4 +++- nummi/category/urls.py | 12 +++++------- nummi/category/views.py | 16 +++++++++------- 4 files changed, 19 insertions(+), 17 deletions(-) rename nummi/{main/templates/main/form/category.html => category/templates/category/category_form.html} (88%) diff --git a/nummi/category/models.py b/nummi/category/models.py index 845600b..116ba4f 100644 --- a/nummi/category/models.py +++ b/nummi/category/models.py @@ -22,10 +22,10 @@ class Category(UserModel): return str(self.name) def get_absolute_url(self): - return reverse("category", kwargs={"pk": self.pk}) + return reverse("category", args=(self.pk,)) def get_delete_url(self): - return reverse("del_category", kwargs={"pk": self.pk}) + return reverse("del_category", args=(self.pk,)) class Meta: ordering = ["name"] diff --git a/nummi/main/templates/main/form/category.html b/nummi/category/templates/category/category_form.html similarity index 88% rename from nummi/main/templates/main/form/category.html rename to nummi/category/templates/category/category_form.html index 4a7cc62..6223526 100644 --- a/nummi/main/templates/main/form/category.html +++ b/nummi/category/templates/category/category_form.html @@ -9,9 +9,11 @@ {% endblock %} {% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} {% block tables %} - {% if form.instance.transactions %} + {% if transactions %}

      {% translate "Transactions" %}

      {% include "main/table/transaction.html" %} + {% endif %} + {% if history.data %}

      {% translate "History" %}

      {% include "main/plot/history.html" %} {% endif %} diff --git a/nummi/category/urls.py b/nummi/category/urls.py index 601e925..3d72bca 100644 --- a/nummi/category/urls.py +++ b/nummi/category/urls.py @@ -4,18 +4,16 @@ from transaction.views import TransactionMonthView from . import views urlpatterns = [ - path("category", views.CategoryCreateView.as_view(), name="new_category"), - path("category/", views.CategoryUpdateView.as_view(), name="category"), + path("", views.CategoryCreateView.as_view(), name="new_category"), + path("", views.CategoryUpdateView.as_view(), name="category"), path( - "category//transactions", + "/transactions", views.CategoryTListView.as_view(), name="category_transactions", ), + path("/delete", views.CategoryDeleteView.as_view(), name="del_category"), path( - "category//delete", views.CategoryDeleteView.as_view(), name="del_category" - ), - path( - "category//history//", + "/history//", TransactionMonthView.as_view(), name="transaction_month", ), diff --git a/nummi/category/views.py b/nummi/category/views.py index e84ac96..53d8609 100644 --- a/nummi/category/views.py +++ b/nummi/category/views.py @@ -1,3 +1,4 @@ +from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView from transaction.utils import history @@ -10,13 +11,12 @@ from .models import Category class CategoryCreateView(NummiCreateView): model = Category form_class = CategoryForm - template_name = "main/form/category.html" class CategoryUpdateView(NummiUpdateView): model = Category form_class = CategoryForm - template_name = "main/form/category.html" + pk_url_kwarg = "category" def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) @@ -33,17 +33,19 @@ class CategoryUpdateView(NummiUpdateView): class CategoryDeleteView(NummiDeleteView): model = Category + pk_url_kwarg = "category" class CategoryMixin: def get_queryset(self): - return super().get_queryset().filter(category=self.kwargs.get("pk")) + self.category = get_object_or_404( + Category.objects.filter(user=self.request.user), + pk=self.kwargs.get("category"), + ) + return super().get_queryset().filter(category=self.category) def get_context_data(self, **kwargs): - return super().get_context_data(**kwargs) | { - "object": Category.objects.get(pk=self.kwargs.get("pk")), - "category": True, - } + return super().get_context_data(**kwargs) | {"category": self.category} class CategoryTListView(CategoryMixin, TransactionListView): From c9d1496e00de4b27eafe8a53580c57a2f26eb548 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 12:20:03 +0200 Subject: [PATCH 061/276] Implemented frontend for search --- nummi/nummi/settings.py | 1 + .../templates/main => search/templates/search}/search.html | 0 nummi/search/views.py | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) rename nummi/{main/templates/main => search/templates/search}/search.html (100%) diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index a2cc1e9..28af502 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -50,6 +50,7 @@ INSTALLED_APPS = [ "category", "statement", "transaction", + "search", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", diff --git a/nummi/main/templates/main/search.html b/nummi/search/templates/search/search.html similarity index 100% rename from nummi/main/templates/main/search.html rename to nummi/search/templates/search/search.html diff --git a/nummi/search/views.py b/nummi/search/views.py index c41965c..6a0dd10 100644 --- a/nummi/search/views.py +++ b/nummi/search/views.py @@ -14,7 +14,7 @@ from .forms import SearchForm class SearchFormView(LoginRequiredMixin, FormView): - template_name = "main/search.html" + template_name = "search/search.html" form_class = SearchForm def form_valid(self, form): From a98b073eeac74e57bdb75e572c6e746fc704a10d Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 12:30:08 +0200 Subject: [PATCH 062/276] Implemented frontend for statements --- nummi/statement/models.py | 4 ++-- .../templates/statement/statement_form.html} | 0 .../templates/statement/statement_list.html} | 0 nummi/statement/urls.py | 12 ++++++------ nummi/statement/views.py | 16 ++++++++-------- 5 files changed, 16 insertions(+), 16 deletions(-) rename nummi/{main/templates/main/form/statement.html => statement/templates/statement/statement_form.html} (100%) rename nummi/{main/templates/main/list/statement.html => statement/templates/statement/statement_list.html} (100%) diff --git a/nummi/statement/models.py b/nummi/statement/models.py index 8e91bcb..865abdd 100644 --- a/nummi/statement/models.py +++ b/nummi/statement/models.py @@ -79,10 +79,10 @@ class Statement(AccountModel): super().delete(*args, **kwargs) def get_absolute_url(self): - return reverse("statement", kwargs={"pk": self.pk}) + return reverse("statement", args=(self.pk,)) def get_delete_url(self): - return reverse("del_statement", kwargs={"pk": self.pk}) + return reverse("del_statement", args=(self.pk,)) class Meta: ordering = ["-date", "account"] diff --git a/nummi/main/templates/main/form/statement.html b/nummi/statement/templates/statement/statement_form.html similarity index 100% rename from nummi/main/templates/main/form/statement.html rename to nummi/statement/templates/statement/statement_form.html diff --git a/nummi/main/templates/main/list/statement.html b/nummi/statement/templates/statement/statement_list.html similarity index 100% rename from nummi/main/templates/main/list/statement.html rename to nummi/statement/templates/statement/statement_list.html diff --git a/nummi/statement/urls.py b/nummi/statement/urls.py index c5d7442..d9e1e3b 100644 --- a/nummi/statement/urls.py +++ b/nummi/statement/urls.py @@ -4,21 +4,21 @@ from transaction.views import TransactionCreateView from . import views urlpatterns = [ - path("statements", views.StatementListView.as_view(), name="statements"), - path("statement", views.StatementCreateView.as_view(), name="new_statement"), - path("statement/", views.StatementUpdateView.as_view(), name="statement"), + path("list", views.StatementListView.as_view(), name="statements"), + path("new", views.StatementCreateView.as_view(), name="new_statement"), + path("", views.StatementUpdateView.as_view(), name="statement"), path( - "statement//transactions", + "/transaction/list", views.StatementTListView.as_view(), name="statement_transactions", ), path( - "statement//transaction", + "/transaction/new", TransactionCreateView.as_view(), name="new_transaction", ), path( - "statement//delete", + "/delete", views.StatementDeleteView.as_view(), name="del_statement", ), diff --git a/nummi/statement/views.py b/nummi/statement/views.py index 68cdc43..cbbbc6a 100644 --- a/nummi/statement/views.py +++ b/nummi/statement/views.py @@ -12,7 +12,6 @@ from .models import Statement class StatementCreateView(NummiCreateView): model = Statement form_class = StatementForm - template_name = "main/form/statement.html" def get_initial(self): _queryset = Account.objects.filter(user=self.request.user) @@ -36,7 +35,7 @@ class StatementCreateView(NummiCreateView): class StatementUpdateView(NummiUpdateView): model = Statement form_class = StatementForm - template_name = "main/form/statement.html" + pk_url_kwarg = "statement" def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) @@ -74,23 +73,24 @@ class StatementUpdateView(NummiUpdateView): class StatementDeleteView(NummiDeleteView): model = Statement + pk_url_kwarg = "statement" class StatementListView(NummiListView): model = Statement - template_name = "main/list/statement.html" context_object_name = "statements" class StatementMixin: def get_queryset(self): - return super().get_queryset().filter(statement=self.kwargs.get("pk")) + self.statement = get_object_or_404( + Statement.objects.filter(user=self.request.user), + pk=self.kwargs.get("statement"), + ) + return super().get_queryset().filter(statement=self.statement) def get_context_data(self, **kwargs): - return super().get_context_data(**kwargs) | { - "object": Statement.objects.get(pk=self.kwargs.get("pk")), - "statement": True, - } + return super().get_context_data(**kwargs) | {"statement": self.statement} class StatementTListView(StatementMixin, TransactionListView): From ee25223e73567ae0aa597b1980752da076ff2fc5 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 12:44:34 +0200 Subject: [PATCH 063/276] Implemented frontend for transactions and invoices --- .../templates/main/month/transaction.html | 27 ----------- nummi/transaction/models.py | 10 ++-- .../templates/transaction/invoice_form.html} | 0 .../transaction_archive_month.html | 2 + .../transaction/transaction_form.html} | 0 .../transaction/transaction_list.html} | 10 +++- nummi/transaction/urls.py | 46 +++++++++---------- nummi/transaction/views.py | 22 ++++----- 8 files changed, 46 insertions(+), 71 deletions(-) delete mode 100644 nummi/main/templates/main/month/transaction.html rename nummi/{main/templates/main/form/invoice.html => transaction/templates/transaction/invoice_form.html} (100%) create mode 100644 nummi/transaction/templates/transaction/transaction_archive_month.html rename nummi/{main/templates/main/form/transaction.html => transaction/templates/transaction/transaction_form.html} (100%) rename nummi/{main/templates/main/list/transaction.html => transaction/templates/transaction/transaction_list.html} (86%) diff --git a/nummi/main/templates/main/month/transaction.html b/nummi/main/templates/main/month/transaction.html deleted file mode 100644 index cb492c1..0000000 --- a/nummi/main/templates/main/month/transaction.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "main/base.html" %} -{% load static %} -{% load main_extras %} -{% load i18n %} -{% block link %} - {{ block.super }} - - -{% endblock %} -{% block body %} -

      {% translate "Transactions" %} – {{ month|date:"F Y"|capfirst }}

      - {% if account %} -

      - {{ account.icon|remix }}{{ account }} -

      - {% endif %} - {% if category %} -

      - {{ category.icon|remix }}{{ category }} -

      - {% endif %} - {% include "main/table/transaction.html" %} -{% endblock %} diff --git a/nummi/transaction/models.py b/nummi/transaction/models.py index 3238a76..7f20c7c 100644 --- a/nummi/transaction/models.py +++ b/nummi/transaction/models.py @@ -67,7 +67,7 @@ class Transaction(UserModel): return reverse("transaction", kwargs={"pk": self.pk}) def get_delete_url(self): - return reverse("del_transaction", kwargs={"pk": self.pk}) + return reverse("del_transaction", args=(self.pk,)) @property def invoices(self): @@ -113,14 +113,10 @@ class Invoice(UserModel): super().delete(*args, **kwargs) def get_absolute_url(self): - return reverse( - "invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk} - ) + return reverse("invoice", args=(self.transaction.pk, self.pk)) def get_delete_url(self): - return reverse( - "del_invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk} - ) + return reverse("del_invoice", args=(self.transaction.pk, self.pk)) class Meta: verbose_name = _("Invoice") diff --git a/nummi/main/templates/main/form/invoice.html b/nummi/transaction/templates/transaction/invoice_form.html similarity index 100% rename from nummi/main/templates/main/form/invoice.html rename to nummi/transaction/templates/transaction/invoice_form.html diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html new file mode 100644 index 0000000..efd440f --- /dev/null +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -0,0 +1,2 @@ +{% extends "transaction/transaction_list.html" %} +{% block h2 %}{{ month|date:"F Y"|capfirst }}{% endblock %} diff --git a/nummi/main/templates/main/form/transaction.html b/nummi/transaction/templates/transaction/transaction_form.html similarity index 100% rename from nummi/main/templates/main/form/transaction.html rename to nummi/transaction/templates/transaction/transaction_form.html diff --git a/nummi/main/templates/main/list/transaction.html b/nummi/transaction/templates/transaction/transaction_list.html similarity index 86% rename from nummi/main/templates/main/list/transaction.html rename to nummi/transaction/templates/transaction/transaction_list.html index de2f7b0..ee087e7 100644 --- a/nummi/main/templates/main/list/transaction.html +++ b/nummi/transaction/templates/transaction/transaction_list.html @@ -21,7 +21,11 @@ type="text/css" /> {% endblock %} {% block body %} -

      {% translate "Transactions" %}

      +

      + {% block h2 %} + {% translate "Transactions" %} + {% endblock %} +

      {% if account %}

      {{ account.icon|remix }}{{ account }} @@ -33,7 +37,9 @@

      {% endif %} {% if search %} - {% translate "Search" %} +

      + {% translate "Search" %} +

      {% endif %} {% if transactions %} {% include "main/list/pagination.html" %} diff --git a/nummi/transaction/urls.py b/nummi/transaction/urls.py index 31866e9..cba5e2e 100644 --- a/nummi/transaction/urls.py +++ b/nummi/transaction/urls.py @@ -3,32 +3,32 @@ from django.urls import path from . import views urlpatterns = [ - path("transactions", views.TransactionListView.as_view(), name="transactions"), - path("transaction", views.TransactionCreateView.as_view(), name="new_transaction"), - path( - "transaction//invoice", - views.InvoiceCreateView.as_view(), - name="new_invoice", - ), - path("transaction/", views.TransactionUpdateView.as_view(), name="transaction"), - path( - "transaction//invoice/", - views.InvoiceUpdateView.as_view(), - name="invoice", - ), - path( - "transaction//delete", - views.TransactionDeleteView.as_view(), - name="del_transaction", - ), - path( - "transaction//invoice//delete", - views.InvoiceDeleteView.as_view(), - name="del_invoice", - ), + path("list", views.TransactionListView.as_view(), name="transactions"), path( "history//", views.TransactionMonthView.as_view(), name="transaction_month", ), + path("new", views.TransactionCreateView.as_view(), name="new_transaction"), + path("", views.TransactionUpdateView.as_view(), name="transaction"), + path( + "/delete", + views.TransactionDeleteView.as_view(), + name="del_transaction", + ), + path( + "/invoice/new", + views.InvoiceCreateView.as_view(), + name="new_invoice", + ), + path( + "/invoice/", + views.InvoiceUpdateView.as_view(), + name="invoice", + ), + path( + "/invoice//delete", + views.InvoiceDeleteView.as_view(), + name="del_invoice", + ), ] diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 42bf7e1..e30825b 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -19,7 +19,6 @@ from .models import Invoice, Transaction class TransactionCreateView(NummiCreateView): model = Transaction form_class = TransactionForm - template_name = "main/form/transaction.html" def get_initial(self): _queryset = Statement.objects.filter(user=self.request.user) @@ -43,32 +42,31 @@ class TransactionCreateView(NummiCreateView): class InvoiceCreateView(NummiCreateView): model = Invoice form_class = InvoiceForm - template_name = "main/form/invoice.html" def form_valid(self, form): form.instance.transaction = get_object_or_404( Transaction.objects.filter(user=self.request.user), - pk=self.kwargs["transaction_pk"], + pk=self.kwargs["transaction"], ) return super().form_valid(form) def get_success_url(self): - return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk}) + return reverse_lazy("transaction", args=(self.object.transaction.pk,)) class TransactionUpdateView(NummiUpdateView): model = Transaction form_class = TransactionForm - template_name = "main/form/transaction.html" + pk_url_kwarg = "transaction" class InvoiceUpdateView(NummiUpdateView): model = Invoice form_class = InvoiceForm - template_name = "main/form/invoice.html" + pk_url_kwarg = "invoice" def get_success_url(self): - return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk}) + return reverse_lazy("transaction", args=(self.object.transaction.pk,)) def get_queryset(self): return ( @@ -76,7 +74,7 @@ class InvoiceUpdateView(NummiUpdateView): .get_queryset() .filter( transaction=get_object_or_404( - Transaction, pk=self.kwargs["transaction_pk"] + Transaction, pk=self.kwargs["transaction"] ) ) ) @@ -84,13 +82,15 @@ class InvoiceUpdateView(NummiUpdateView): class TransactionDeleteView(NummiDeleteView): model = Transaction + pk_url_kwarg = "transaction" class InvoiceDeleteView(NummiDeleteView): model = Invoice + pk_url_kwarg = "invoice" def get_success_url(self): - return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk}) + return reverse_lazy("transaction", args=(self.object.transaction.pk,)) def get_queryset(self): return ( @@ -98,7 +98,7 @@ class InvoiceDeleteView(NummiDeleteView): .get_queryset() .filter( transaction=get_object_or_404( - Transaction, pk=self.kwargs["transaction_pk"] + Transaction, pk=self.kwargs["transaction"] ) ) ) @@ -106,12 +106,10 @@ class InvoiceDeleteView(NummiDeleteView): class TransactionListView(NummiListView): model = Transaction - template_name = "main/list/transaction.html" context_object_name = "transactions" class TransactionMonthView(UserMixin, MonthArchiveView): - template_name = "main/month/transaction.html" model = Transaction date_field = "date" context_object_name = "transactions" From 06c8cab4bf6b275c5e5518e742418e05b7d17265 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 13:27:58 +0200 Subject: [PATCH 064/276] Move PKGBUILD to pkgbuild dir --- PKGBUILD => pkgbuild/PKGBUILD | 0 nummi.nginx => pkgbuild/nummi.nginx | 0 nummi.service => pkgbuild/nummi.service | 0 nummi.sysusers => pkgbuild/nummi.sysusers | 0 nummi.tmpfiles => pkgbuild/nummi.tmpfiles | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename PKGBUILD => pkgbuild/PKGBUILD (100%) rename nummi.nginx => pkgbuild/nummi.nginx (100%) rename nummi.service => pkgbuild/nummi.service (100%) rename nummi.sysusers => pkgbuild/nummi.sysusers (100%) rename nummi.tmpfiles => pkgbuild/nummi.tmpfiles (100%) diff --git a/PKGBUILD b/pkgbuild/PKGBUILD similarity index 100% rename from PKGBUILD rename to pkgbuild/PKGBUILD diff --git a/nummi.nginx b/pkgbuild/nummi.nginx similarity index 100% rename from nummi.nginx rename to pkgbuild/nummi.nginx diff --git a/nummi.service b/pkgbuild/nummi.service similarity index 100% rename from nummi.service rename to pkgbuild/nummi.service diff --git a/nummi.sysusers b/pkgbuild/nummi.sysusers similarity index 100% rename from nummi.sysusers rename to pkgbuild/nummi.sysusers diff --git a/nummi.tmpfiles b/pkgbuild/nummi.tmpfiles similarity index 100% rename from nummi.tmpfiles rename to pkgbuild/nummi.tmpfiles From 2d45fef9752af2d1c865174080ca2bd8a8aa64a9 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 13:31:12 +0200 Subject: [PATCH 065/276] Move config.toml to pkgbuild dir --- config.toml => pkgbuild/config.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename config.toml => pkgbuild/config.toml (100%) diff --git a/config.toml b/pkgbuild/config.toml similarity index 100% rename from config.toml rename to pkgbuild/config.toml From 62f360e77bbcf800c3410ff876bffd19ad8051e2 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 13:33:12 +0200 Subject: [PATCH 066/276] Hide connected as when not logged in --- nummi/main/templates/main/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 274bfd4..98da0b8 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -79,7 +79,7 @@
    • {% endif %}
    - {% if user %} + {% if user.is_authenticated %}

    {% blocktranslate %}Logged in as {{ user }}{% endblocktranslate %}

    From f0a232f366d5cfc2a220b7b423eda490ed34dd96 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 13:47:43 +0200 Subject: [PATCH 067/276] Moved views to adequate apps --- .../templates/account/account_form.html | 18 ++++++++++-------- .../templates/category/category_form.html | 4 ++-- .../templates/category/category_plot.html} | 0 nummi/main/templates/main/index.html | 6 +++--- nummi/search/forms.py | 2 +- .../templates/search/search_form.html} | 0 .../templates/statement/statement_form.html | 4 ++-- .../templates/statement/statement_list.html | 2 +- .../templates/statement/statement_table.html} | 0 .../templates/transaction/history_plot.html} | 0 .../templates/transaction/invoice_table.html} | 0 .../transaction/transaction_form.html | 2 +- .../transaction/transaction_list.html | 2 +- .../transaction/transaction_table.html} | 0 14 files changed, 21 insertions(+), 19 deletions(-) rename nummi/{main/templates/main/plot/category.html => category/templates/category/category_plot.html} (100%) rename nummi/{main/templates/main/form/search.html => search/templates/search/search_form.html} (100%) rename nummi/{main/templates/main/table/statement.html => statement/templates/statement/statement_table.html} (100%) rename nummi/{main/templates/main/plot/history.html => transaction/templates/transaction/history_plot.html} (100%) rename nummi/{main/templates/main/table/invoice.html => transaction/templates/transaction/invoice_table.html} (100%) rename nummi/{main/templates/main/table/transaction.html => transaction/templates/transaction/transaction_table.html} (100%) diff --git a/nummi/account/templates/account/account_form.html b/nummi/account/templates/account/account_form.html index 0e18fbb..91cd0af 100644 --- a/nummi/account/templates/account/account_form.html +++ b/nummi/account/templates/account/account_form.html @@ -10,13 +10,15 @@ {% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} {% block tables %} {% if not form.instance|adding %} -

    {% translate "Statements" %}

    - {% include "main/table/statement.html" %} - {% endif %} - {% if transactions %} -

    {% translate "Transactions" %}

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

    {% translate "History" %}

    - {% include "main/plot/history.html" %} + {% if statements %} +

    {% translate "Statements" %}

    + {% include "statement/statement_table.html" %} + {% endif %} + {% if transactions %} +

    {% translate "Transactions" %}

    + {% include "transaction/transaction_table.html" %} +

    {% translate "History" %}

    + {% include "transaction/history_plot.html" %} + {% endif %} {% endif %} {% endblock %} diff --git a/nummi/category/templates/category/category_form.html b/nummi/category/templates/category/category_form.html index 6223526..d995b04 100644 --- a/nummi/category/templates/category/category_form.html +++ b/nummi/category/templates/category/category_form.html @@ -11,10 +11,10 @@ {% block tables %} {% if transactions %}

    {% translate "Transactions" %}

    - {% include "main/table/transaction.html" %} + {% include "transaction/transaction_table.html" %} {% endif %} {% if history.data %}

    {% translate "History" %}

    - {% include "main/plot/history.html" %} + {% include "transaction/history_plot.html" %} {% endif %} {% endblock %} diff --git a/nummi/main/templates/main/plot/category.html b/nummi/category/templates/category/category_plot.html similarity index 100% rename from nummi/main/templates/main/plot/category.html rename to nummi/category/templates/category/category_plot.html diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index b2dbbaa..9da7a05 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -24,7 +24,7 @@ {% endif %} {% if transactions %}

    {% translate "Transactions" %}

    - {% include "main/table/transaction.html" %} + {% include "transaction/transaction_table.html" %} {% endif %} {% if categories %}

    {% translate "Categories" %}

    @@ -38,10 +38,10 @@ {% endif %} {% if statements %}

    {% translate "Statements" %}

    - {% include "main/table/statement.html" %} + {% include "statement/statement_table.html" %} {% endif %} {% if history.data %}

    {% translate "History" %}

    - {% include "main/plot/history.html" %} + {% include "transaction/history_plot.html" %} {% endif %} {% endblock %} diff --git a/nummi/search/forms.py b/nummi/search/forms.py index 70d6e60..0aba785 100644 --- a/nummi/search/forms.py +++ b/nummi/search/forms.py @@ -3,5 +3,5 @@ from django.utils.translation import gettext_lazy as _ class SearchForm(forms.Form): - template_name = "main/form/search.html" + template_name = "search/search_form.html" search = forms.CharField(label=_("Search"), max_length=128) diff --git a/nummi/main/templates/main/form/search.html b/nummi/search/templates/search/search_form.html similarity index 100% rename from nummi/main/templates/main/form/search.html rename to nummi/search/templates/search/search_form.html diff --git a/nummi/statement/templates/statement/statement_form.html b/nummi/statement/templates/statement/statement_form.html index feaf251..06fe238 100644 --- a/nummi/statement/templates/statement/statement_form.html +++ b/nummi/statement/templates/statement/statement_form.html @@ -21,10 +21,10 @@ {% block tables %} {% if categories %}

    {% translate "Categories" %}

    - {% include "main/plot/category.html" %} + {% include "category/category_plot.html" %} {% endif %} {% if not form.instance|adding %}

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

    - {% include "main/table/transaction.html" %} + {% include "transaction/transaction_table.html" %} {% endif %} {% endblock %} diff --git a/nummi/statement/templates/statement/statement_list.html b/nummi/statement/templates/statement/statement_list.html index f6b31a9..9522c3e 100644 --- a/nummi/statement/templates/statement/statement_list.html +++ b/nummi/statement/templates/statement/statement_list.html @@ -31,7 +31,7 @@ {% endif %} {% if statements %} {% include "main/list/pagination.html" %} - {% include "main/table/statement.html" %} + {% include "statement/statement_table.html" %} {% include "main/list/pagination.html" %} {% else %}

    {% translate "No statements to show" %}

    diff --git a/nummi/main/templates/main/table/statement.html b/nummi/statement/templates/statement/statement_table.html similarity index 100% rename from nummi/main/templates/main/table/statement.html rename to nummi/statement/templates/statement/statement_table.html diff --git a/nummi/main/templates/main/plot/history.html b/nummi/transaction/templates/transaction/history_plot.html similarity index 100% rename from nummi/main/templates/main/plot/history.html rename to nummi/transaction/templates/transaction/history_plot.html diff --git a/nummi/main/templates/main/table/invoice.html b/nummi/transaction/templates/transaction/invoice_table.html similarity index 100% rename from nummi/main/templates/main/table/invoice.html rename to nummi/transaction/templates/transaction/invoice_table.html diff --git a/nummi/transaction/templates/transaction/transaction_form.html b/nummi/transaction/templates/transaction/transaction_form.html index 39e10fb..75eb0d4 100644 --- a/nummi/transaction/templates/transaction/transaction_form.html +++ b/nummi/transaction/templates/transaction/transaction_form.html @@ -17,6 +17,6 @@ {% block tables %} {% if not form.instance|adding %}

    {% translate "Invoices" %}

    - {% include "main/table/invoice.html" %} + {% include "transaction/invoice_table.html" %} {% endif %} {% endblock %} diff --git a/nummi/transaction/templates/transaction/transaction_list.html b/nummi/transaction/templates/transaction/transaction_list.html index ee087e7..cc44397 100644 --- a/nummi/transaction/templates/transaction/transaction_list.html +++ b/nummi/transaction/templates/transaction/transaction_list.html @@ -43,7 +43,7 @@ {% endif %} {% if transactions %} {% include "main/list/pagination.html" %} - {% include "main/table/transaction.html" %} + {% include "transaction/transaction_table.html" %} {% include "main/list/pagination.html" %} {% else %}

    {% translate "No transactions to show" %}

    diff --git a/nummi/main/templates/main/table/transaction.html b/nummi/transaction/templates/transaction/transaction_table.html similarity index 100% rename from nummi/main/templates/main/table/transaction.html rename to nummi/transaction/templates/transaction/transaction_table.html From 2d5f209c41ca5800c2dd1c1bad21bed86a523425 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 13:53:45 +0200 Subject: [PATCH 068/276] Move history to separate app --- nummi/account/templates/account/account_form.html | 2 +- nummi/account/views.py | 2 +- nummi/category/templates/category/category_form.html | 2 +- nummi/category/views.py | 2 +- nummi/history/__init__.py | 0 nummi/history/apps.py | 6 ++++++ nummi/history/migrations/__init__.py | 0 .../templates/history/plot.html} | 0 nummi/{transaction => history}/utils.py | 0 nummi/main/templates/main/index.html | 2 +- nummi/main/views.py | 2 +- nummi/nummi/settings.py | 1 + 12 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 nummi/history/__init__.py create mode 100644 nummi/history/apps.py create mode 100644 nummi/history/migrations/__init__.py rename nummi/{transaction/templates/transaction/history_plot.html => history/templates/history/plot.html} (100%) rename nummi/{transaction => history}/utils.py (100%) diff --git a/nummi/account/templates/account/account_form.html b/nummi/account/templates/account/account_form.html index 91cd0af..bb9f369 100644 --- a/nummi/account/templates/account/account_form.html +++ b/nummi/account/templates/account/account_form.html @@ -18,7 +18,7 @@

    {% translate "Transactions" %}

    {% include "transaction/transaction_table.html" %}

    {% translate "History" %}

    - {% include "transaction/history_plot.html" %} + {% include "history/plot.html" %} {% endif %} {% endif %} {% endblock %} diff --git a/nummi/account/views.py b/nummi/account/views.py index a73c7a8..446ac30 100644 --- a/nummi/account/views.py +++ b/nummi/account/views.py @@ -1,8 +1,8 @@ from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy +from history.utils import history from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView from statement.views import StatementListView -from transaction.utils import history from transaction.views import TransactionListView from .forms import AccountForm diff --git a/nummi/category/templates/category/category_form.html b/nummi/category/templates/category/category_form.html index d995b04..b97f71b 100644 --- a/nummi/category/templates/category/category_form.html +++ b/nummi/category/templates/category/category_form.html @@ -15,6 +15,6 @@ {% endif %} {% if history.data %}

    {% translate "History" %}

    - {% include "transaction/history_plot.html" %} + {% include "history/plot.html" %} {% endif %} {% endblock %} diff --git a/nummi/category/views.py b/nummi/category/views.py index 53d8609..2e2fc1c 100644 --- a/nummi/category/views.py +++ b/nummi/category/views.py @@ -1,7 +1,7 @@ from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy +from history.utils import history from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView -from transaction.utils import history from transaction.views import TransactionListView from .forms import CategoryForm diff --git a/nummi/history/__init__.py b/nummi/history/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/history/apps.py b/nummi/history/apps.py new file mode 100644 index 0000000..af5c1f4 --- /dev/null +++ b/nummi/history/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HistoryConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "history" diff --git a/nummi/history/migrations/__init__.py b/nummi/history/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/transaction/templates/transaction/history_plot.html b/nummi/history/templates/history/plot.html similarity index 100% rename from nummi/transaction/templates/transaction/history_plot.html rename to nummi/history/templates/history/plot.html diff --git a/nummi/transaction/utils.py b/nummi/history/utils.py similarity index 100% rename from nummi/transaction/utils.py rename to nummi/history/utils.py diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 9da7a05..5f194d8 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -42,6 +42,6 @@ {% endif %} {% if history.data %}

    {% translate "History" %}

    - {% include "transaction/history_plot.html" %} + {% include "history/plot.html" %} {% endif %} {% endblock %} diff --git a/nummi/main/views.py b/nummi/main/views.py index e7fbea9..8095486 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -10,9 +10,9 @@ from django.views.generic import ( TemplateView, UpdateView, ) +from history.utils import history from statement.models import Statement from transaction.models import Transaction -from transaction.utils import history class IndexView(LoginRequiredMixin, TemplateView): diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index 28af502..d1cd960 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -51,6 +51,7 @@ INSTALLED_APPS = [ "statement", "transaction", "search", + "history", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", From 210268928bc7c6268727e07f60d3064176c56578 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 13:58:30 +0200 Subject: [PATCH 069/276] Move pagination file up --- nummi/main/templates/main/{list => }/pagination.html | 0 nummi/statement/templates/statement/statement_list.html | 4 ++-- nummi/transaction/templates/transaction/transaction_list.html | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename nummi/main/templates/main/{list => }/pagination.html (100%) diff --git a/nummi/main/templates/main/list/pagination.html b/nummi/main/templates/main/pagination.html similarity index 100% rename from nummi/main/templates/main/list/pagination.html rename to nummi/main/templates/main/pagination.html diff --git a/nummi/statement/templates/statement/statement_list.html b/nummi/statement/templates/statement/statement_list.html index 9522c3e..3e4d97f 100644 --- a/nummi/statement/templates/statement/statement_list.html +++ b/nummi/statement/templates/statement/statement_list.html @@ -30,9 +30,9 @@

    {% endif %} {% if statements %} - {% include "main/list/pagination.html" %} + {% include "main/pagination.html" %} {% include "statement/statement_table.html" %} - {% include "main/list/pagination.html" %} + {% include "main/pagination.html" %} {% else %}

    {% translate "No statements to show" %}

    {% endif %} diff --git a/nummi/transaction/templates/transaction/transaction_list.html b/nummi/transaction/templates/transaction/transaction_list.html index cc44397..069e6aa 100644 --- a/nummi/transaction/templates/transaction/transaction_list.html +++ b/nummi/transaction/templates/transaction/transaction_list.html @@ -42,9 +42,9 @@

    {% endif %} {% if transactions %} - {% include "main/list/pagination.html" %} + {% include "main/pagination.html" %} {% include "transaction/transaction_table.html" %} - {% include "main/list/pagination.html" %} + {% include "main/pagination.html" %} {% else %}

    {% translate "No transactions to show" %}

    {% endif %} From 2e3d76ad19e15599e06853cb960733f4d6bfd772 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 13:59:39 +0200 Subject: [PATCH 070/276] Remove unused templates --- nummi/main/templates/main/tag/pmvalue.html | 9 --------- nummi/main/templates/main/tag/value.html | 5 ----- 2 files changed, 14 deletions(-) delete mode 100644 nummi/main/templates/main/tag/pmvalue.html delete mode 100644 nummi/main/templates/main/tag/value.html diff --git a/nummi/main/templates/main/tag/pmvalue.html b/nummi/main/templates/main/tag/pmvalue.html deleted file mode 100644 index db9eb45..0000000 --- a/nummi/main/templates/main/tag/pmvalue.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "main/tag/value.html" %} -{% block "value" %} - {% if value %} - {% if value > 0 %}+{% endif %} - {{ block.super }} - {% else %} - – - {% endif %} -{% endblock %} diff --git a/nummi/main/templates/main/tag/value.html b/nummi/main/templates/main/tag/value.html deleted file mode 100644 index 3cdbb41..0000000 --- a/nummi/main/templates/main/tag/value.html +++ /dev/null @@ -1,5 +0,0 @@ -{% spaceless %} - - {% block "value" %}{{ value }} €{% endblock %} - -{% endspaceless %} From a3f28631df0b94487e320354212bd89147425306 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 14:06:24 +0200 Subject: [PATCH 071/276] Create base template for lists --- nummi/main/static/main/css/page.css | 3 -- nummi/main/templates/main/list.html | 42 +++++++++++++++ .../templates/statement/statement_list.html | 40 +++----------- .../transaction/transaction_list.html | 52 +++---------------- 4 files changed, 54 insertions(+), 83 deletions(-) delete mode 100644 nummi/main/static/main/css/page.css create mode 100644 nummi/main/templates/main/list.html diff --git a/nummi/main/static/main/css/page.css b/nummi/main/static/main/css/page.css deleted file mode 100644 index 188aa6d..0000000 --- a/nummi/main/static/main/css/page.css +++ /dev/null @@ -1,3 +0,0 @@ -.pagination .current { - font-feature-settings: "tnum", "ss01"; -} diff --git a/nummi/main/templates/main/list.html b/nummi/main/templates/main/list.html new file mode 100644 index 0000000..1e9d53f --- /dev/null +++ b/nummi/main/templates/main/list.html @@ -0,0 +1,42 @@ +{% extends "main/base.html" %} +{% load static %} +{% load main_extras %} +{% load i18n %} +{% block title %} + {% block name %}{% endblock %} + {% if account %}– {{ account }}{% endif %} + {% if category %}– {{ category }}{% endif %} + {% if search %} + – {% translate "Search" %} + {% endif %} + – Nummi +{% endblock %} +{% block link %} + {{ block.super }} + +{% endblock %} +{% block body %} +

    + {% block h2 %}{% endblock %} +

    + {% if account %} +

    + {{ account.icon|remix }}{{ account }} +

    + {% endif %} + {% if category %} +

    + {{ category.icon|remix }}{{ category }} +

    + {% endif %} + {% if search %} +

    + {% translate "Search" %} +

    + {% endif %} + {% include "main/pagination.html" %} + {% block table %}{% endblock %} + {% include "main/pagination.html" %} +{% endblock %} diff --git a/nummi/statement/templates/statement/statement_list.html b/nummi/statement/templates/statement/statement_list.html index 3e4d97f..73bc595 100644 --- a/nummi/statement/templates/statement/statement_list.html +++ b/nummi/statement/templates/statement/statement_list.html @@ -1,39 +1,11 @@ -{% extends "main/base.html" %} -{% load static %} -{% load main_extras %} +{% extends "main/list.html" %} {% load i18n %} -{% block title %} +{% block name %} {% translate "Statements" %} - {% if account %}– {{ account }}{% endif %} - {% if category %}– {{ category }}{% endif %} - – Nummi {% endblock %} -{% block link %} - {{ block.super }} - - +{% block h2 %} + {% translate "Statements" %} {% endblock %} -{% block body %} -

    {% translate "Statements" %}

    - {% if account %} -

    - {{ account.icon|remix }}{{ account }} -

    - {% endif %} - {% if category %} -

    - {{ category.icon|remix }}{{ category }} -

    - {% endif %} - {% if statements %} - {% include "main/pagination.html" %} - {% include "statement/statement_table.html" %} - {% include "main/pagination.html" %} - {% else %} -

    {% translate "No statements to show" %}

    - {% endif %} +{% block table %} + {% include "statement/statement_table.html" %} {% endblock %} diff --git a/nummi/transaction/templates/transaction/transaction_list.html b/nummi/transaction/templates/transaction/transaction_list.html index 069e6aa..a7e8dea 100644 --- a/nummi/transaction/templates/transaction/transaction_list.html +++ b/nummi/transaction/templates/transaction/transaction_list.html @@ -1,51 +1,11 @@ -{% extends "main/base.html" %} -{% load static %} -{% load main_extras %} +{% extends "main/list.html" %} {% load i18n %} -{% block title %} +{% block name %} {% translate "Transactions" %} - {% if account %}– {{ account }}{% endif %} - {% if category %}– {{ category }}{% endif %} - {% if search %} - – {% translate "Search" %} - {% endif %} - – Nummi {% endblock %} -{% block link %} - {{ block.super }} - - +{% block h2 %} + {% translate "Transactions" %} {% endblock %} -{% block body %} -

    - {% block h2 %} - {% translate "Transactions" %} - {% endblock %} -

    - {% if account %} -

    - {{ account.icon|remix }}{{ account }} -

    - {% endif %} - {% if category %} -

    - {{ category.icon|remix }}{{ category }} -

    - {% endif %} - {% if search %} -

    - {% translate "Search" %} -

    - {% endif %} - {% if transactions %} - {% include "main/pagination.html" %} - {% include "transaction/transaction_table.html" %} - {% include "main/pagination.html" %} - {% else %} -

    {% translate "No transactions to show" %}

    - {% endif %} +{% block table %} + {% include "transaction/transaction_table.html" %} {% endblock %} From 2d7957b813866a0a356fc35c6e2c2d01fabdd890 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 14:28:49 +0200 Subject: [PATCH 072/276] Empty tables are now visible --- .../templates/account/account_form.html | 12 +++---- .../templates/category/category_form.html | 6 ++-- nummi/category/views.py | 11 ++++--- nummi/main/static/main/css/table.css | 5 +++ .../templates/statement/statement_table.html | 4 +++ .../templates/transaction/invoice_table.html | 32 +++++++++---------- .../transaction/transaction_table.html | 7 ++++ 7 files changed, 45 insertions(+), 32 deletions(-) diff --git a/nummi/account/templates/account/account_form.html b/nummi/account/templates/account/account_form.html index bb9f369..93918d4 100644 --- a/nummi/account/templates/account/account_form.html +++ b/nummi/account/templates/account/account_form.html @@ -10,13 +10,11 @@ {% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} {% block tables %} {% if not form.instance|adding %} - {% if statements %} -

    {% translate "Statements" %}

    - {% include "statement/statement_table.html" %} - {% endif %} - {% if transactions %} -

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} +

    {% translate "Statements" %}

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

    {% translate "Transactions" %}

    + {% include "transaction/transaction_table.html" %} + {% if history.data %}

    {% translate "History" %}

    {% include "history/plot.html" %} {% endif %} diff --git a/nummi/category/templates/category/category_form.html b/nummi/category/templates/category/category_form.html index b97f71b..aa16a70 100644 --- a/nummi/category/templates/category/category_form.html +++ b/nummi/category/templates/category/category_form.html @@ -9,10 +9,8 @@ {% endblock %} {% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} {% block tables %} - {% if transactions %} -

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} - {% endif %} +

    {% translate "Transactions" %}

    + {% include "transaction/transaction_table.html" %} {% if history.data %}

    {% translate "History" %}

    {% include "history/plot.html" %} diff --git a/nummi/category/views.py b/nummi/category/views.py index 2e2fc1c..36fe0d0 100644 --- a/nummi/category/views.py +++ b/nummi/category/views.py @@ -19,14 +19,17 @@ class CategoryUpdateView(NummiUpdateView): pk_url_kwarg = "category" def get_context_data(self, **kwargs): + _max = 8 data = super().get_context_data(**kwargs) category = data["form"].instance - return data | { - "transactions": category.transaction_set.all()[:8], - "transactions_url": reverse_lazy( + data["transactions"] = category.transaction_set.all()[:_max] + if len(data["transactions"]) == _max: + data["transactions_url"] = reverse_lazy( "category_transactions", args=(category.pk,) - ), + ) + + return data | { "history": history(category.transaction_set), } diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index 1fd4076..ddab001 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -47,3 +47,8 @@ th { .date { text-align: center; } +td.empty { + text-align: center; + opacity: 0.8; + font-weight: 300; +} diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index ebe81b7..12cdccc 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -51,6 +51,10 @@ {{ snap.diff|pmvalue }} {{ snap.sum|pmvalue }} + {% empty %} + + {% translate "No transaction" %} + {% endfor %} diff --git a/nummi/transaction/templates/transaction/invoice_table.html b/nummi/transaction/templates/transaction/invoice_table.html index 87b6447..82a4406 100644 --- a/nummi/transaction/templates/transaction/invoice_table.html +++ b/nummi/transaction/templates/transaction/invoice_table.html @@ -11,25 +11,23 @@ {% translate "Delete" %} - {% if transaction.invoices %} - {% for invoice in transaction.invoices %} - - - {{ invoice.name }} - - - {% translate "File" %} [{{ invoice.file|extension }}] + {% for invoice in transaction.invoices %} + + + {{ invoice.name }} + + + {% translate "File" %} [{{ invoice.file|extension }}] + + + {% translate "Delete" %} - - {% translate "Delete" %} - - - {% endfor %} - {% else %} - - {% translate "No invoice" %} - {% endif %} + {% empty %} + + {% translate "No invoice" %} + + {% endfor %} diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index fe7c34b..27543d2 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -64,6 +64,13 @@ {% endif %} + {% empty %} + + + {% translate "No transaction" %} + + {% endfor %} From a77cfbe339efa238a72432a11a03018f5df8103b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 14:33:07 +0200 Subject: [PATCH 073/276] Fix urls --- nummi/account/urls.py | 2 +- nummi/category/urls.py | 2 +- nummi/search/urls.py | 4 ++-- nummi/transaction/views.py | 3 --- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/nummi/account/urls.py b/nummi/account/urls.py index 43bc302..b46816f 100644 --- a/nummi/account/urls.py +++ b/nummi/account/urls.py @@ -5,7 +5,7 @@ from transaction.views import TransactionMonthView from . import views urlpatterns = [ - path("", views.AccountCreateView.as_view(), name="new_account"), + path("new", views.AccountCreateView.as_view(), name="new_account"), path("", views.AccountUpdateView.as_view(), name="account"), path( "/transactions", diff --git a/nummi/category/urls.py b/nummi/category/urls.py index 3d72bca..4809677 100644 --- a/nummi/category/urls.py +++ b/nummi/category/urls.py @@ -4,7 +4,7 @@ from transaction.views import TransactionMonthView from . import views urlpatterns = [ - path("", views.CategoryCreateView.as_view(), name="new_category"), + path("new", views.CategoryCreateView.as_view(), name="new_category"), path("", views.CategoryUpdateView.as_view(), name="category"), path( "/transactions", diff --git a/nummi/search/urls.py b/nummi/search/urls.py index fb383ab..bec1649 100644 --- a/nummi/search/urls.py +++ b/nummi/search/urls.py @@ -3,6 +3,6 @@ from django.urls import path from . import views urlpatterns = [ - path("search", views.SearchFormView.as_view(), name="search"), - path("search/", views.SearchView.as_view(), name="search"), + path("", views.SearchFormView.as_view(), name="search"), + path("", views.SearchView.as_view(), name="search"), ] diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index e30825b..0a54fc7 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -115,9 +115,6 @@ class TransactionMonthView(UserMixin, MonthArchiveView): context_object_name = "transactions" month_format = "%m" - account = None - category = None - def get_queryset(self): if "account" in self.kwargs: self.account = get_object_or_404( From 13b014e16ef4f107aea5437748e730e71a402ba9 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 14:46:34 +0200 Subject: [PATCH 074/276] Update translations --- nummi/locale/fr_fr/LC_MESSAGES/django.mo | Bin 0 -> 3289 bytes nummi/locale/fr_fr/LC_MESSAGES/django.po | 315 +++++++++++++++++ nummi/main/locale/fr/LC_MESSAGES/django.mo | Bin 3538 -> 0 bytes nummi/main/locale/fr/LC_MESSAGES/django.po | 324 ------------------ nummi/main/templates/main/login.html | 2 +- .../templates/statement/statement_form.html | 4 +- 6 files changed, 318 insertions(+), 327 deletions(-) create mode 100644 nummi/locale/fr_fr/LC_MESSAGES/django.mo create mode 100644 nummi/locale/fr_fr/LC_MESSAGES/django.po delete mode 100644 nummi/main/locale/fr/LC_MESSAGES/django.mo delete mode 100644 nummi/main/locale/fr/LC_MESSAGES/django.po diff --git a/nummi/locale/fr_fr/LC_MESSAGES/django.mo b/nummi/locale/fr_fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..75a8c0ec814a85c28a537e12cd67a01c0cb3f815 GIT binary patch literal 3289 zcma)-ON<=V6^3sJhA=od5C{+RxJiK6;2Mv|2Fze=#-1@AWKSm3V+$!nsN8j@r|jyg zrXFKv0TPjt1&<8~B`nA$L1-jc<)KJ%#G+vVY!DJkR#?g+S)c?(LI@!i`Tpu#-Hp7- zRh|Cs`<{Ewxu-wA=F*o9c9%)V^mSTbdrc7M_Dz|2)+G zzk*u#Ti^dC)cimD@wedjXul7o*FT{4{S@k)&)|3Am+(4x6_?)i4P$PH>i-FwYd;5> zViI@*Ea74Jf*=3DkN*uyuM1FmeggT-=lp0Nb;_@oL!Ea6lz%7T)o>O{-xidfXQ0l_ zp!PWjwf{4oFL=HJW#`wR=KUUO-CrS}dB?XufYRgdzJ0;-Ur_%36v|FtK>2G6hwX;f zLFqFO^&}pF^4}tqoqh&&uP30^{T#|)FG9_G9qOKcf;#72sB=E_;};;G`42xFZN7vz z!7WVEdJDDhZm4@5g4*X{sC7%8k3qfPHOOav;rm~Ny7zCO?E4zjyz>xu*EXAhyAYMT z5S^oqb>mq3R_eO1Y_$)$ANe7oqWj;C$WMxC#m_y6 z{M?l7RODBS>_Oz~JCUaRsHs3wCKufig{9kB*rJ48mzHL>-Oj2J3o{!t{2-{_x zrF7)ZZe*1$DqlGlrlpNC8@a@l&K@YrJWD$V@7R%@KI7Wu&f>ss-(?S(!&TIAr8yF& zZI^)3b+SBmMN{1{N3wJ^&b#JF?ils3q1S8BYukf`%&uqII9<=;_Hg#-K7M@c24+s% z&bigFN=hiDLDA0Ro^EL7;?-4`I}|kYX=G)hy0RW76}2aOE-g^_Xq-56EH26{-!R9r zt}_d5RxhOOy6zQP7_czMvkqF;$f{E8xQHGb7Gox>3YU)=%$&&5a?LD;?7ryE4XvR1 z&;)8U_g8&%d9}uGzLzA7G_w2Hl){|ECgxOa{8KSE3X^1X z$HENz#%@}er94yc1q)Gd%H;)qonc{bEu2_(y(}+-6GbPEg2Pp(2v)KgJF)cRVA-w5 zni(iUX6*FT^xj}N*rhHX zD7}=qC@$^Z$?3`I3A4;2SnqGzsIpB;;2Cy%ICTJC{TZUxwI448&dU@Ps)&G?fX4AAva5YJxzd6bZzTV^oP00J3L)c2eIMB83r^D%S z4A+f%SIJ>yEx&H*gcw^nA6$!>`>sjy)_3To@n)NiI{^qDnm}mqq zy403R$!tAl_=_`3T$shIi&X0_tQsTM%_RAn35R`+0h=|A`Gyn5-k_PS+Hys+#y7`j zEQRslU#B*l{KUa#xxX2waT$k6te4*k(T_)@)UoW^YmQt*b(X2Qt@YOXs4StDw&Pxy z)LxStu+M4y<~`x}lzr=HZ^&Oy)+I>$uVb|FgjD|#c-LMW_jL+r{u57@G0Nios?Jn3 Z^a4sfi^LU8oRO`3H;<+s7&Av<{s%ix6m|dr literal 0 HcmV?d00001 diff --git a/nummi/locale/fr_fr/LC_MESSAGES/django.po b/nummi/locale/fr_fr/LC_MESSAGES/django.po new file mode 100644 index 0000000..4bbca6e --- /dev/null +++ b/nummi/locale/fr_fr/LC_MESSAGES/django.po @@ -0,0 +1,315 @@ +# Nummi. +# Copyright (C) 2023 +# This file is distributed under the same license as the Nummi package. +# edpibu , 2023 +# +msgid "" +msgstr "" +"Project-Id-Version: alpha\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-04-22 14:45+0200\n" +"PO-Revision-Date: 2023-04-22 14:46+0200\n" +"Last-Translator: Edgar P. Burkhart \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.2.2\n" + +#: .\account\models.py:11 .\account\models.py:37 .\account\models.py:45 +#: .\statement\templates\statement\statement_table.html:24 +#: .\transaction\models.py:48 +#: .\transaction\templates\transaction\transaction_table.html:35 +msgid "Account" +msgstr "Compte" + +#: .\account\models.py:11 .\category\models.py:12 .\transaction\models.py:19 +#: .\transaction\models.py:89 +#: .\transaction\templates\transaction\invoice_table.html:9 +#: .\transaction\templates\transaction\transaction_table.html:28 +msgid "Name" +msgstr "Nom" + +#: .\account\models.py:15 .\category\models.py:17 +msgid "Icon" +msgstr "Icône" + +#: .\account\models.py:17 +msgid "Default" +msgstr "Défaut" + +#: .\account\models.py:38 .\main\templates\main\index.html:16 +msgid "Accounts" +msgstr "Comptes" + +#: .\account\templates\account\account_form.html:5 +#: .\main\templates\main\base.html:50 +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_form.html:13 +#: .\main\templates\main\base.html:38 .\main\templates\main\index.html:40 +#: .\statement\models.py:90 +#: .\statement\templates\statement\statement_list.html:4 +#: .\statement\templates\statement\statement_list.html:7 +msgid "Statements" +msgstr "Relevés" + +#: .\account\templates\account\account_form.html:15 +#: .\category\templates\category\category_form.html:12 +#: .\main\templates\main\base.html:44 .\main\templates\main\index.html:26 +#: .\statement\templates\statement\statement_form.html:27 +#: .\statement\templates\statement\statement_table.html:28 +#: .\transaction\models.py:83 +#: .\transaction\templates\transaction\transaction_list.html:4 +#: .\transaction\templates\transaction\transaction_list.html:7 +msgid "Transactions" +msgstr "Transactions" + +#: .\account\templates\account\account_form.html:18 +#: .\category\templates\category\category_form.html:15 +#: .\main\templates\main\index.html:44 +msgid "History" +msgstr "Historique" + +#: .\category\models.py:12 .\category\models.py:32 +#: .\category\templates\category\category_plot.html:14 +#: .\transaction\models.py:38 +#: .\transaction\templates\transaction\transaction_table.html:32 +msgid "Category" +msgstr "Catégorie" + +#: .\category\models.py:19 +msgid "Budget" +msgstr "Budget" + +#: .\category\models.py:33 .\main\templates\main\index.html:30 +#: .\statement\templates\statement\statement_form.html:23 +msgid "Categories" +msgstr "Catégories" + +#: .\category\templates\category\category_form.html:5 +#: .\main\templates\main\base.html:60 +msgid "Create category" +msgstr "Créer une catégorie" + +#: .\category\templates\category\category_form.html:8 +msgid "New category" +msgstr "Nouvelle catégorie" + +#: .\category\templates\category\category_plot.html:15 +#: .\history\templates\history\plot.html:16 +msgid "Expenses" +msgstr "Dépenses" + +#: .\category\templates\category\category_plot.html:16 +#: .\history\templates\history\plot.html:17 +msgid "Income" +msgstr "Revenus" + +#: .\history\templates\history\plot.html:15 +msgid "Month" +msgstr "Mois" + +#: .\main\models.py:10 +msgid "User" +msgstr "Utilisateur" + +#: .\main\templates\main\base.html:27 +msgid "Skip to main content" +msgstr "Aller au contenu principal" + +#: .\main\templates\main\base.html:33 +msgid "Home" +msgstr "Accueil" + +#: .\main\templates\main\base.html:55 +#: .\statement\templates\statement\statement_form.html:5 +#: .\statement\templates\statement\statement_table.html:5 +msgid "Create statement" +msgstr "Créer un relevé" + +#: .\main\templates\main\base.html:65 +#: .\transaction\templates\transaction\transaction_form.html:5 +#: .\transaction\templates\transaction\transaction_table.html:5 +msgid "Create transaction" +msgstr "Créer une transaction" + +#: .\main\templates\main\base.html:70 .\main\templates\main\list.html:10 +#: .\main\templates\main\list.html:36 .\search\forms.py:7 +#: .\search\templates\search\search.html:6 +#: .\search\templates\search\search.html:18 +#: .\search\templates\search\search_form.html:5 +msgid "Search" +msgstr "Rechercher" + +#: .\main\templates\main\base.html:73 +msgid "Log out" +msgstr "Se déconnecter" + +#: .\main\templates\main\base.html:78 .\main\templates\main\form\login.html:6 +#: .\main\templates\main\login.html:14 +msgid "Log in" +msgstr "Se connecter" + +#: .\main\templates\main\base.html:84 +#, python-format +msgid "Logged in as %(user)s" +msgstr "Connecté en tant que %(user)s" + +#: .\main\templates\main\confirm_delete.html:19 +#, 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:23 +msgid "Cancel" +msgstr "Annuler" + +#: .\main\templates\main\confirm_delete.html:24 +msgid "Confirm" +msgstr "Confirmer" + +#: .\main\templates\main\form\fileinput.html:8 .\statement\models.py:42 +#: .\transaction\models.py:94 +#: .\transaction\templates\transaction\invoice_table.html:10 +#: .\transaction\templates\transaction\invoice_table.html:20 +msgid "File" +msgstr "Fichier" + +#: .\main\templates\main\form\form_base.html:30 +#: .\transaction\templates\transaction\invoice_table.html:11 +#: .\transaction\templates\transaction\invoice_table.html:23 +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 +msgid "Create" +msgstr "Créer" + +#: .\main\templates\main\form\form_base.html:36 +msgid "Save" +msgstr "Sauvegarder" + +#: .\statement\forms.py:28 +msgid "Add transactions" +msgstr "Ajouter des transactions" + +#: .\statement\models.py:15 +msgid "End date" +msgstr "Date de fin" + +#: .\statement\models.py:17 +msgid "Start date" +msgstr "Date de début" + +#: .\statement\models.py:20 +msgid "End value" +msgstr "Valeur finale" + +#: .\statement\models.py:23 +msgid "Start value" +msgstr "Valeur initiale" + +#: .\statement\models.py:29 +#: .\statement\templates\statement\statement_table.html:27 +msgid "Difference" +msgstr "Différence" + +#: .\statement\models.py:36 +msgid "Transaction difference" +msgstr "Différence des transactions" + +#: .\statement\models.py:49 +#, python-format +msgid "%(date)s statement" +msgstr "Relevé du %(date)s" + +#: .\statement\models.py:89 .\transaction\models.py:43 +msgid "Statement" +msgstr "Relevé" + +#: .\statement\templates\statement\statement_form.html:8 +msgid "New statement" +msgstr "Nouveau relevé" + +#: .\statement\templates\statement\statement_table.html:22 +#: .\transaction\models.py:25 +#: .\transaction\templates\transaction\transaction_table.html:27 +msgid "Date" +msgstr "Date" + +#: .\statement\templates\statement\statement_table.html:26 +#: .\transaction\models.py:23 +#: .\transaction\templates\transaction\transaction_table.html:29 +msgid "Value" +msgstr "Valeur" + +#: .\statement\templates\statement\statement_table.html:56 +#: .\transaction\templates\transaction\transaction_table.html:71 +msgid "No transaction" +msgstr "Aucune transaction" + +#: .\statement\templates\statement\statement_table.html:64 +msgid "View all statements" +msgstr "Voir tous les relevés" + +#: .\transaction\models.py:19 .\transaction\models.py:82 +msgid "Transaction" +msgstr "Transaction" + +#: .\transaction\models.py:21 +msgid "Description" +msgstr "Description" + +#: .\transaction\models.py:26 +msgid "Real date" +msgstr "Date réelle" + +#: .\transaction\models.py:28 +#: .\transaction\templates\transaction\transaction_table.html:30 +msgid "Trader" +msgstr "Commerçant" + +#: .\transaction\models.py:31 +msgid "Payment" +msgstr "Paiement" + +#: .\transaction\models.py:89 .\transaction\models.py:122 +msgid "Invoice" +msgstr "Facture" + +#: .\transaction\models.py:123 +#: .\transaction\templates\transaction\transaction_form.html:19 +msgid "Invoices" +msgstr "Factures" + +#: .\transaction\templates\transaction\invoice_form.html:4 +#: .\transaction\templates\transaction\invoice_table.html:35 +msgid "Create invoice" +msgstr "Créer une facture" + +#: .\transaction\templates\transaction\invoice_form.html:7 +msgid "New invoice" +msgstr "Nouvelle facture" + +#: .\transaction\templates\transaction\invoice_table.html:28 +msgid "No invoice" +msgstr "Aucune facture" + +#: .\transaction\templates\transaction\transaction_form.html:8 +msgid "New transaction" +msgstr "Nouvelle transaction" + +#: .\transaction\templates\transaction\transaction_table.html:80 +msgid "View all transactions" +msgstr "Voir toutes les transactions" diff --git a/nummi/main/locale/fr/LC_MESSAGES/django.mo b/nummi/main/locale/fr/LC_MESSAGES/django.mo deleted file mode 100644 index a7f3afafc73a0d279d3f4d0c598add86fbdc8f76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3538 zcma)-O^h5z6~~Kw@Ues#5|bE0LJ?xe#_9F!8e(U>yYbpyD=T8N+7C;>h4ysKY=@ri zNq?-p9D*Xm0mFeqq9{T_Mj%<6Z~++!5iVhba?61OLPCTSLI?>7fy5yo@%wkzd<07< zt$F>cs(w}V>b+NQ-n!?G7Y)xD_0C-w%HYKMa2j z7vXQ=d*Oaw;sd;9;KNY*0o44*AwRRk>u$IL_rXoyk9@y?TK^kR`@aBX(haC}ehm4U zo4h^>pT)7%O5{VrZwXTKkR(DM<<&m84- z2p)$V-FyjZ{jWgTJ!o_pd>n_a!KMZ@~TVr|^FG2IOb{$V>MB8EXB%L#_WW zC_DcRHSczel->bY`x|Qiz>h!S$In8|yXe1f!TackP4?*pJ6l%TWke^xfT=m?7A7VU&(#xRc?Lw9`&-?y&pzL|U z_rK@)L&(w1t5E*B>G?WTJl}-J;9F33-Hp=i@Ls4qzaPq*e}J<8FYx2=pHTYyIIQ#zK<)P^)Ok-reLoE~f7Npf z>i&n2CCqbv{M%6ecnQky--pus86+L*z2#3mI)`F^1X0XT89z_b(zzZ$4k9Ozjv@~upG8K`W1|Lq98vD+{^*hal|y=#8`QCRl(u}XN3qbO+NC(@ zOmm2QuJbM-4;rFnln|@bEP>OrX81n()F@DcEzZ< zYtCkAH_rR!Z0`8#VMA|MGip1{M6_!y8>c&2+!<>ZX*ejhGxljdPtNBDrcPweX`6Gd z8&*jPbqG*&@_3*Vn{#ou>vD&|W+jcRTvj)B!la^gb>Py11DuZ&XP%0SGRt?(Q(51c z)ef^)(@x#?2Cg;f^yK zF3daIX5*{zKnwLlj)Xf(<=BRgPGL66Fei89(Wcq>l(tXDajP(!c^J7|80F%0+eVWp zO{o-S3)h)Tb*L`IoIFgDNxBziJT`qqvzBK{-C#8eF1frQ3);3d+nSx5S$BghFM|t3 zFOGsURj&v(v$maC`$Dkpc4BD;%BQxSZ_UpKbMwLcoSi$-KHfUonrpRYo(_vLsO?Ka z5~XcjG>9)(cA*!Sr(M(w^Fi$L+|73LC6v=%74}@P>B9cIQ^k}Es~1)#R?p40X1G!E zd$74Xz_8L?EsqTn0&>cBw!@r|EpBa|4^B?>2T#7o|q>-;i@w1dE>U!nkgqk$GnL6UL%;qlnWzoR)L?~WCw zGRG`e9Ytz1r|yp8VTadk*zLxhZO-Ktr{=!n^I8};e}=WM6>Q8i>%+Y`jmtPpVwIGY zG7=CKI?a, 2022. -# -msgid "" -msgstr "" -"Project-Id-Version: 0.0.1\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-04-22 09:41+0200\n" -"PO-Revision-Date: 2022-12-21 17:30+0100\n" -"Last-Translator: edpibu \n" -"Language-Team: edpibu \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#: .\forms.py:95 -msgid "Add transactions" -msgstr "Ajouter des transactions" - -#: .\forms.py:113 .\templates\main\base.html:70 -#: .\templates\main\form\search.html:5 .\templates\main\list\transaction.html:9 -#: .\templates\main\list\transaction.html:26 .\templates\main\search.html:6 -#: .\templates\main\search.html:18 -msgid "Search" -msgstr "Rechercher" - -#: .\models.py:18 -msgid "User" -msgstr "Utilisateur" - -#: .\models.py:37 .\models.py:71 .\models.py:79 .\models.py:229 -#: .\templates\main\table\snapshot.html:24 -#: .\templates\main\table\transaction.html:35 -msgid "Account" -msgstr "Compte" - -#: .\models.py:37 .\models.py:89 .\models.py:200 .\models.py:270 -#: .\templates\main\table\invoice.html:9 -#: .\templates\main\table\transaction.html:28 -msgid "Name" -msgstr "Nom" - -#: .\models.py:41 .\models.py:94 -msgid "Icon" -msgstr "Icône" - -#: .\models.py:43 -msgid "Default" -msgstr "Défaut" - -#: .\models.py:72 .\templates\main\index.html:16 -msgid "Accounts" -msgstr "Comptes" - -#: .\models.py:89 .\models.py:113 .\models.py:219 -#: .\templates\main\plot\category.html:14 -#: .\templates\main\table\transaction.html:32 -msgid "Category" -msgstr "Catégorie" - -#: .\models.py:96 -msgid "Budget" -msgstr "Budget" - -#: .\models.py:114 .\templates\main\form\snapshot.html:23 -#: .\templates\main\index.html:30 -msgid "Categories" -msgstr "Catégories" - -#: .\models.py:119 -msgid "End date" -msgstr "Date de fin" - -#: .\models.py:121 -msgid "Start date" -msgstr "Date de début" - -#: .\models.py:124 -msgid "End value" -msgstr "Valeur de fin" - -#: .\models.py:127 -msgid "Start value" -msgstr "Valeur de début" - -#: .\models.py:133 .\templates\main\table\snapshot.html:27 -msgid "Difference" -msgstr "Différence" - -#: .\models.py:140 -msgid "Transaction difference" -msgstr "Différence des transactions" - -#: .\models.py:146 .\models.py:275 .\templates\main\form\fileinput.html:8 -#: .\templates\main\table\invoice.html:10 -#: .\templates\main\table\invoice.html:21 -msgid "File" -msgstr "Fichier" - -#: .\models.py:153 -#, python-format -msgid "%(date)s statement" -msgstr "Relevé du %(date)s" - -#: .\models.py:193 .\models.py:224 -msgid "Statement" -msgstr "Relevé" - -#: .\models.py:194 .\templates\main\form\account.html:13 -#: .\templates\main\list\snapshot.html:6 .\templates\main\list\snapshot.html:20 -msgid "Statements" -msgstr "Relevés" - -#: .\models.py:200 .\models.py:263 -msgid "Transaction" -msgstr "Transaction" - -#: .\models.py:202 -msgid "Description" -msgstr "Description" - -#: .\models.py:204 .\templates\main\table\snapshot.html:26 -#: .\templates\main\table\transaction.html:29 -msgid "Value" -msgstr "Valeur" - -#: .\models.py:206 .\templates\main\table\snapshot.html:22 -#: .\templates\main\table\transaction.html:27 -msgid "Date" -msgstr "Date" - -#: .\models.py:207 -msgid "Real date" -msgstr "Date réelle" - -#: .\models.py:209 .\templates\main\table\transaction.html:30 -msgid "Trader" -msgstr "Commerçant" - -#: .\models.py:212 -msgid "Payment" -msgstr "Paiement" - -#: .\models.py:264 .\templates\main\base.html:44 -#: .\templates\main\form\account.html:17 .\templates\main\form\category.html:13 -#: .\templates\main\form\snapshot.html:27 .\templates\main\index.html:26 -#: .\templates\main\list\transaction.html:6 -#: .\templates\main\list\transaction.html:23 -#: .\templates\main\month\transaction.html:15 -#: .\templates\main\table\snapshot.html:28 -msgid "Transactions" -msgstr "Transactions" - -#: .\models.py:270 .\models.py:307 -msgid "Invoice" -msgstr "Facture" - -#: .\models.py:308 .\templates\main\form\transaction.html:18 -msgid "Invoices" -msgstr "Factures" - -#: .\templates\main\base.html:27 -msgid "Skip to main content" -msgstr "Aller au contenu principal" - -#: .\templates\main\base.html:33 -msgid "Home" -msgstr "Accueil" - -#: .\templates\main\base.html:38 .\templates\main\index.html:40 -msgid "Snapshots" -msgstr "Relevés" - -#: .\templates\main\base.html:50 .\templates\main\form\account.html:5 -msgid "Create account" -msgstr "Créer un compte" - -#: .\templates\main\base.html:55 .\templates\main\form\snapshot.html:5 -msgid "Create snapshot" -msgstr "Créer un relevé" - -#: .\templates\main\base.html:60 .\templates\main\form\category.html:5 -msgid "Create category" -msgstr "Créer une catégorie" - -#: .\templates\main\base.html:65 .\templates\main\form\transaction.html:4 -#: .\templates\main\table\transaction.html:5 -msgid "Create transaction" -msgstr "Créer une transaction" - -#: .\templates\main\base.html:73 -msgid "Log out" -msgstr "Se déconnecter" - -#: .\templates\main\base.html:78 .\templates\main\form\login.html:6 -msgid "Log in" -msgstr "Se connecter" - -#: .\templates\main\base.html:83 -msgid "Logged in as %(user)s" -msgstr "Connecté en tant que %(user)s" - -#: .\templates\main\confirm_delete.html:19 -#, python-format -msgid "Are you sure you want do delete %(object)s ?" -msgstr "Êtes-vous sûr de vouloir supprimer %(object)s ?" - -#: .\templates\main\confirm_delete.html:23 -msgid "Cancel" -msgstr "Annuler" - -#: .\templates\main\confirm_delete.html:24 -msgid "Confirm" -msgstr "Confirmer" - -#: .\templates\main\form\account.html:8 -msgid "New account" -msgstr "Nouveau compte" - -#: .\templates\main\form\account.html:19 .\templates\main\form\category.html:15 -#: .\templates\main\index.html:44 -msgid "History" -msgstr "Historique" - -#: .\templates\main\form\category.html:8 -msgid "New category" -msgstr "Nouvelle catégorie" - -#: .\templates\main\form\form_base.html:29 -#: .\templates\main\table\invoice.html:11 -#: .\templates\main\table\invoice.html:24 -msgid "Delete" -msgstr "Supprimer" - -#: .\templates\main\form\form_base.html:31 -msgid "Reset" -msgstr "Réinitialiser" - -#: .\templates\main\form\form_base.html:33 -msgid "Create" -msgstr "Créer" - -#: .\templates\main\form\form_base.html:35 -msgid "Save" -msgstr "Enregistrer" - -#: .\templates\main\form\invoice.html:4 .\templates\main\table\invoice.html:37 -msgid "Create invoice" -msgstr "Créer une facture" - -#: .\templates\main\form\invoice.html:7 -msgid "New invoice" -msgstr "Nouvelle facture" - -#: .\templates\main\form\snapshot.html:8 -msgid "New snapshot" -msgstr "Nouveau relevé" - -#: .\templates\main\form\transaction.html:7 -msgid "New transaction" -msgstr "Nouvelle transaction" - -#: .\templates\main\list\snapshot.html:27 -msgid "No snapshots to show" -msgstr "Aucun relevé à afficher" - -#: .\templates\main\list\transaction.html:33 -msgid "No transactions to show" -msgstr "Aucune transaction à afficher" - -#: .\templates\main\login.html:14 -msgid "Log In" -msgstr "Se connecter" - -#: .\templates\main\plot\category.html:15 .\templates\main\plot\history.html:16 -msgid "Expenses" -msgstr "Dépenses" - -#: .\templates\main\plot\category.html:16 .\templates\main\plot\history.html:17 -msgid "Income" -msgstr "Revenus" - -#: .\templates\main\plot\history.html:15 -msgid "Month" -msgstr "Mois" - -#: .\templates\main\table\invoice.html:30 -msgid "No invoice" -msgstr "Aucune facture" - -#: .\templates\main\table\snapshot.html:5 -msgid "Create statement" -msgstr "Créer un relevé" - -#: .\templates\main\table\snapshot.html:60 -msgid "View all statements" -msgstr "Voir tous les relevés" - -#: .\templates\main\table\transaction.html:73 -msgid "View all transactions" -msgstr "Voir toutes les transactions" - -#~ msgid "New statement" -#~ msgstr "Nouveau relevé" - -#~ msgid "Create Account" -#~ msgstr "Créer Compte" - -#~ msgid "Create Snapshot" -#~ msgstr "Créer Relevé" - -#~ msgid "Create Category" -#~ msgstr "Créer Catégorie" - -#~ msgid "Create Transaction" -#~ msgstr "Créer Transaction" - -#, python-format -#~ msgid "Create %(name)s" -#~ msgstr "Créer %(name)s" diff --git a/nummi/main/templates/main/login.html b/nummi/main/templates/main/login.html index 8cc7cc7..ab579cb 100644 --- a/nummi/main/templates/main/login.html +++ b/nummi/main/templates/main/login.html @@ -11,7 +11,7 @@ type="text/css" /> {% endblock %} {% block body %} -

    {% translate "Log In" %}

    +

    {% translate "Log in" %}

    {% csrf_token %} {% include "main/form/login.html" %} diff --git a/nummi/statement/templates/statement/statement_form.html b/nummi/statement/templates/statement/statement_form.html index 06fe238..bb40e07 100644 --- a/nummi/statement/templates/statement/statement_form.html +++ b/nummi/statement/templates/statement/statement_form.html @@ -2,10 +2,10 @@ {% load main_extras %} {% load i18n %} {% block title_new %} - {% translate "Create snapshot" %} + {% translate "Create statement" %} {% endblock %} {% block h2_new %} - {% translate "New snapshot" %} + {% translate "New statement" %} {% endblock %} {% block h2 %} {{ form.instance.sum|check:form.instance.diff }} From 5abb9a3b3067a0bd60ed7a069bde851c4dbd0cf0 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 15:08:21 +0200 Subject: [PATCH 075/276] Update settings for locale --- nummi/nummi/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index d1cd960..282d93c 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -63,6 +63,7 @@ INSTALLED_APPS = [ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", @@ -118,9 +119,10 @@ AUTH_PASSWORD_VALIDATORS = [] LANGUAGE_CODE = "fr-fr" TIME_ZONE = CONFIG.get("time_zone", "CET") USE_I18N = True +USE_L10N = True USE_TZ = True LOCALE_PATHS = [ - "locale", + BASE_DIR.joinpath("locale"), ] # Static files (CSS, JavaScript, Images) From d478f038ba60f65948dd36f08b395c9c24f2df54 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 15:10:34 +0200 Subject: [PATCH 076/276] Log BASE_DIR --- nummi/nummi/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index 282d93c..a3f7d18 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -24,6 +24,7 @@ else: # Build paths inside the project like this: BASE_DIR / 'subdir'. MEDIA_CONF = CONFIG.get("media", {}) BASE_DIR = Path(__file__).resolve().parent.parent +print(BASE_DIR) MEDIA_ROOT = Path(MEDIA_CONF.get("root", "/var/lib/nummi")) MEDIA_URL = "media/" From 068dab59d88a3a75686f04ced26b10d56850b3f5 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 15:11:51 +0200 Subject: [PATCH 077/276] Log LOCALE_PATHS --- nummi/nummi/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index a3f7d18..d3b1906 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -125,6 +125,7 @@ USE_TZ = True LOCALE_PATHS = [ BASE_DIR.joinpath("locale"), ] +print(LOCALE_PATHS) # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ From 681651109a762263a3d4d6f6b15029c489fe0576 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 15:24:12 +0200 Subject: [PATCH 078/276] Fix translations --- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 0 -> 762 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 58 ++++ .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 0 -> 813 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 63 ++++ .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 0 -> 454 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 30 ++ nummi/locale/fr_fr/LC_MESSAGES/django.mo | Bin 3289 -> 0 bytes nummi/locale/fr_fr/LC_MESSAGES/django.po | 315 ------------------ nummi/main/locale/fr_FR/LC_MESSAGES/django.mo | Bin 0 -> 1596 bytes nummi/main/locale/fr_FR/LC_MESSAGES/django.po | 118 +++++++ nummi/nummi/settings.py | 5 - .../search/locale/fr_FR/LC_MESSAGES/django.mo | Bin 0 -> 395 bytes .../search/locale/fr_FR/LC_MESSAGES/django.po | 24 ++ .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 0 -> 1305 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 107 ++++++ .../templates/statement/statement_table.html | 2 +- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 0 -> 1392 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 125 +++++++ 18 files changed, 526 insertions(+), 321 deletions(-) create mode 100644 nummi/account/locale/fr_FR/LC_MESSAGES/django.mo create mode 100644 nummi/account/locale/fr_FR/LC_MESSAGES/django.po create mode 100644 nummi/category/locale/fr_FR/LC_MESSAGES/django.mo create mode 100644 nummi/category/locale/fr_FR/LC_MESSAGES/django.po create mode 100644 nummi/history/locale/fr_FR/LC_MESSAGES/django.mo create mode 100644 nummi/history/locale/fr_FR/LC_MESSAGES/django.po delete mode 100644 nummi/locale/fr_fr/LC_MESSAGES/django.mo delete mode 100644 nummi/locale/fr_fr/LC_MESSAGES/django.po create mode 100644 nummi/main/locale/fr_FR/LC_MESSAGES/django.mo create mode 100644 nummi/main/locale/fr_FR/LC_MESSAGES/django.po create mode 100644 nummi/search/locale/fr_FR/LC_MESSAGES/django.mo create mode 100644 nummi/search/locale/fr_FR/LC_MESSAGES/django.po create mode 100644 nummi/statement/locale/fr_FR/LC_MESSAGES/django.mo create mode 100644 nummi/statement/locale/fr_FR/LC_MESSAGES/django.po create mode 100644 nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo create mode 100644 nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po diff --git a/nummi/account/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/account/locale/fr_FR/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..1829874bb523006c772335f1cf9474c1f6b15411 GIT binary patch literal 762 zcmZ9JJCD;q5XTodUIw8%IwXn}4Fy``_&^-yA@1^k2y#RrJi61KJu!xO*R0op`yhyp z5-B|rB}F%sMUC)Q02R;YSg8SfQNJA&!e>l48{}Eh=|Ec!BfUfQ<=<>dS z$G{(;oBtDZ`}_u7-#;K)F3shghTMFFm*#eJ?`X``9Hb4L4l<#y|Y(~sm#Z|!H+dm-+VBC}e0s42-T-}FO2^jKTxg{2zq28%fvUMU{0K6DmyoT0fw_NF2n ZV)0qw3Oa|X8Of?i9?_I$i}`=2_zRS>w-5jT literal 0 HcmV?d00001 diff --git a/nummi/account/locale/fr_FR/LC_MESSAGES/django.po b/nummi/account/locale/fr_FR/LC_MESSAGES/django.po new file mode 100644 index 0000000..0d61184 --- /dev/null +++ b/nummi/account/locale/fr_FR/LC_MESSAGES/django.po @@ -0,0 +1,58 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-04-22 15:16+0200\n" +"PO-Revision-Date: 2023-04-22 15:17+0200\n" +"Last-Translator: Edgar P. Burkhart \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.2.2\n" + +#: .\account\models.py:11 .\account\models.py:37 .\account\models.py:45 +msgid "Account" +msgstr "Compte" + +#: .\account\models.py:11 +msgid "Name" +msgstr "Nom" + +#: .\account\models.py:15 +msgid "Icon" +msgstr "Icône" + +#: .\account\models.py:17 +msgid "Default" +msgstr "Défaut" + +#: .\account\models.py:38 +msgid "Accounts" +msgstr "Comptes" + +#: .\account\templates\account\account_form.html:5 +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_form.html:13 +msgid "Statements" +msgstr "Relevés" + +#: .\account\templates\account\account_form.html:15 +msgid "Transactions" +msgstr "Transactions" + +#: .\account\templates\account\account_form.html:18 +msgid "History" +msgstr "Historique" diff --git a/nummi/category/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/category/locale/fr_FR/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..4dfa16e82cd635d8d957ae5b6163dbe69f6c8601 GIT binary patch literal 813 zcmY+B!EO^V5QYsDC`%;-h#LoU0tq>6Hbsh-f>6>FA~hw7Qp5!zcXzTF5IxUxQ)a(K1$G(9lbM0`J1Vz(d`)^Nd{ue}F+}0p0?C zf>*#l%luz3)c*s69=lLIY!keRa0?7N9K_KKWuc_9kPjC63ulINf(6~68UAZ!7qO_$ zZyS%%G@z-mUi4g{a?vUaCx!;;{tGdnV-f-j9UT%c_T`6uwRsSxdE% zeS+<}C}lxnNOA(%Rh;sXeX}=+dfi@U-E6%UM{T1$X=u)-Xv))xe=r?Or62JOYL5Kl z!{MH2uIs`LjL3=(?kHEfNO|)}`RJ8+Mw;wt(|tp^^1M+?YDvV_<5, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-04-22 15:16+0200\n" +"PO-Revision-Date: 2023-04-22 15:18+0200\n" +"Last-Translator: Edgar P. Burkhart \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.2.2\n" + +#: .\category\models.py:12 .\category\models.py:32 +#: .\category\templates\category\category_plot.html:14 +msgid "Category" +msgstr "Catégorie" + +#: .\category\models.py:12 +msgid "Name" +msgstr "Nom" + +#: .\category\models.py:17 +msgid "Icon" +msgstr "Icône" + +#: .\category\models.py:19 +msgid "Budget" +msgstr "Budget" + +#: .\category\models.py:33 +msgid "Categories" +msgstr "Catégories" + +#: .\category\templates\category\category_form.html:5 +msgid "Create category" +msgstr "Créer une catégorie" + +#: .\category\templates\category\category_form.html:8 +msgid "New category" +msgstr "Nouvelle catégorie" + +#: .\category\templates\category\category_form.html:12 +msgid "Transactions" +msgstr "Transactions" + +#: .\category\templates\category\category_form.html:15 +msgid "History" +msgstr "Historique" + +#: .\category\templates\category\category_plot.html:15 +msgid "Expenses" +msgstr "Dépenses" + +#: .\category\templates\category\category_plot.html:16 +msgid "Income" +msgstr "Revenus" diff --git a/nummi/history/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/history/locale/fr_FR/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..c369f8dc5a4f3023676a06c1e9925a36ca66799f GIT binary patch literal 454 zcmYL_y-ve05XTLauZ+wL-WZTWnu1h?f>3FSDp65Y1u-BY(_B+Saf0oj!p6iC@DNPQ zjEMK(S@?$!)$&jOv+sO&zOS{)3&W~_bu$iB2NU$XJ!>hzAeWJHWnBub|duU{$Wor@#V fWPY3OWt1q#Ykug59&3HR{*4bKAYE9@Pg(X2v5, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-04-22 15:16+0200\n" +"PO-Revision-Date: 2023-04-22 15:18+0200\n" +"Last-Translator: Edgar P. Burkhart \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.2.2\n" + +#: .\history\templates\history\plot.html:15 +msgid "Month" +msgstr "Mois" + +#: .\history\templates\history\plot.html:16 +msgid "Expenses" +msgstr "Dépenses" + +#: .\history\templates\history\plot.html:17 +msgid "Income" +msgstr "Revenus" diff --git a/nummi/locale/fr_fr/LC_MESSAGES/django.mo b/nummi/locale/fr_fr/LC_MESSAGES/django.mo deleted file mode 100644 index 75a8c0ec814a85c28a537e12cd67a01c0cb3f815..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3289 zcma)-ON<=V6^3sJhA=od5C{+RxJiK6;2Mv|2Fze=#-1@AWKSm3V+$!nsN8j@r|jyg zrXFKv0TPjt1&<8~B`nA$L1-jc<)KJ%#G+vVY!DJkR#?g+S)c?(LI@!i`Tpu#-Hp7- zRh|Cs`<{Ewxu-wA=F*o9c9%)V^mSTbdrc7M_Dz|2)+G zzk*u#Ti^dC)cimD@wedjXul7o*FT{4{S@k)&)|3Am+(4x6_?)i4P$PH>i-FwYd;5> zViI@*Ea74Jf*=3DkN*uyuM1FmeggT-=lp0Nb;_@oL!Ea6lz%7T)o>O{-xidfXQ0l_ zp!PWjwf{4oFL=HJW#`wR=KUUO-CrS}dB?XufYRgdzJ0;-Ur_%36v|FtK>2G6hwX;f zLFqFO^&}pF^4}tqoqh&&uP30^{T#|)FG9_G9qOKcf;#72sB=E_;};;G`42xFZN7vz z!7WVEdJDDhZm4@5g4*X{sC7%8k3qfPHOOav;rm~Ny7zCO?E4zjyz>xu*EXAhyAYMT z5S^oqb>mq3R_eO1Y_$)$ANe7oqWj;C$WMxC#m_y6 z{M?l7RODBS>_Oz~JCUaRsHs3wCKufig{9kB*rJ48mzHL>-Oj2J3o{!t{2-{_x zrF7)ZZe*1$DqlGlrlpNC8@a@l&K@YrJWD$V@7R%@KI7Wu&f>ss-(?S(!&TIAr8yF& zZI^)3b+SBmMN{1{N3wJ^&b#JF?ils3q1S8BYukf`%&uqII9<=;_Hg#-K7M@c24+s% z&bigFN=hiDLDA0Ro^EL7;?-4`I}|kYX=G)hy0RW76}2aOE-g^_Xq-56EH26{-!R9r zt}_d5RxhOOy6zQP7_czMvkqF;$f{E8xQHGb7Gox>3YU)=%$&&5a?LD;?7ryE4XvR1 z&;)8U_g8&%d9}uGzLzA7G_w2Hl){|ECgxOa{8KSE3X^1X z$HENz#%@}er94yc1q)Gd%H;)qonc{bEu2_(y(}+-6GbPEg2Pp(2v)KgJF)cRVA-w5 zni(iUX6*FT^xj}N*rhHX zD7}=qC@$^Z$?3`I3A4;2SnqGzsIpB;;2Cy%ICTJC{TZUxwI448&dU@Ps)&G?fX4AAva5YJxzd6bZzTV^oP00J3L)c2eIMB83r^D%S z4A+f%SIJ>yEx&H*gcw^nA6$!>`>sjy)_3To@n)NiI{^qDnm}mqq zy403R$!tAl_=_`3T$shIi&X0_tQsTM%_RAn35R`+0h=|A`Gyn5-k_PS+Hys+#y7`j zEQRslU#B*l{KUa#xxX2waT$k6te4*k(T_)@)UoW^YmQt*b(X2Qt@YOXs4StDw&Pxy z)LxStu+M4y<~`x}lzr=HZ^&Oy)+I>$uVb|FgjD|#c-LMW_jL+r{u57@G0Nios?Jn3 Z^a4sfi^LU8oRO`3H;<+s7&Av<{s%ix6m|dr diff --git a/nummi/locale/fr_fr/LC_MESSAGES/django.po b/nummi/locale/fr_fr/LC_MESSAGES/django.po deleted file mode 100644 index 4bbca6e..0000000 --- a/nummi/locale/fr_fr/LC_MESSAGES/django.po +++ /dev/null @@ -1,315 +0,0 @@ -# Nummi. -# Copyright (C) 2023 -# This file is distributed under the same license as the Nummi package. -# edpibu , 2023 -# -msgid "" -msgstr "" -"Project-Id-Version: alpha\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-04-22 14:45+0200\n" -"PO-Revision-Date: 2023-04-22 14:46+0200\n" -"Last-Translator: Edgar P. Burkhart \n" -"Language-Team: \n" -"Language: fr_FR\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.2.2\n" - -#: .\account\models.py:11 .\account\models.py:37 .\account\models.py:45 -#: .\statement\templates\statement\statement_table.html:24 -#: .\transaction\models.py:48 -#: .\transaction\templates\transaction\transaction_table.html:35 -msgid "Account" -msgstr "Compte" - -#: .\account\models.py:11 .\category\models.py:12 .\transaction\models.py:19 -#: .\transaction\models.py:89 -#: .\transaction\templates\transaction\invoice_table.html:9 -#: .\transaction\templates\transaction\transaction_table.html:28 -msgid "Name" -msgstr "Nom" - -#: .\account\models.py:15 .\category\models.py:17 -msgid "Icon" -msgstr "Icône" - -#: .\account\models.py:17 -msgid "Default" -msgstr "Défaut" - -#: .\account\models.py:38 .\main\templates\main\index.html:16 -msgid "Accounts" -msgstr "Comptes" - -#: .\account\templates\account\account_form.html:5 -#: .\main\templates\main\base.html:50 -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_form.html:13 -#: .\main\templates\main\base.html:38 .\main\templates\main\index.html:40 -#: .\statement\models.py:90 -#: .\statement\templates\statement\statement_list.html:4 -#: .\statement\templates\statement\statement_list.html:7 -msgid "Statements" -msgstr "Relevés" - -#: .\account\templates\account\account_form.html:15 -#: .\category\templates\category\category_form.html:12 -#: .\main\templates\main\base.html:44 .\main\templates\main\index.html:26 -#: .\statement\templates\statement\statement_form.html:27 -#: .\statement\templates\statement\statement_table.html:28 -#: .\transaction\models.py:83 -#: .\transaction\templates\transaction\transaction_list.html:4 -#: .\transaction\templates\transaction\transaction_list.html:7 -msgid "Transactions" -msgstr "Transactions" - -#: .\account\templates\account\account_form.html:18 -#: .\category\templates\category\category_form.html:15 -#: .\main\templates\main\index.html:44 -msgid "History" -msgstr "Historique" - -#: .\category\models.py:12 .\category\models.py:32 -#: .\category\templates\category\category_plot.html:14 -#: .\transaction\models.py:38 -#: .\transaction\templates\transaction\transaction_table.html:32 -msgid "Category" -msgstr "Catégorie" - -#: .\category\models.py:19 -msgid "Budget" -msgstr "Budget" - -#: .\category\models.py:33 .\main\templates\main\index.html:30 -#: .\statement\templates\statement\statement_form.html:23 -msgid "Categories" -msgstr "Catégories" - -#: .\category\templates\category\category_form.html:5 -#: .\main\templates\main\base.html:60 -msgid "Create category" -msgstr "Créer une catégorie" - -#: .\category\templates\category\category_form.html:8 -msgid "New category" -msgstr "Nouvelle catégorie" - -#: .\category\templates\category\category_plot.html:15 -#: .\history\templates\history\plot.html:16 -msgid "Expenses" -msgstr "Dépenses" - -#: .\category\templates\category\category_plot.html:16 -#: .\history\templates\history\plot.html:17 -msgid "Income" -msgstr "Revenus" - -#: .\history\templates\history\plot.html:15 -msgid "Month" -msgstr "Mois" - -#: .\main\models.py:10 -msgid "User" -msgstr "Utilisateur" - -#: .\main\templates\main\base.html:27 -msgid "Skip to main content" -msgstr "Aller au contenu principal" - -#: .\main\templates\main\base.html:33 -msgid "Home" -msgstr "Accueil" - -#: .\main\templates\main\base.html:55 -#: .\statement\templates\statement\statement_form.html:5 -#: .\statement\templates\statement\statement_table.html:5 -msgid "Create statement" -msgstr "Créer un relevé" - -#: .\main\templates\main\base.html:65 -#: .\transaction\templates\transaction\transaction_form.html:5 -#: .\transaction\templates\transaction\transaction_table.html:5 -msgid "Create transaction" -msgstr "Créer une transaction" - -#: .\main\templates\main\base.html:70 .\main\templates\main\list.html:10 -#: .\main\templates\main\list.html:36 .\search\forms.py:7 -#: .\search\templates\search\search.html:6 -#: .\search\templates\search\search.html:18 -#: .\search\templates\search\search_form.html:5 -msgid "Search" -msgstr "Rechercher" - -#: .\main\templates\main\base.html:73 -msgid "Log out" -msgstr "Se déconnecter" - -#: .\main\templates\main\base.html:78 .\main\templates\main\form\login.html:6 -#: .\main\templates\main\login.html:14 -msgid "Log in" -msgstr "Se connecter" - -#: .\main\templates\main\base.html:84 -#, python-format -msgid "Logged in as %(user)s" -msgstr "Connecté en tant que %(user)s" - -#: .\main\templates\main\confirm_delete.html:19 -#, 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:23 -msgid "Cancel" -msgstr "Annuler" - -#: .\main\templates\main\confirm_delete.html:24 -msgid "Confirm" -msgstr "Confirmer" - -#: .\main\templates\main\form\fileinput.html:8 .\statement\models.py:42 -#: .\transaction\models.py:94 -#: .\transaction\templates\transaction\invoice_table.html:10 -#: .\transaction\templates\transaction\invoice_table.html:20 -msgid "File" -msgstr "Fichier" - -#: .\main\templates\main\form\form_base.html:30 -#: .\transaction\templates\transaction\invoice_table.html:11 -#: .\transaction\templates\transaction\invoice_table.html:23 -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 -msgid "Create" -msgstr "Créer" - -#: .\main\templates\main\form\form_base.html:36 -msgid "Save" -msgstr "Sauvegarder" - -#: .\statement\forms.py:28 -msgid "Add transactions" -msgstr "Ajouter des transactions" - -#: .\statement\models.py:15 -msgid "End date" -msgstr "Date de fin" - -#: .\statement\models.py:17 -msgid "Start date" -msgstr "Date de début" - -#: .\statement\models.py:20 -msgid "End value" -msgstr "Valeur finale" - -#: .\statement\models.py:23 -msgid "Start value" -msgstr "Valeur initiale" - -#: .\statement\models.py:29 -#: .\statement\templates\statement\statement_table.html:27 -msgid "Difference" -msgstr "Différence" - -#: .\statement\models.py:36 -msgid "Transaction difference" -msgstr "Différence des transactions" - -#: .\statement\models.py:49 -#, python-format -msgid "%(date)s statement" -msgstr "Relevé du %(date)s" - -#: .\statement\models.py:89 .\transaction\models.py:43 -msgid "Statement" -msgstr "Relevé" - -#: .\statement\templates\statement\statement_form.html:8 -msgid "New statement" -msgstr "Nouveau relevé" - -#: .\statement\templates\statement\statement_table.html:22 -#: .\transaction\models.py:25 -#: .\transaction\templates\transaction\transaction_table.html:27 -msgid "Date" -msgstr "Date" - -#: .\statement\templates\statement\statement_table.html:26 -#: .\transaction\models.py:23 -#: .\transaction\templates\transaction\transaction_table.html:29 -msgid "Value" -msgstr "Valeur" - -#: .\statement\templates\statement\statement_table.html:56 -#: .\transaction\templates\transaction\transaction_table.html:71 -msgid "No transaction" -msgstr "Aucune transaction" - -#: .\statement\templates\statement\statement_table.html:64 -msgid "View all statements" -msgstr "Voir tous les relevés" - -#: .\transaction\models.py:19 .\transaction\models.py:82 -msgid "Transaction" -msgstr "Transaction" - -#: .\transaction\models.py:21 -msgid "Description" -msgstr "Description" - -#: .\transaction\models.py:26 -msgid "Real date" -msgstr "Date réelle" - -#: .\transaction\models.py:28 -#: .\transaction\templates\transaction\transaction_table.html:30 -msgid "Trader" -msgstr "Commerçant" - -#: .\transaction\models.py:31 -msgid "Payment" -msgstr "Paiement" - -#: .\transaction\models.py:89 .\transaction\models.py:122 -msgid "Invoice" -msgstr "Facture" - -#: .\transaction\models.py:123 -#: .\transaction\templates\transaction\transaction_form.html:19 -msgid "Invoices" -msgstr "Factures" - -#: .\transaction\templates\transaction\invoice_form.html:4 -#: .\transaction\templates\transaction\invoice_table.html:35 -msgid "Create invoice" -msgstr "Créer une facture" - -#: .\transaction\templates\transaction\invoice_form.html:7 -msgid "New invoice" -msgstr "Nouvelle facture" - -#: .\transaction\templates\transaction\invoice_table.html:28 -msgid "No invoice" -msgstr "Aucune facture" - -#: .\transaction\templates\transaction\transaction_form.html:8 -msgid "New transaction" -msgstr "Nouvelle transaction" - -#: .\transaction\templates\transaction\transaction_table.html:80 -msgid "View all transactions" -msgstr "Voir toutes les transactions" diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..1e6d0d3f4cc7e1f1acf7fdcf5b36f70079b899ef GIT binary patch literal 1596 zcmZvbyN_E%6vhYgHi3i?5{M{9LLrIJ^?IEkCC+Avce6`^vcj?*M2BYYJ$rY^^~}vY z7ED7!fe;cE1(Y<%5)v&Er5pZ*G*lE6sR9L}f$!YC#^y27_;=5l*EeTA|8(l$7Xs}? z^q0_oMn8}K?GyMxyZNLLN5L+51AG%a41NMOz|X-W;8%72Yw#Jw6YvE1J$MrQ5j+Jx z1bO`<@F4gr$m@TvV{u@A{1C|b)8g6gCmgVeF$E_ zT|NSNzi*IS1HS_~ehfYZ{sA5X591Kt_aw-=&w|f`7r|%2Rqzbh19^T9@;(nhK7U-t zAA_vpGmz`>CAb7mK(5b2kk9=ErG@qK9nPY2?O4xZveBo~x{z%7TxZEz%zZMnNcY}K`n0EcQgRE|?8tVjeK~1e zN!o3>{90%E+N-U0tJS!xd`POJ3WYm%u>9*>7y!+1i+kU2e7-cqzVVG8~r>73hPoR2C?~b(!Kc z6zRsj;bwAmK99?=LoV4csm-*>JM!wb4vlw`H^`9N7u~az=^!sR+s$?ZW*C(LrkH+; z-(=55Fa7if2X9FPh1CweD@&(GFFNrTd25U) literal 0 HcmV?d00001 diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.po b/nummi/main/locale/fr_FR/LC_MESSAGES/django.po new file mode 100644 index 0000000..9d1766a --- /dev/null +++ b/nummi/main/locale/fr_FR/LC_MESSAGES/django.po @@ -0,0 +1,118 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-04-22 15:16+0200\n" +"PO-Revision-Date: 2023-04-22 15:19+0200\n" +"Last-Translator: Edgar P. Burkhart \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.2.2\n" + +#: .\main\models.py:10 +msgid "User" +msgstr "Utilisateur" + +#: .\main\templates\main\base.html:27 +msgid "Skip to main content" +msgstr "Aller au contenu principal" + +#: .\main\templates\main\base.html:33 +msgid "Home" +msgstr "Accueil" + +#: .\main\templates\main\base.html:38 .\main\templates\main\index.html:40 +msgid "Statements" +msgstr "Relevés" + +#: .\main\templates\main\base.html:44 .\main\templates\main\index.html:26 +msgid "Transactions" +msgstr "Transactions" + +#: .\main\templates\main\base.html:50 +msgid "Create account" +msgstr "Créer un compte" + +#: .\main\templates\main\base.html:55 +msgid "Create statement" +msgstr "Créer un relevé" + +#: .\main\templates\main\base.html:60 +msgid "Create category" +msgstr "Créer une catégorie" + +#: .\main\templates\main\base.html:65 +msgid "Create transaction" +msgstr "Créer une transaction" + +#: .\main\templates\main\base.html:70 .\main\templates\main\list.html:10 +#: .\main\templates\main\list.html:36 +msgid "Search" +msgstr "Rechercher" + +#: .\main\templates\main\base.html:73 +msgid "Log out" +msgstr "Se déconnecter" + +#: .\main\templates\main\base.html:78 .\main\templates\main\form\login.html:6 +#: .\main\templates\main\login.html:14 +msgid "Log in" +msgstr "Se connecter" + +#: .\main\templates\main\base.html:84 +#, python-format +msgid "Logged in as %(user)s" +msgstr "Connecté en tant que %(user)s" + +#: .\main\templates\main\confirm_delete.html:19 +#, 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:23 +msgid "Cancel" +msgstr "Annuler" + +#: .\main\templates\main\confirm_delete.html:24 +msgid "Confirm" +msgstr "Confirmer" + +#: .\main\templates\main\form\fileinput.html:8 +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 +msgid "Create" +msgstr "Créer" + +#: .\main\templates\main\form\form_base.html:36 +msgid "Save" +msgstr "Enregistrer" + +#: .\main\templates\main\index.html:16 +msgid "Accounts" +msgstr "Comptes" + +#: .\main\templates\main\index.html:30 +msgid "Categories" +msgstr "Catégories" + +#: .\main\templates\main\index.html:44 +msgid "History" +msgstr "Historique" diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index d3b1906..144c198 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -24,7 +24,6 @@ else: # Build paths inside the project like this: BASE_DIR / 'subdir'. MEDIA_CONF = CONFIG.get("media", {}) BASE_DIR = Path(__file__).resolve().parent.parent -print(BASE_DIR) MEDIA_ROOT = Path(MEDIA_CONF.get("root", "/var/lib/nummi")) MEDIA_URL = "media/" @@ -122,10 +121,6 @@ TIME_ZONE = CONFIG.get("time_zone", "CET") USE_I18N = True USE_L10N = True USE_TZ = True -LOCALE_PATHS = [ - BASE_DIR.joinpath("locale"), -] -print(LOCALE_PATHS) # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ diff --git a/nummi/search/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/search/locale/fr_FR/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..6a801881555d84c3a628006c279ccacd4db33abb GIT binary patch literal 395 zcmYL_O-{ow5QPH*7Fn`p;T;Q@;JP4Hp&(R9Q6(xu2&g+`l4)uvb~JGi?6?Hi<19=- z&?kTL-;CdrzqU6%1J*jT!|XGg%#i7nGF!|Iv%3}qGoC}{gV#L%)%E}A7Bv-%ASL&N zg@;KA_ehO#HWp!qwIdJHrZOdr+Ny!v@o{<$8NQmX2rjh8SSYFbAP+$)(L0Kj+>=Vm z@Jcrxa?-Y0YVRl(Nm*$UX(UE1JuftQG4!O%w($Ja8Fsg|K_ZOibjt0vYITK>qkduA zzkMv`^f1oC>13L$!uBE=o;&N&vgdovp*+6%gLOV literal 0 HcmV?d00001 diff --git a/nummi/search/locale/fr_FR/LC_MESSAGES/django.po b/nummi/search/locale/fr_FR/LC_MESSAGES/django.po new file mode 100644 index 0000000..5517665 --- /dev/null +++ b/nummi/search/locale/fr_FR/LC_MESSAGES/django.po @@ -0,0 +1,24 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-04-22 15:16+0200\n" +"PO-Revision-Date: 2023-04-22 15:20+0200\n" +"Last-Translator: Edgar P. Burkhart \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.2.2\n" + +#: .\search\forms.py:7 .\search\templates\search\search.html:6 +#: .\search\templates\search\search.html:18 +#: .\search\templates\search\search_form.html:5 +msgid "Search" +msgstr "Rechercher" diff --git a/nummi/statement/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..d4b610942111d253874c027edf8d6c8edff418c8 GIT binary patch literal 1305 zcmZXSJ8u&~5XYAgUWV`p4+RONZ4gB0jB^1g7>LNjL6MSRjAPM7m%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 literal 0 HcmV?d00001 diff --git a/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po new file mode 100644 index 0000000..88593df --- /dev/null +++ b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po @@ -0,0 +1,107 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-04-22 15:22+0200\n" +"PO-Revision-Date: 2023-04-22 15:22+0200\n" +"Last-Translator: Edgar P. Burkhart \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.2.2\n" + +#: .\statement\forms.py:28 +msgid "Add transactions" +msgstr "Ajouter des transactions" + +#: .\statement\models.py:15 +msgid "End date" +msgstr "Date de fin" + +#: .\statement\models.py:17 +msgid "Start date" +msgstr "Date de début" + +#: .\statement\models.py:20 +msgid "End value" +msgstr "Valeur finale" + +#: .\statement\models.py:23 +msgid "Start value" +msgstr "Valeur initiale" + +#: .\statement\models.py:29 +#: .\statement\templates\statement\statement_table.html:27 +msgid "Difference" +msgstr "Différence" + +#: .\statement\models.py:36 +msgid "Transaction difference" +msgstr "Différence des transactions" + +#: .\statement\models.py:42 +msgid "File" +msgstr "Fichier" + +#: .\statement\models.py:49 +#, python-format +msgid "%(date)s statement" +msgstr "Relevé du %(date)s" + +#: .\statement\models.py:89 +msgid "Statement" +msgstr "Relevé" + +#: .\statement\models.py:90 +#: .\statement\templates\statement\statement_list.html:4 +#: .\statement\templates\statement\statement_list.html:7 +msgid "Statements" +msgstr "Relevés" + +#: .\statement\templates\statement\statement_form.html:5 +#: .\statement\templates\statement\statement_table.html:5 +msgid "Create statement" +msgstr "Créer un relevé" + +#: .\statement\templates\statement\statement_form.html:8 +msgid "New statement" +msgstr "Nouveau relevé" + +#: .\statement\templates\statement\statement_form.html:23 +msgid "Categories" +msgstr "Catégories" + +#: .\statement\templates\statement\statement_form.html:27 +#: .\statement\templates\statement\statement_table.html:28 +msgid "Transactions" +msgstr "Transactions" + +#: .\statement\templates\statement\statement_table.html:22 +msgid "Date" +msgstr "Date" + +#: .\statement\templates\statement\statement_table.html:24 +msgid "Account" +msgstr "Compte" + +#: .\statement\templates\statement\statement_table.html:26 +msgid "Value" +msgstr "Valeur" + +#: .\statement\templates\statement\statement_table.html:56 +msgid "No statement" +msgstr "Aucun relevé" + +#: .\statement\templates\statement\statement_table.html:64 +msgid "View all statements" +msgstr "Voir tous les relevés" + +#~ msgid "No transaction" +#~ msgstr "Aucune transaction" diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index 12cdccc..e6bc79b 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -53,7 +53,7 @@ {% empty %} - {% translate "No transaction" %} + {% translate "No statement" %} {% endfor %} diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..3084334d48152f76c48ebead1d8e0a054790250c GIT binary patch literal 1392 zcmY+CJ8Tm{5QY~BkC;awJPRbYi73z+#|9~wXW}F%QWA_~BD!cfkDZ0{?P+h%5Q&B| z4FwW{f(|4Ui4IzjP-+?^Is_%6qoShVpUcI$jJ)@)XLo1**_|K51J4EQ6vkJ zIsVyS^BOap6^2E|n`@>4H}-%h8L~a^+m9Tq+f2RqVV}L7<$mRXNq_C@bql zIg!{0%gQ==#X)nD-r)`6)ku@IRBYR8I39Kq)giA*s*ALn|EscXm!=zqxtY1CEwQ7; zQlVx-hs*JrD-lFFdgRVWfzsh+=_6ZA?&_W9v^Tc3k8i3?)|(1_)6!w5D#z|=SGeol zq>$`}=(?encCu0|7s~}vGu;T6j@DRTLvLvAhV?bFG6_kx(G7{+=0bh_$4HxGA&DZZ zyLhKb%J+%CjLOAy79k^A#dI-}9tFWx{JtX}eLV5=8nVCn9d)HQ8ij>OmW`n3q=0(s kYg`#rt-n?1u4YbkZl1OfB~~mNZKX4bqgVzQxh%2x5AdQy{{R30 literal 0 HcmV?d00001 diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po new file mode 100644 index 0000000..92fd5e2 --- /dev/null +++ b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po @@ -0,0 +1,125 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-04-22 15:16+0200\n" +"PO-Revision-Date: 2023-04-22 15:23+0200\n" +"Last-Translator: Edgar P. Burkhart \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.2.2\n" + +#: .\transaction\models.py:19 .\transaction\models.py:82 +msgid "Transaction" +msgstr "Transaction" + +#: .\transaction\models.py:19 .\transaction\models.py:89 +#: .\transaction\templates\transaction\invoice_table.html:9 +#: .\transaction\templates\transaction\transaction_table.html:28 +msgid "Name" +msgstr "Nom" + +#: .\transaction\models.py:21 +msgid "Description" +msgstr "Description" + +#: .\transaction\models.py:23 +#: .\transaction\templates\transaction\transaction_table.html:29 +msgid "Value" +msgstr "Valeur" + +#: .\transaction\models.py:25 +#: .\transaction\templates\transaction\transaction_table.html:27 +msgid "Date" +msgstr "Date" + +#: .\transaction\models.py:26 +msgid "Real date" +msgstr "Date réelle" + +#: .\transaction\models.py:28 +#: .\transaction\templates\transaction\transaction_table.html:30 +msgid "Trader" +msgstr "Commerçant" + +#: .\transaction\models.py:31 +msgid "Payment" +msgstr "Paiement" + +#: .\transaction\models.py:38 +#: .\transaction\templates\transaction\transaction_table.html:32 +msgid "Category" +msgstr "Catégorie" + +#: .\transaction\models.py:43 +msgid "Statement" +msgstr "Relevé" + +#: .\transaction\models.py:48 +#: .\transaction\templates\transaction\transaction_table.html:35 +msgid "Account" +msgstr "Compte" + +#: .\transaction\models.py:83 +#: .\transaction\templates\transaction\transaction_list.html:4 +#: .\transaction\templates\transaction\transaction_list.html:7 +msgid "Transactions" +msgstr "Transactions" + +#: .\transaction\models.py:89 .\transaction\models.py:122 +msgid "Invoice" +msgstr "Facture" + +#: .\transaction\models.py:94 +#: .\transaction\templates\transaction\invoice_table.html:10 +#: .\transaction\templates\transaction\invoice_table.html:20 +msgid "File" +msgstr "Fichier" + +#: .\transaction\models.py:123 +#: .\transaction\templates\transaction\transaction_form.html:19 +msgid "Invoices" +msgstr "Factures" + +#: .\transaction\templates\transaction\invoice_form.html:4 +#: .\transaction\templates\transaction\invoice_table.html:35 +msgid "Create invoice" +msgstr "Créer une facture" + +#: .\transaction\templates\transaction\invoice_form.html:7 +msgid "New invoice" +msgstr "Nouvelle facture" + +#: .\transaction\templates\transaction\invoice_table.html:11 +#: .\transaction\templates\transaction\invoice_table.html:23 +msgid "Delete" +msgstr "Supprimer" + +#: .\transaction\templates\transaction\invoice_table.html:28 +msgid "No invoice" +msgstr "Aucune facture" + +#: .\transaction\templates\transaction\transaction_form.html:5 +#: .\transaction\templates\transaction\transaction_table.html:5 +msgid "Create transaction" +msgstr "Créer une transaction" + +#: .\transaction\templates\transaction\transaction_form.html:8 +msgid "New transaction" +msgstr "Nouvelle transaction" + +#: .\transaction\templates\transaction\transaction_table.html:71 +msgid "No transaction" +msgstr "Aucune transaction" + +#: .\transaction\templates\transaction\transaction_table.html:80 +msgid "View all transactions" +msgstr "Voir toutes les transactions" From b828324220dff4d8c67b5b129e7395253c98e79e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 15:44:27 +0200 Subject: [PATCH 079/276] Add category plot on history pages --- .../templates/category/category_plot.html | 2 +- nummi/category/utils.py | 22 +++++++++++++++++++ nummi/main/static/main/css/main.css | 4 +++- nummi/statement/views.py | 21 ++---------------- .../transaction_archive_month.html | 14 ++++++++++++ nummi/transaction/views.py | 12 ++++++---- 6 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 nummi/category/utils.py diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index 71cddaa..344500b 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -20,7 +20,7 @@ {% spaceless %} {% for cat in categories.data %} - + {% if cat.category %}{{ cat.category__name }}{% endif %} diff --git a/nummi/category/utils.py b/nummi/category/utils.py new file mode 100644 index 0000000..b3b0ea8 --- /dev/null +++ b/nummi/category/utils.py @@ -0,0 +1,22 @@ +from django.db import models + + +def get_categories(transactions): + categories = ( + transactions.values("category", "category__name", "category__icon") + .annotate( + sum=models.Sum("value"), + sum_m=models.Sum("value", filter=models.Q(value__lt=0)), + sum_p=models.Sum("value", filter=models.Q(value__gt=0)), + ) + .order_by("-sum") + ) + return { + "data": categories, + "max": max( + categories.aggregate( + max=models.Max("sum_p", default=0), + min=models.Min("sum_m", default=0), + ).values(), + ), + } diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 523338c..892b449 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -163,8 +163,10 @@ footer { a.big-link { margin-right: 1em; } -[class^="ri-"] { +a [class^="ri-"] { margin-right: 0.5em; +} +[class^="ri-"] { font-weight: normal; } diff --git a/nummi/statement/views.py b/nummi/statement/views.py index cbbbc6a..9121ba3 100644 --- a/nummi/statement/views.py +++ b/nummi/statement/views.py @@ -1,5 +1,5 @@ from account.models import Account -from django.db import models +from category.utils import get_categories from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from main.views import NummiCreateView, NummiDeleteView, NummiListView, NummiUpdateView @@ -43,24 +43,7 @@ class StatementUpdateView(NummiUpdateView): _transactions = statement.transaction_set.all() if _transactions: - _categories = ( - _transactions.values("category", "category__name", "category__icon") - .annotate( - sum=models.Sum("value"), - sum_m=models.Sum("value", filter=models.Q(value__lt=0)), - sum_p=models.Sum("value", filter=models.Q(value__gt=0)), - ) - .order_by("-sum") - ) - data["categories"] = { - "data": _categories, - "max": max( - _categories.aggregate( - max=models.Max("sum_p", default=0), - min=models.Min("sum_m", default=0), - ).values(), - ), - } + data["categories"] = get_categories(_transactions) return data | { "account": statement.account, diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index efd440f..93561c0 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -1,2 +1,16 @@ {% extends "transaction/transaction_list.html" %} +{% load i18n %} +{% load static %} +{% block link %} + {{ block.super }} + +{% endblock %} {% block h2 %}{{ month|date:"F Y"|capfirst }}{% endblock %} +{% block table %} +

    {% translate "Transactions" %}

    + {{ block.super }} +

    {% translate "History" %}

    + {% include "category/category_plot.html" %} +{% endblock %} diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 0a54fc7..238d19a 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -1,5 +1,6 @@ from account.models import Account from category.models import Category +from category.utils import get_categories from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.views.generic.dates import MonthArchiveView @@ -132,8 +133,11 @@ class TransactionMonthView(UserMixin, MonthArchiveView): return super().get_queryset() def get_context_data(self, **kwargs): - if "account" in self.kwargs: - return super().get_context_data(**kwargs) | {"account": self.account} + context_data = super().get_context_data(**kwargs) if "category" in self.kwargs: - return super().get_context_data(**kwargs) | {"category": self.category} - return super().get_context_data(**kwargs) + return context_data | {"category": self.category} + + context_data["categories"] = get_categories(context_data["transactions"]) + if "account" in self.kwargs: + return context_data | {"account": self.account} + return context_data From f7564eb2828d714b514428fb2ee2bdbe616d0c67 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 15:52:32 +0200 Subject: [PATCH 080/276] Add links to category in month category plot --- nummi/category/templates/category/category_plot.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index 344500b..1a0fc4a 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -21,7 +21,13 @@ {% for cat in categories.data %} - {% if cat.category %}{{ cat.category__name }}{% endif %} + {% if cat.category %} + {% if month %} +
    {{ cat.category__name }} + {% else %} + {{ cat.category__name }} + {% endif %} + {% endif %} {% if cat.category %}{{ cat.category__icon|remix }}{% endif %} From b6326bdc8fa2239aba12ff2da9d5133c3c7b9113 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 15:57:36 +0200 Subject: [PATCH 081/276] Fix category plot on month page --- .../templates/transaction/transaction_archive_month.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index 93561c0..431e4db 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -11,6 +11,8 @@ {% block table %}

    {% translate "Transactions" %}

    {{ block.super }} -

    {% translate "History" %}

    - {% include "category/category_plot.html" %} + {% if categories %} +

    {% translate "Categories" %}

    + {% include "category/category_plot.html" %} + {% endif %} {% endblock %} From 517dd28b2e6d020a2e652d106e0b084c1686a61b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 19:41:03 +0200 Subject: [PATCH 082/276] Fix navbar scrolling --- nummi/main/static/main/css/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 892b449..7a5f2f0 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -91,6 +91,7 @@ nav { height: 100vh; position: sticky; top: 0; + overflow-y: auto; background: var(--bg-01); line-height: 2rem; From b9cd7a8460cb8142c7a047cdaf6a3dbbe33f8ab9 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 19:43:31 +0200 Subject: [PATCH 083/276] Fix icon margin --- 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 7a5f2f0..761186d 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -164,7 +164,7 @@ footer { a.big-link { margin-right: 1em; } -a [class^="ri-"] { +[class^="ri-"]:last-child { margin-right: 0.5em; } [class^="ri-"] { From 1bad7d7291c42b5c599ef15e0356d995ad262eac Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 22 Apr 2023 19:47:58 +0200 Subject: [PATCH 084/276] Fix icon margin --- nummi/main/static/main/css/main.css | 2 +- nummi/main/templates/main/list.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 761186d..6aa5a99 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -164,7 +164,7 @@ footer { a.big-link { margin-right: 1em; } -[class^="ri-"]:last-child { +a.big-link [class^="ri-"] { margin-right: 0.5em; } [class^="ri-"] { diff --git a/nummi/main/templates/main/list.html b/nummi/main/templates/main/list.html index 1e9d53f..aa90d98 100644 --- a/nummi/main/templates/main/list.html +++ b/nummi/main/templates/main/list.html @@ -23,12 +23,12 @@ {% if account %}

    - {{ account.icon|remix }}{{ account }} + {{ account.icon|remix }}{{ account }}

    {% endif %} {% if category %}

    - {{ category.icon|remix }}{{ category }} + {{ category.icon|remix }}{{ category }}

    {% endif %} {% if search %} From a0872b65c4f1337889c74f5bc73be8bdd19cf982 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 23 Apr 2023 08:08:43 +0200 Subject: [PATCH 085/276] Use get_absolute_url to get model urls --- nummi/main/locale/fr_FR/LC_MESSAGES/django.mo | Bin 1596 -> 1682 bytes nummi/main/locale/fr_FR/LC_MESSAGES/django.po | 18 ++++++++++---- nummi/main/templates/main/index.html | 22 +++++++++--------- .../templates/statement/statement_table.html | 4 ++-- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 1392 -> 1431 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 9 +++++-- nummi/transaction/models.py | 2 +- .../transaction/transaction_table.html | 6 ++--- 8 files changed, 37 insertions(+), 24 deletions(-) diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo index 1e6d0d3f4cc7e1f1acf7fdcf5b36f70079b899ef..4685846eef54e3eb6b0be73dad20de77fa8de525 100644 GIT binary patch delta 636 zcmXZZzb^w}9LMqJuBFAHe${VjBqb6_34=7LghgVonG7_igoH}dOLP#6u$Wv77L$Pq zX=1>{Bq1?&kj5nb0E^B0Q+<-}z3zFQ?|q-=p6AgyY1}Wj22TyGm*^vYh&b_T@u02P z#)L7ADcreihXpaW$2lRjz~g zZX)VicBj==?6wE3=t4G@cHOMEoj1I?>3n8An=AOqz;S3IHa3+Un<%~o9D88KbG_}D Xo88*UXH2D&QTFo{=^GipRI~aAAJ{`2 delta 531 zcmXZZzb^w}9LMqRU9Ua#O4V@`i68M}t4a(dL=6TZ@i#D-3~ajGVvvwXTnvmBX(BOn zG57=g3tdbG5)%fo@cw9@L#wRgmu&nH8HzM@=BtOUs%Ln)Rh\n" "Language-Team: \n" "Language: fr_FR\n" @@ -33,7 +33,7 @@ msgstr "Accueil" msgid "Statements" msgstr "Relevés" -#: .\main\templates\main\base.html:44 .\main\templates\main\index.html:26 +#: .\main\templates\main\base.html:44 .\main\templates\main\index.html:24 msgid "Transactions" msgstr "Transactions" @@ -105,14 +105,22 @@ msgstr "Créer" msgid "Save" msgstr "Enregistrer" -#: .\main\templates\main\index.html:16 +#: .\main\templates\main\index.html:15 msgid "Accounts" msgstr "Comptes" -#: .\main\templates\main\index.html:30 +#: .\main\templates\main\index.html:20 +msgid "No account" +msgstr "Aucun compte" + +#: .\main\templates\main\index.html:28 msgid "Categories" msgstr "Catégories" +#: .\main\templates\main\index.html:34 +msgid "No category" +msgstr "Aucune catégorie" + #: .\main\templates\main\index.html:44 msgid "History" msgstr "Historique" diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 5f194d8..cdadde8 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -12,16 +12,14 @@ type="text/css" /> {% endblock %} {% block body %} - {% if accounts %} -

    {% translate "Accounts" %}

    - {% spaceless %} -

    - {% for acc in accounts %} - {{ acc.icon|remix }}{{ acc }} - {% endfor %} -

    - {% endspaceless %} - {% endif %} +

    {% translate "Accounts" %}

    +

    + {% for acc in accounts %} + {{ acc.icon|remix }}{{ acc }} + {% empty %} + {% translate "No account" %} + {% endfor %} +

    {% if transactions %}

    {% translate "Transactions" %}

    {% include "transaction/transaction_table.html" %} @@ -31,7 +29,9 @@ {% spaceless %}

    {% for cat in categories %} - {{ cat.icon|remix }}{{ cat }} + {{ cat.icon|remix }}{{ cat }} + {% empty %} + {% translate "No category" %} {% endfor %}

    {% endspaceless %} diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index e6bc79b..e4310e4 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -39,12 +39,12 @@ {% if snap.file %}{{ "attachment"|remix }}{% endif %} - {{ snap.date|date:"Y-m-d" }} + {{ snap.date|date:"Y-m-d" }} {% if not account %} {{ snap.account.icon|remix }} - {{ snap.account }} + {{ snap.account }} {% endif %} {{ snap.value|value }} diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo index 3084334d48152f76c48ebead1d8e0a054790250c..b34179b90b20d71fd55363e1e3f5378f254f26e2 100644 GIT binary patch delta 627 zcmX}py-UMD7{~ET8(U4ZYJI7K1qVfND(Fz1S_HwxML`f8q|_G>15LZ=kVRY^MT+3) zs2~fC)T97cZjsb<{pLsP(s<_kp=*poxz-h|j1(Z>RzvsD&-; z!*=8^s*oM;taq`W{~7GT863lToTHfnYW@MIR6P$J6Bu*KKpmXpI9?+^^T0y|pHK^4 zPzQO9d`ETiiR^8fID{=6#UE4yDNdvPv#9ww)IpapNqw`*Kn2z#x1$Mrs1y6BPjiCN zR|((iwzMG^@gFLvy%Z`kOlC;kXg72#-Pjsnq;Idz6*@TwHzOqV!p?LH>Rd5FsvCv6 q4Wn2pd9~xJS=p_Y_r1zt*>B`yi&kP`I+tJ0T~^}XmfzU0v(_I){V`zx delta 598 zcmX}pzb`{k7{>AUwpY0=%0(F@EKOG}1|kt=2C3apivag6nl{C#p~f z`|$^RFiM+^VIE5iQAC|zr%UDEL^ZyJ1?t-Y8~&`xLxoOJi7!zN+<0$M6Wk#=?H-5l z5l8VE)%XXhz&GlAL|1AsM&+AAX@rNSQO7jwCQ#REL@~}9cDui8a@&7JIMi>Xbi5M#! diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po index 92fd5e2..fc73dd9 100644 --- a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po +++ b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-04-22 15:16+0200\n" -"PO-Revision-Date: 2023-04-22 15:23+0200\n" +"POT-Creation-Date: 2023-04-23 08:02+0200\n" +"PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" "Language: fr_FR\n" @@ -69,6 +69,7 @@ msgid "Account" msgstr "Compte" #: .\transaction\models.py:83 +#: .\transaction\templates\transaction\transaction_archive_month.html:12 #: .\transaction\templates\transaction\transaction_list.html:4 #: .\transaction\templates\transaction\transaction_list.html:7 msgid "Transactions" @@ -107,6 +108,10 @@ msgstr "Supprimer" msgid "No invoice" msgstr "Aucune facture" +#: .\transaction\templates\transaction\transaction_archive_month.html:15 +msgid "Categories" +msgstr "Catégories" + #: .\transaction\templates\transaction\transaction_form.html:5 #: .\transaction\templates\transaction\transaction_table.html:5 msgid "Create transaction" diff --git a/nummi/transaction/models.py b/nummi/transaction/models.py index 7f20c7c..46cdd20 100644 --- a/nummi/transaction/models.py +++ b/nummi/transaction/models.py @@ -64,7 +64,7 @@ class Transaction(UserModel): return f"{self.name}" def get_absolute_url(self): - return reverse("transaction", kwargs={"pk": self.pk}) + return reverse("transaction", args=(self.pk,)) def get_delete_url(self): return reverse("del_transaction", args=(self.pk,)) diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index 27543d2..3af719e 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -43,7 +43,7 @@ {{ trans.date|date:"Y-m-d" }} - {{ trans.name }} + {{ trans.name }} {{ trans.value|pmvalue }} {{ trans.trader|default_if_none:"" }} @@ -51,7 +51,7 @@ {% if trans.category %} {{ trans.category.icon|remix }} - {{ trans.category }} + {{ trans.category }} {% else %} @@ -60,7 +60,7 @@ {% if not account %} {{ trans.account.icon|remix }} - {{ trans.account }} + {{ trans.account }} {% endif %} From 9a666b6d22ffaffda458cd14019c2f0a3454da90 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 23 Apr 2023 08:10:30 +0200 Subject: [PATCH 086/276] Fix .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 542fc9c..acf92f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -env* +/env* __pycache__ *.pkg.tar.zst /nummi-git/ From 09c8da0650f62b9423f74561a2dccd2901eb2ece Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 23 Apr 2023 14:07:54 +0200 Subject: [PATCH 087/276] Update PKGBUILD with version --- .gitignore | 4 ---- pkgbuild/.gitignore | 3 +++ pkgbuild/PKGBUILD | 33 +++++++++++++++++---------------- 3 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 pkgbuild/.gitignore diff --git a/.gitignore b/.gitignore index acf92f2..89291af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ /env* __pycache__ -*.pkg.tar.zst -/nummi-git/ -/pkg/ -/src/ /media diff --git a/pkgbuild/.gitignore b/pkgbuild/.gitignore new file mode 100644 index 0000000..19f79ea --- /dev/null +++ b/pkgbuild/.gitignore @@ -0,0 +1,3 @@ +/*.pkg.tar.zst +/src/ +/nummi/ diff --git a/pkgbuild/PKGBUILD b/pkgbuild/PKGBUILD index bad6c31..871eefd 100644 --- a/pkgbuild/PKGBUILD +++ b/pkgbuild/PKGBUILD @@ -1,5 +1,5 @@ -pkgname=nummi-git -pkgver=r298.2c95379 +pkgname=nummi +pkgver=v0.9 pkgrel=1 pkgdesc="Web-based accounting interface" arch=("any") @@ -13,13 +13,14 @@ depends=( ) makedepends=("git") optdepends=("postgresql: database") -backup=("etc/${pkgname%-git}/config.toml") +backup=("etc/${pkgname}/config.toml") +_tag=c29a01ed5821787bfcef7afbdb100604feab97d0 source=( - "${pkgname}::git+https://git.edgarpierre.fr/edpibu/nummi" - "${pkgname%-git}.service" - "${pkgname%-git}.tmpfiles" - "${pkgname%-git}.sysusers" - "${pkgname%-git}.nginx" + "${pkgname}::git+https://git.edgarpierre.fr/edpibu/nummi?signed#tag=$_tag" + "${pkgname}.service" + "${pkgname}.tmpfiles" + "${pkgname}.sysusers" + "${pkgname}.nginx" "config.toml" ) b2sums=('SKIP' @@ -31,16 +32,16 @@ b2sums=('SKIP' pkgver() { cd "$pkgname" - printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" + git describe } package() { - install -Dm644 ${pkgname%-git}.service -t "${pkgdir}"/usr/lib/systemd/system/ - install -Dm644 ${pkgname%-git}.tmpfiles "${pkgdir}"/usr/lib/tmpfiles.d/${pkgname%-git}.conf - install -Dm644 ${pkgname%-git}.sysusers "${pkgdir}"/usr/lib/sysusers.d/${pkgname%-git}.conf - install -Dm644 ${pkgname%-git}.nginx "${pkgdir}"/etc/nginx/sites-enabled/${pkgname%-git}.conf - install -Dm750 -o nummi -g nummi config.toml -t "${pkgdir}"/etc/${pkgname%-git}/ + install -Dm644 ${pkgname}.service -t "${pkgdir}"/usr/lib/systemd/system/ + install -Dm644 ${pkgname}.tmpfiles "${pkgdir}"/usr/lib/tmpfiles.d/${pkgname}.conf + install -Dm644 ${pkgname}.sysusers "${pkgdir}"/usr/lib/sysusers.d/${pkgname}.conf + install -Dm644 ${pkgname}.nginx "${pkgdir}"/etc/nginx/sites-enabled/${pkgname}.conf + install -Dm600 -o nummi -g nummi config.toml -t "${pkgdir}"/etc/${pkgname}/ - cd ${pkgname}/${pkgname%-git} - find * -type f -exec install -Dm0644 "{}" "${pkgdir}/usr/share/webapps/${pkgname%-git}/{}" \; + cd ${pkgname}/${pkgname} + find * -type f -exec install -Dm0644 "{}" "${pkgdir}/usr/share/webapps/${pkgname}/{}" \; } From 3980f2230c97d5abee06401508fa6f4d8271ee3f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 23 Apr 2023 16:54:28 +0200 Subject: [PATCH 088/276] Add colors to logo --- .pre-commit-config.yaml | 2 +- nummi/main/static/main/svg/logo-white.svg | 22 ---------------------- nummi/main/static/main/svg/logo.svg | 10 +++++++--- 3 files changed, 8 insertions(+), 26 deletions(-) delete mode 100644 nummi/main/static/main/svg/logo-white.svg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2da9f62..68993c2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,4 +22,4 @@ repos: rev: "v3.0.0-alpha.6" hooks: - id: prettier - types_or: ["css", "javascript"] + types_or: ["css", "javascript", "svg"] diff --git a/nummi/main/static/main/svg/logo-white.svg b/nummi/main/static/main/svg/logo-white.svg deleted file mode 100644 index ed0bb05..0000000 --- a/nummi/main/static/main/svg/logo-white.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/nummi/main/static/main/svg/logo.svg b/nummi/main/static/main/svg/logo.svg index 0c7fc52..6ea8b87 100644 --- a/nummi/main/static/main/svg/logo.svg +++ b/nummi/main/static/main/svg/logo.svg @@ -6,6 +6,10 @@ + + + + @@ -15,8 +19,8 @@ + fill="url('#grad')" + mask="url('#mask')" + transform-origin="center center" transform="rotate(10)" /> From 3d9424a1d517477ebad367c0d742a17bc800b413 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 23 Apr 2023 17:14:56 +0200 Subject: [PATCH 089/276] Remove LocaleMiddleware --- nummi/nummi/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index 144c198..346c749 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -63,7 +63,6 @@ INSTALLED_APPS = [ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", From 819bd20fdf64442ef3deb4159a123c1c6623dfa3 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 24 Apr 2023 09:07:30 +0200 Subject: [PATCH 090/276] Fix file uploads (borken by missing enctype on form) --- nummi/main/templates/main/form/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html index def3515..65c6f6e 100644 --- a/nummi/main/templates/main/form/base.html +++ b/nummi/main/templates/main/form/base.html @@ -34,7 +34,7 @@ {% endif %} {% block pre %}{% endblock %} - + {% csrf_token %} {% if instance|adding %}{% endif %} {{ form }} From fcdfd0e9adac0225cd224c61bab6b707e3530a7b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 24 Apr 2023 09:12:22 +0200 Subject: [PATCH 091/276] Update PKGBUILD to v0.9.1 --- pkgbuild/.gitignore | 1 + pkgbuild/PKGBUILD | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkgbuild/.gitignore b/pkgbuild/.gitignore index 19f79ea..96cce92 100644 --- a/pkgbuild/.gitignore +++ b/pkgbuild/.gitignore @@ -1,3 +1,4 @@ /*.pkg.tar.zst +/pkg/ /src/ /nummi/ diff --git a/pkgbuild/PKGBUILD b/pkgbuild/PKGBUILD index 871eefd..16ac39e 100644 --- a/pkgbuild/PKGBUILD +++ b/pkgbuild/PKGBUILD @@ -1,5 +1,5 @@ pkgname=nummi -pkgver=v0.9 +pkgver=v0.9.1 pkgrel=1 pkgdesc="Web-based accounting interface" arch=("any") @@ -9,12 +9,12 @@ depends=( "gunicorn" "python-django" "python-toml" - "python-psycopg2" + "python-psycopg" ) makedepends=("git") optdepends=("postgresql: database") backup=("etc/${pkgname}/config.toml") -_tag=c29a01ed5821787bfcef7afbdb100604feab97d0 +_tag=bd6447606b8fb6a8f5006ef504f73618b179f9ac source=( "${pkgname}::git+https://git.edgarpierre.fr/edpibu/nummi?signed#tag=$_tag" "${pkgname}.service" From b9e129e80a46ee165942f2c2b87c16b867a1513b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 May 2023 12:01:25 +0200 Subject: [PATCH 092/276] Move category plot to templatetag, hide non-budget categories --- .../templates/category/category_plot.html | 16 +++++++++------- nummi/category/templatetags/__init__.py | 0 .../{utils.py => templatetags/category.py} | 11 ++++++++--- .../templates/statement/statement_form.html | 9 +++------ nummi/statement/views.py | 3 --- .../transaction/transaction_archive_month.html | 7 +++---- nummi/transaction/views.py | 3 --- 7 files changed, 23 insertions(+), 26 deletions(-) create mode 100644 nummi/category/templatetags/__init__.py rename nummi/category/{utils.py => templatetags/category.py} (62%) diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index 1a0fc4a..1313da6 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -18,7 +18,7 @@ {% spaceless %} - {% for cat in categories.data %} + {% for cat in categories %} {% if cat.category %} @@ -34,25 +34,27 @@ {{ cat.sum_m|pmrvalue }} -
    +
    {% if cat.sum < 0 %} -
    +
    {{ cat.sum|pmrvalue }}
    {% endif %} -
    +
    {% if cat.sum > 0 %} -
    +
    {{ cat.sum|pmrvalue }}
    {% endif %} {{ cat.sum_p|pmrvalue }} + {% empty %} + + {% translate "No transaction" %} + {% endfor %} {% endspaceless %} diff --git a/nummi/category/templatetags/__init__.py b/nummi/category/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nummi/category/utils.py b/nummi/category/templatetags/category.py similarity index 62% rename from nummi/category/utils.py rename to nummi/category/templatetags/category.py index b3b0ea8..c43256b 100644 --- a/nummi/category/utils.py +++ b/nummi/category/templatetags/category.py @@ -1,9 +1,14 @@ +from django import template from django.db import models +register = template.Library() -def get_categories(transactions): + +@register.inclusion_tag("category/category_plot.html") +def category_plot(transactions): categories = ( - transactions.values("category", "category__name", "category__icon") + transactions.filter(category__budget=True) + .values("category", "category__name", "category__icon") .annotate( sum=models.Sum("value"), sum_m=models.Sum("value", filter=models.Q(value__lt=0)), @@ -12,7 +17,7 @@ def get_categories(transactions): .order_by("-sum") ) return { - "data": categories, + "categories": categories, "max": max( categories.aggregate( max=models.Max("sum_p", default=0), diff --git a/nummi/statement/templates/statement/statement_form.html b/nummi/statement/templates/statement/statement_form.html index bb40e07..ef28ce5 100644 --- a/nummi/statement/templates/statement/statement_form.html +++ b/nummi/statement/templates/statement/statement_form.html @@ -1,6 +1,5 @@ {% extends "main/form/base.html" %} -{% load main_extras %} -{% load i18n %} +{% load i18n main_extras category %} {% block title_new %} {% translate "Create statement" %} {% endblock %} @@ -19,11 +18,9 @@ {% endif %} {% endblock %} {% block tables %} - {% if categories %} -

    {% translate "Categories" %}

    - {% include "category/category_plot.html" %} - {% endif %} {% if not form.instance|adding %} +

    {% translate "Categories" %}

    + {% category_plot transactions %}

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

    {% include "transaction/transaction_table.html" %} {% endif %} diff --git a/nummi/statement/views.py b/nummi/statement/views.py index 9121ba3..435cb85 100644 --- a/nummi/statement/views.py +++ b/nummi/statement/views.py @@ -1,5 +1,4 @@ from account.models import Account -from category.utils import get_categories from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from main.views import NummiCreateView, NummiDeleteView, NummiListView, NummiUpdateView @@ -42,8 +41,6 @@ class StatementUpdateView(NummiUpdateView): statement = data["form"].instance _transactions = statement.transaction_set.all() - if _transactions: - data["categories"] = get_categories(_transactions) return data | { "account": statement.account, diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index 431e4db..9c5ea87 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -1,6 +1,5 @@ {% extends "transaction/transaction_list.html" %} -{% load i18n %} -{% load static %} +{% load i18n static category %} {% block link %} {{ block.super }} {% translate "Transactions" %} {{ block.super }} - {% if categories %} + {% if not category %}

    {% translate "Categories" %}

    - {% include "category/category_plot.html" %} + {% category_plot transactions %} {% endif %} {% endblock %} diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 238d19a..f7464cd 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -1,6 +1,5 @@ from account.models import Account from category.models import Category -from category.utils import get_categories from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.views.generic.dates import MonthArchiveView @@ -136,8 +135,6 @@ class TransactionMonthView(UserMixin, MonthArchiveView): context_data = super().get_context_data(**kwargs) if "category" in self.kwargs: return context_data | {"category": self.category} - - context_data["categories"] = get_categories(context_data["transactions"]) if "account" in self.kwargs: return context_data | {"account": self.account} return context_data From 574d1d28750ef025d398c99c07946e34e470e843 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 May 2023 12:18:20 +0200 Subject: [PATCH 093/276] Add total line to category plot Closes #9 --- .../templates/category/category_plot.html | 25 +++++++++++++++++++ nummi/category/templatetags/category.py | 14 +++++------ nummi/main/static/main/css/plot.css | 4 +++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index 1313da6..33d67fd 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -58,5 +58,30 @@ {% endfor %} {% endspaceless %} + {% if categories %} + + + {% translate "Total" %} + {{ total_m|pmrvalue }} + +
    + {% if total < 0 %} +
    + {{ total|pmrvalue }} +
    + {% endif %} + + +
    + {% if total > 0 %} +
    + {{ total|pmrvalue }} +
    + {% endif %} + + {{ total_p|pmrvalue }} + + + {% endif %}
    diff --git a/nummi/category/templatetags/category.py b/nummi/category/templatetags/category.py index c43256b..5709af0 100644 --- a/nummi/category/templatetags/category.py +++ b/nummi/category/templatetags/category.py @@ -1,5 +1,6 @@ from django import template from django.db import models +from django.db.models.functions import Greatest register = template.Library() @@ -18,10 +19,9 @@ def category_plot(transactions): ) return { "categories": categories, - "max": max( - categories.aggregate( - max=models.Max("sum_p", default=0), - min=models.Min("sum_m", default=0), - ).values(), - ), - } + } | categories.aggregate( + max=Greatest(models.Sum("sum_m"), models.Sum("sum_p")), + total_m=models.Sum("sum_m"), + total_p=models.Sum("sum_p"), + total=models.Sum("value"), + ) diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index d38d575..15fc7ab 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -70,3 +70,7 @@ table.full-width col.bar { overflow: hidden; } } + +.plot tfoot { + background: var(--bg-01); +} From eabdaf2516300614a4ff7cd4efe180621e27effb Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 May 2023 12:20:19 +0200 Subject: [PATCH 094/276] Fix urls in category plot on month page --- nummi/category/templatetags/category.py | 20 +++++++++++-------- .../transaction_archive_month.html | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/nummi/category/templatetags/category.py b/nummi/category/templatetags/category.py index 5709af0..8bfd4e4 100644 --- a/nummi/category/templatetags/category.py +++ b/nummi/category/templatetags/category.py @@ -6,7 +6,7 @@ register = template.Library() @register.inclusion_tag("category/category_plot.html") -def category_plot(transactions): +def category_plot(transactions, **kwargs): categories = ( transactions.filter(category__budget=True) .values("category", "category__name", "category__icon") @@ -17,11 +17,15 @@ def category_plot(transactions): ) .order_by("-sum") ) - return { - "categories": categories, - } | categories.aggregate( - max=Greatest(models.Sum("sum_m"), models.Sum("sum_p")), - total_m=models.Sum("sum_m"), - total_p=models.Sum("sum_p"), - total=models.Sum("value"), + return ( + kwargs + | { + "categories": categories, + } + | categories.aggregate( + max=Greatest(models.Sum("sum_m"), models.Sum("sum_p")), + total_m=models.Sum("sum_m"), + total_p=models.Sum("sum_p"), + total=models.Sum("value"), + ) ) diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index 9c5ea87..3f5dc1b 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -12,6 +12,6 @@ {{ block.super }} {% if not category %}

    {% translate "Categories" %}

    - {% category_plot transactions %} + {% category_plot transactions month=month %} {% endif %} {% endblock %} From a5dd41b53385db28ea5b37b3f1baa2ce7572462c Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 May 2023 12:56:00 +0200 Subject: [PATCH 095/276] In category_plot templatetag, replace filter budget with exclude not budget --- nummi/category/templatetags/category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/category/templatetags/category.py b/nummi/category/templatetags/category.py index 8bfd4e4..8af8c24 100644 --- a/nummi/category/templatetags/category.py +++ b/nummi/category/templatetags/category.py @@ -8,7 +8,7 @@ register = template.Library() @register.inclusion_tag("category/category_plot.html") def category_plot(transactions, **kwargs): categories = ( - transactions.filter(category__budget=True) + transactions.exclude(category__budget=False) .values("category", "category__name", "category__icon") .annotate( sum=models.Sum("value"), From 02bd1853d5dfd239ae8699a74909b5954f084d7a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 May 2023 22:00:36 +0200 Subject: [PATCH 096/276] Fix wrong total values (#10) --- nummi/category/templatetags/category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/category/templatetags/category.py b/nummi/category/templatetags/category.py index 8af8c24..e344471 100644 --- a/nummi/category/templatetags/category.py +++ b/nummi/category/templatetags/category.py @@ -26,6 +26,6 @@ def category_plot(transactions, **kwargs): max=Greatest(models.Sum("sum_m"), models.Sum("sum_p")), total_m=models.Sum("sum_m"), total_p=models.Sum("sum_p"), - total=models.Sum("value"), + total=models.Sum("sum"), ) ) From 3c1e4ac45da193344335c960b01fe2ccbadb87be Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 May 2023 22:02:22 +0200 Subject: [PATCH 097/276] Fix broken negative bars when total is negative (fixes #10) --- nummi/category/templatetags/category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/category/templatetags/category.py b/nummi/category/templatetags/category.py index e344471..5f76341 100644 --- a/nummi/category/templatetags/category.py +++ b/nummi/category/templatetags/category.py @@ -23,7 +23,7 @@ def category_plot(transactions, **kwargs): "categories": categories, } | categories.aggregate( - max=Greatest(models.Sum("sum_m"), models.Sum("sum_p")), + max=Greatest(-models.Sum("sum_m"), models.Sum("sum_p")), total_m=models.Sum("sum_m"), total_p=models.Sum("sum_p"), total=models.Sum("sum"), From 76ac6bc7fbd87ab783f5b333e291a417c6da6341 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 25 Nov 2023 10:44:57 +0100 Subject: [PATCH 098/276] Use tomllib instead of toml --- nummi/nummi/settings.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/nummi/nummi/settings.py b/nummi/nummi/settings.py index 346c749..1050bab 100644 --- a/nummi/nummi/settings.py +++ b/nummi/nummi/settings.py @@ -11,15 +11,14 @@ https://docs.djangoproject.com/en/4.0/ref/settings/ """ import os +import tomllib from pathlib import Path -import toml - CONFIG_PATH = os.environ.get("NUMMI_CONFIG", None) -if CONFIG_PATH is None: - CONFIG = dict() -else: - CONFIG = toml.load(CONFIG_PATH) +CONFIG = dict() +if CONFIG_PATH is not None: + with Path(CONFIG_PATH).open("rb") as CONFIG_FILE: + CONFIG = tomllib.load(CONFIG_FILE) # Build paths inside the project like this: BASE_DIR / 'subdir'. MEDIA_CONF = CONFIG.get("media", {}) From 8dd29be8bf8b3f915153bab0311b60f3ae058461 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 25 Nov 2023 11:28:20 +0100 Subject: [PATCH 099/276] Add success message on object creation with link to object --- nummi/main/templates/main/base.html | 5 +++++ nummi/main/views.py | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 98da0b8..61de121 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -88,6 +88,11 @@ {% endspaceless %} {% endblock %}
    + {% if messages %} +
      + {% for message in messages %}
    • {{ message }}
    • {% endfor %} +
    + {% endif %} {% block body %}{% endblock %}
    diff --git a/nummi/main/views.py b/nummi/main/views.py index 8095486..51a7131 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -1,8 +1,10 @@ from account.models import Account from category.models import Category +from django.contrib import messages from django.contrib.auth import views as auth_views from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse_lazy +from django.utils.html import format_html from django.views.generic import ( CreateView, DeleteView, @@ -55,7 +57,18 @@ class NummiCreateView(UserMixin, CreateView): return super().form_valid(form) def get_success_url(self): - return self.next or super().get_success_url() + surl = super().get_success_url() + messages.success( + self.request, + format_html( + "{name} {msg}", + surl=surl, + name=self.object, + msg="was created successfully", + ), + ) + + return self.next or surl class NummiUpdateView(UserMixin, UpdateView): From ffc0fa3ce1c1da8a57f11e709ba2e86c71b166a7 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 25 Nov 2023 11:56:14 +0100 Subject: [PATCH 100/276] Update messages styling --- nummi/main/static/main/css/main.css | 22 ++++++++++++++++++++++ nummi/main/templates/main/base.html | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 6aa5a99..8930bbd 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -194,3 +194,25 @@ main h2.new { p { margin: 0.5em 0; } + +ul.messages { + font-weight: 550; + list-style-type: none; + margin: 0; + background: var(--bg-01); + padding: 0; +} +ul.messages li { + padding: calc(var(--gap) / 2) var(--gap); + border-left: var(--border) solid var(--gray); +} + +ul.messages li.msg-level-25 { + border-left-color: var(--green-1); +} +ul.messages li.msg-level-30 { + border-left-color: var(--red-1); +} +ul.messages li.msg-level-40 { + border-left-color: var(--red); +} diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 61de121..3b3d2aa 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -89,8 +89,8 @@ {% endblock %}
    {% if messages %} -
      - {% for message in messages %}
    • {{ message }}
    • {% endfor %} +
        + {% for message in messages %}
      • {{ message }}
      • {% endfor %}
      {% endif %} {% block body %}{% endblock %} From 4bbb5de3c5547264dbfbde1f18f3eea28691c8f8 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 25 Nov 2023 12:07:01 +0100 Subject: [PATCH 101/276] Update translations --- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 813 -> 899 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 10 +++++++++- nummi/main/locale/fr_FR/LC_MESSAGES/django.mo | Bin 1682 -> 1751 bytes nummi/main/locale/fr_FR/LC_MESSAGES/django.po | 6 +++++- nummi/main/views.py | 3 ++- .../locale/fr_FR/LC_MESSAGES/django.po | 10 +++++----- .../locale/fr_FR/LC_MESSAGES/django.po | 6 +++--- 7 files changed, 24 insertions(+), 11 deletions(-) diff --git a/nummi/category/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/category/locale/fr_FR/LC_MESSAGES/django.mo index 4dfa16e82cd635d8d957ae5b6163dbe69f6c8601..32338f9c0ed8d8e5f6168003298a21f3684d5ae9 100644 GIT binary patch delta 401 zcmZY5y-EW?6o%n%G(W~Q2BLNmw9&%e&cY&S8nH+x3QLkgkXrQ7o8t6q2#u}n6|dvb;RB^`%BDq#y3@fa8I z1Z&vM`x%z$53zuE=yl_~|A0&MpV75muqcf*$p=1goBSlb@Ow^*@CRL3^U@kbSiw#7 z_Z{^7UGxF=kVj2kzDNt}c#0I%CHf%O7<533@QJ+1v=F+0!wY;H&M*zusG7H3%p3V{ rM33v?Z1pJJ$gbj~A9u3zG*LUv;*0m2@EW%E2c1FE`#-AoY<^#VaHK43 delta 315 zcmXxfF%Q8|5Ww+!R9jR8u}Rd#ZnfHsj3k)4RH8PL4i>xk2u6v-z;5>m#ALSH{Excy z@_YAkefQqlIoR=|Z0&^b$QrpKOXQu@@QsL#4a{K+U2J0!J4xL`hkAe(4pIM`Bz1r~ zBTQUk(?~2E7TS1BiRAH&I_Qd3ykQof$^Hwq|BX6O(%(;9tWbKWgM8#@p-o%9q5B$o mK|Rx!x|YAeI#gfwj|>ZE+cl%p#q3R?KOcwDc)AE?VI&_l+Zi|j diff --git a/nummi/category/locale/fr_FR/LC_MESSAGES/django.po b/nummi/category/locale/fr_FR/LC_MESSAGES/django.po index ca5b98a..e5881ca 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: 2023-04-22 15:16+0200\n" +"POT-Creation-Date: 2023-11-25 12:05+0100\n" "PO-Revision-Date: 2023-04-22 15:18+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -61,3 +61,11 @@ msgstr "Dépenses" #: .\category\templates\category\category_plot.html:16 msgid "Income" msgstr "Revenus" + +#: .\category\templates\category\category_plot.html:56 +msgid "No transaction" +msgstr "Aucune transaction" + +#: .\category\templates\category\category_plot.html:64 +msgid "Total" +msgstr "Total" diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo index 4685846eef54e3eb6b0be73dad20de77fa8de525..7f2a8f069af009197b2d2fc8c0ebda76e0fe89e9 100644 GIT binary patch delta 551 zcmXZZJ1+!L6u|L2v&$N+WrJ*(ViNJ_XcQ6}5uHXQqM3|Y5nC*?k5HI|W*d!1p;4$z zMY1Fkg~%j?jz%`|XcP(`fJEVc?A+we?>^2w_sq<0@;T|hrQ-*RW=8!N`{^$l4_aPV zstH$d0ynS`FR>Y~F@X=!`V+P>uVD&5u^qoLjYh0~-o&_4zUpHj35MzmY9tC6jn>C; zfc+_y3sqJiS(JW!4HFBG{`O+V<-uyQ9k7y zN+C-qi9GDVO_X1Ah;r^2CGHe^@oMj0+t$qLjJ~X8%C=`YYpz{z7cH;oIIibqi@Dr- a^&Js&bnxzgLK9mF3EP{tv zz!RLnCQjlbhOz69_i>v33(nvWJse{Ke^KjW0kfc4)#hj@LF#|NmQev&e|!yB=x?Al z+{Gz8!VsPziL8QJcZo^7M#VQUh7YK?Hs-k86NcH}MvRu2_(mOMm8{xe9iv!4B`%|G zvW@e&kF$7=3wVo)zenY1q7KmV`#sb_o>4FJiuo-XU;c#0E=rg|Jz*YokRmG4CZ=%* b^-E4s2`^A_H??=?%CXL#d+c;R10U`m;ae(S diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.po b/nummi/main/locale/fr_FR/LC_MESSAGES/django.po index 6697c94..18f7b5d 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: 2023-04-23 08:02+0200\n" +"POT-Creation-Date: 2023-11-25 12:05+0100\n" "PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -124,3 +124,7 @@ msgstr "Aucune catégorie" #: .\main\templates\main\index.html:44 msgid "History" msgstr "Historique" + +#: .\main\views.py:68 +msgid "was created successfully" +msgstr "a été créé avec succès" diff --git a/nummi/main/views.py b/nummi/main/views.py index 51a7131..e1d52a3 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -5,6 +5,7 @@ from django.contrib.auth import views as auth_views from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse_lazy from django.utils.html import format_html +from django.utils.translation import gettext as _ from django.views.generic import ( CreateView, DeleteView, @@ -64,7 +65,7 @@ class NummiCreateView(UserMixin, CreateView): "{name} {msg}", surl=surl, name=self.object, - msg="was created successfully", + msg=_("was created successfully"), ), ) diff --git a/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po index 88593df..c9ae392 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: 2023-04-22 15:22+0200\n" +"POT-Creation-Date: 2023-11-25 12:05+0100\n" "PO-Revision-Date: 2023-04-22 15:22+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -65,20 +65,20 @@ msgstr "Relevé" msgid "Statements" msgstr "Relevés" -#: .\statement\templates\statement\statement_form.html:5 +#: .\statement\templates\statement\statement_form.html:4 #: .\statement\templates\statement\statement_table.html:5 msgid "Create statement" msgstr "Créer un relevé" -#: .\statement\templates\statement\statement_form.html:8 +#: .\statement\templates\statement\statement_form.html:7 msgid "New statement" msgstr "Nouveau relevé" -#: .\statement\templates\statement\statement_form.html:23 +#: .\statement\templates\statement\statement_form.html:22 msgid "Categories" msgstr "Catégories" -#: .\statement\templates\statement\statement_form.html:27 +#: .\statement\templates\statement\statement_form.html:24 #: .\statement\templates\statement\statement_table.html:28 msgid "Transactions" msgstr "Transactions" diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po index fc73dd9..26f95c0 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: 2023-04-23 08:02+0200\n" +"POT-Creation-Date: 2023-11-25 12:05+0100\n" "PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -69,7 +69,7 @@ msgid "Account" msgstr "Compte" #: .\transaction\models.py:83 -#: .\transaction\templates\transaction\transaction_archive_month.html:12 +#: .\transaction\templates\transaction\transaction_archive_month.html:11 #: .\transaction\templates\transaction\transaction_list.html:4 #: .\transaction\templates\transaction\transaction_list.html:7 msgid "Transactions" @@ -108,7 +108,7 @@ msgstr "Supprimer" msgid "No invoice" msgstr "Aucune facture" -#: .\transaction\templates\transaction\transaction_archive_month.html:15 +#: .\transaction\templates\transaction\transaction_archive_month.html:14 msgid "Categories" msgstr "Catégories" From 9dbbd3d48e6e27366d12fdbc1510b4e0bf50e074 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 28 Dec 2023 15:46:52 +0100 Subject: [PATCH 102/276] Add monthly year chart --- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 454 -> 490 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 6 ++- nummi/history/templates/history/plot.html | 39 ++++++++++++++++++ nummi/history/utils.py | 29 +++++++++++-- nummi/main/static/main/css/plot.css | 25 +++++++++++ 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/nummi/history/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/history/locale/fr_FR/LC_MESSAGES/django.mo index c369f8dc5a4f3023676a06c1e9925a36ca66799f..cdfe26fffdcf15c1ca75914eaed79c8fc4615fe2 100644 GIT binary patch delta 159 zcmX@c{EE5$o)F7a1|VPsVi_QI0b+I_&H-W&=m264AnpWWHXxn^#2~dZftUq|=L4}i zBLl+{Ak7KHXMk*w{%b%QB!3S`gY-QH(m(_T%s>)k1`|jMq=;c+t`tjTYGTpGHU~y_ L$Gp75D^nQ&ROS-} delta 144 zcmaFGe2lsNo)F7a1|VPoVi_Q|0b*7ljsap2C;(y(AT9)AHXyD7Vvu|z5UVpXFth_{ zP9R\n" "Language-Team: \n" @@ -28,3 +28,7 @@ msgstr "Dépenses" #: .\history\templates\history\plot.html:17 msgid "Income" msgstr "Revenus" + +#: .\history\templates\history\plot.html:68 +msgid "Year" +msgstr "Année" diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 43d94ef..cba9e18 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -61,3 +61,42 @@
    +
    + + + + + + + + + + + + + + + + + + + + {% spaceless %} + {% for year in history.years %} + + + {% for m in year.d %} + {% if m %} + + {% else %} + + {% endif %} + {% endfor %} + + {% endfor %} + {% endspaceless %} + +
    {% translate "Year" %}010203040506070809101112
    {{ year.y }} +
    +
    diff --git a/nummi/history/utils.py b/nummi/history/utils.py index ef0a353..adf1c35 100644 --- a/nummi/history/utils.py +++ b/nummi/history/utils.py @@ -1,6 +1,8 @@ +import datetime + from django.db import models from django.db.models import Func, Max, Min, Q, Sum, Value -from django.db.models.functions import Now, TruncMonth +from django.db.models.functions import Abs, Now, TruncMonth class GenerateMonth(Func): @@ -22,11 +24,17 @@ def history(transaction_set): ) ) .annotate( - sum_m=Value(0), sum_p=Value(0), sum=Value(0), has_transactions=Value(0) + sum_m=Value(0), + sum_p=Value(0), + sum=Value(0), + has_transactions=Value(0), ) .difference( _transaction_month.annotate( - sum_m=Value(0), sum_p=Value(0), sum=Value(0), has_transactions=Value(0) + sum_m=Value(0), + sum_p=Value(0), + sum=Value(0), + has_transactions=Value(0), ) ) ) @@ -39,10 +47,25 @@ def history(transaction_set): return { "data": _history.union(_months).order_by("-month"), + "years": [ + { + "y": y, + "d": [ + _history.filter(month=datetime.date(y, m + 1, 1)).first() + for m in range(12) + ], + } + for y in range( + _transaction_month.first()["month"].year, + _transaction_month.last()["month"].year - 1, + -1, + ) + ], "max": max( _history.aggregate( max=Max("sum_p", default=0), min=-Min("sum_m", default=0), ).values(), ), + "years_max": _history.aggregate(max=Max(Abs("sum")))["max"], } diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 15fc7ab..a55a06f 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -74,3 +74,28 @@ table.full-width col.bar { .plot tfoot { background: var(--bg-01); } + +.calendar { + margin-top: var(--gap); +} +.calendar .p { + background: var(--green); +} +.calendar .o-0 { + opacity: 0.1; +} +.calendar .o-1 { + opacity: 0.5; +} +.calendar .o-2 { + opacity: 0.75; +} +.calendar .o-3 { + opacity: 0.9; +} +.calendar .o-4 { + opacity: 1; +} +.calendar .m { + background: var(--red); +} From 6f631607b8c289f117bd462a5de42a29fcb0364b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 28 Dec 2023 16:31:13 +0100 Subject: [PATCH 103/276] Reduce computations in history generation --- .../templates/category/category_form.html | 2 +- nummi/history/templates/history/plot.html | 85 ++++++++++--------- nummi/history/utils.py | 40 +++------ nummi/main/static/main/css/plot.css | 3 + nummi/main/templates/main/index.html | 2 +- 5 files changed, 63 insertions(+), 69 deletions(-) diff --git a/nummi/category/templates/category/category_form.html b/nummi/category/templates/category/category_form.html index aa16a70..ed9e399 100644 --- a/nummi/category/templates/category/category_form.html +++ b/nummi/category/templates/category/category_form.html @@ -11,7 +11,7 @@ {% block tables %}

    {% translate "Transactions" %}

    {% include "transaction/transaction_table.html" %} - {% if history.data %} + {% if history %}

    {% translate "History" %}

    {% include "history/plot.html" %} {% endif %} diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index cba9e18..3f0b456 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -19,43 +19,51 @@ {% spaceless %} - {% for date in history.data %} - - - - - - {% if date.has_transactions %} - {% if account %} - {{ date.month|date:"Y-m" }} - {% elif category %} - {{ date.month|date:"Y-m" }} - {% else %} - {{ date.month|date:"Y-m" }} - {% endif %} - {% else %} - {{ date.month|date:"Y-m" }} - {% endif %} - - {{ date.sum_m|pmrvalue }} - -
    - {% if date.sum < 0 %} -
    - {{ date.sum|pmrvalue }} -
    - {% endif %} - - -
    - {% if date.sum > 0 %} -
    - {{ date.sum|pmrvalue }} -
    - {% endif %} - - {{ date.sum_p|pmrvalue }} - + {% for y in history.years reversed %} + {% for date in y.d reversed %} + {% if date %} + + + + + + {% if date.has_transactions %} + {% if account %} + {{ date.month|date:"Y-m" }} + {% elif category %} + {{ date.month|date:"Y-m" }} + {% else %} + {{ date.month|date:"Y-m" }} + {% endif %} + {% else %} + {{ date.month|date:"Y-m" }} + {% endif %} + + {{ date.sum_m|pmrvalue }} + +
    + {% if date.sum < 0 %} +
    + {{ date.sum|pmrvalue }} +
    + {% endif %} + + +
    + {% if date.sum > 0 %} +
    + {{ date.sum|pmrvalue }} +
    + {% endif %} + + {{ date.sum_p|pmrvalue }} + + {% else %} + + + + {% endif %} + {% endfor %} {% endfor %} {% endspaceless %} @@ -82,7 +90,7 @@ {% spaceless %} - {% for year in history.years %} + {% for year in history.years reversed %} {{ year.y }} {% for m in year.d %} @@ -100,3 +108,4 @@ +{{ history.years|json_script }} diff --git a/nummi/history/utils.py b/nummi/history/utils.py index adf1c35..2af095f 100644 --- a/nummi/history/utils.py +++ b/nummi/history/utils.py @@ -1,8 +1,7 @@ import datetime -from django.db import models from django.db.models import Func, Max, Min, Q, Sum, Value -from django.db.models.functions import Abs, Now, TruncMonth +from django.db.models.functions import Abs, TruncMonth class GenerateMonth(Func): @@ -13,31 +12,13 @@ class GenerateMonth(Func): def history(transaction_set): if not transaction_set.exists(): return None + _transaction_month = transaction_set.values(month=TruncMonth("date")).order_by( "-date" ) - _months = ( - transaction_set.values( - month=GenerateMonth( - _transaction_month.last()["month"], - Now(output_field=models.DateField()), - ) - ) - .annotate( - sum_m=Value(0), - sum_p=Value(0), - sum=Value(0), - has_transactions=Value(0), - ) - .difference( - _transaction_month.annotate( - sum_m=Value(0), - sum_p=Value(0), - sum=Value(0), - has_transactions=Value(0), - ) - ) - ) + _first_month = _transaction_month.last()["month"] + _last_month = _transaction_month.first()["month"] + _history = _transaction_month.annotate( sum_p=Sum("value", filter=Q(value__gt=0)), sum_m=Sum("value", filter=Q(value__lt=0)), @@ -46,19 +27,20 @@ def history(transaction_set): ).order_by("-month") return { - "data": _history.union(_months).order_by("-month"), "years": [ { "y": y, "d": [ _history.filter(month=datetime.date(y, m + 1, 1)).first() - for m in range(12) + for m in range( + 0, + _last_month.month if _last_month.year == y else 12, + ) ], } for y in range( - _transaction_month.first()["month"].year, - _transaction_month.last()["month"].year - 1, - -1, + _first_month.year, + _last_month.year + 1, ) ], "max": max( diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index a55a06f..52dd617 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -63,6 +63,9 @@ table.full-width col.bar { .plot td.bar.m div.tot span { right: 0; } +.plot tr.empty { + height: 0.5rem; +} @media (width < 720px) { .plot .bar { diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index cdadde8..a6fd1c0 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -40,7 +40,7 @@

    {% translate "Statements" %}

    {% include "statement/statement_table.html" %} {% endif %} - {% if history.data %} + {% if history %}

    {% translate "History" %}

    {% include "history/plot.html" %} {% endif %} From 8652eb0b57d3e04d44834bac024f1091e7420a2c Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 29 Dec 2023 09:23:51 +0100 Subject: [PATCH 104/276] Remove borders in calendar table --- nummi/main/static/main/css/plot.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 52dd617..0d1223c 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -102,3 +102,9 @@ table.full-width col.bar { .calendar .m { background: var(--red); } +.calendar tbody tr:not(:last-child) { + border-bottom: none; +} +.calendar tbody tr:not(:first-child) { + border-top: none; +} From 4bb682fc8806a04d496294f432ef4f993089170e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 29 Dec 2023 09:50:08 +0100 Subject: [PATCH 105/276] Fix HTML for index page --- nummi/history/templates/history/plot.html | 17 ++++++--- nummi/history/utils.py | 35 ++++++++++--------- nummi/main/templates/main/base.html | 12 +++---- nummi/main/templates/main/index.html | 4 +-- .../templates/statement/statement_table.html | 20 ++++++----- .../transaction/transaction_table.html | 24 +++++++------ 6 files changed, 64 insertions(+), 48 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 3f0b456..16e2b94 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -41,7 +41,9 @@ {{ date.sum_m|pmrvalue }} -
    + {% if date.sum_m %} +
    + {% endif %} {% if date.sum < 0 %}
    {{ date.sum|pmrvalue }} @@ -49,7 +51,9 @@ {% endif %} -
    + {% if date.sum_p %} +
    + {% endif %} {% if date.sum > 0 %}
    {{ date.sum|pmrvalue }} @@ -60,7 +64,7 @@ {% else %} - + {% endif %} {% endfor %} @@ -94,6 +98,9 @@ {{ year.y }} {% for m in year.d %} + {% if forloop.parentloop.last and forloop.first %} + {% for _ in history.offset.0 %}{% endfor %} + {% endif %} {% if m %} @@ -101,6 +108,9 @@ {% else %} {% endif %} + {% if forloop.parentloop.first and forloop.last %} + {% for _ in history.offset.1 %}{% endfor %} + {% endif %} {% endfor %} {% endfor %} @@ -108,4 +118,3 @@
    -{{ history.years|json_script }} diff --git a/nummi/history/utils.py b/nummi/history/utils.py index 2af095f..282d354 100644 --- a/nummi/history/utils.py +++ b/nummi/history/utils.py @@ -26,23 +26,26 @@ def history(transaction_set): has_transactions=Value(1), ).order_by("-month") + _data = [ + { + "y": y, + "d": [ + _history.filter(month=datetime.date(y, m + 1, 1)).first() + for m in range( + _first_month.month if _first_month.year == y else 0, + _last_month.month if _last_month.year == y else 12, + ) + ], + } + for y in range( + _first_month.year, + _last_month.year + 1, + ) + ] + return { - "years": [ - { - "y": y, - "d": [ - _history.filter(month=datetime.date(y, m + 1, 1)).first() - for m in range( - 0, - _last_month.month if _last_month.year == y else 12, - ) - ], - } - for y in range( - _first_month.year, - _last_month.year + 1, - ) - ], + "years": _data, + "offset": [range(_first_month.month), range(12 - _last_month.month)], "max": max( _history.aggregate( max=Max("sum_p", default=0), diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 3b3d2aa..6c60492 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -3,15 +3,15 @@ - - + + {% block title %}Nummi{% endblock %} {% block link %} - - - + + + {% endblock %} @@ -19,7 +19,7 @@ {% spaceless %}
    @@ -95,28 +93,26 @@ - {% spaceless %} - {% for year in history.years reversed %} - - {{ year.y }} - {% for m in year.d %} - {% if forloop.parentloop.last and forloop.first %} - {% for _ in history.offset.0 %}{% endfor %} - {% endif %} - {% if m %} - - - {% else %} - - {% endif %} - {% if forloop.parentloop.first and forloop.last %} - {% for _ in history.offset.1 %}{% endfor %} - {% endif %} - {% endfor %} - - {% endfor %} - {% endspaceless %} + {% for year in history.years reversed %} + + {{ year.y }} + {% for m in year.d %} + {% if forloop.parentloop.last and forloop.first %} + {% for _ in history.offset.0 %}{% endfor %} + {% endif %} + {% if m %} + + + {% else %} + + {% endif %} + {% if forloop.parentloop.first and forloop.last %} + {% for _ in history.offset.1 %}{% endfor %} + {% endif %} + {% endfor %} + + {% endfor %} From 0e5b8ea85d4fad42f9dc1df606ae105afb050112 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 30 Dec 2023 10:24:24 +0100 Subject: [PATCH 116/276] Use :is css pseudo-class --- nummi/main/static/main/css/form.css | 4 +--- nummi/main/static/main/css/main.css | 12 ++++-------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 520470a..3f792b5 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -9,9 +9,7 @@ form > table > tbody > tr > th { background: var(--bg-01); background-clip: padding-box; } -form tbody input, -form tbody select, -form tbody textarea { +form tbody :is(input, select, textarea) { font: inherit; border: none; background: transparent; diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 28b67c4..de17985 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -61,8 +61,7 @@ a { text-decoration: none; display: inline-block; } -a:hover, -a:focus { +a:is(:hover, :focus) { text-decoration: underline; } @@ -109,8 +108,7 @@ nav ul { nav .skip-link { font-weight: 300; } -nav .skip-link:active, -nav .skip-link:focus { +nav .skip-link:is(:active, :focus) { font-weight: 500; } nav a { @@ -124,8 +122,7 @@ nav a.cur::after { position: absolute; right: 0; } -nav > :first-child, -main > :first-child { +:is(nav, main) > :first-child { margin-top: 0; } footer { @@ -146,8 +143,7 @@ footer { font-weight: 650; text-decoration: underline dotted; } -.pagination a.cur:hover, -.pagination a.cur:focus { +.pagination a.cur:is(:hover, :focus) { text-decoration: underline; } From 7f3b9c7b5c961d9398aeef9e00a15b10da29c056 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 30 Dec 2023 10:38:30 +0100 Subject: [PATCH 117/276] Structure page content with sections --- .../templates/account/account_form.html | 18 ++++-- .../templates/category/category_form.html | 12 ++-- nummi/main/templates/main/index.html | 58 +++++++++++-------- .../templates/statement/statement_form.html | 12 ++-- .../transaction/transaction_form.html | 6 +- 5 files changed, 66 insertions(+), 40 deletions(-) diff --git a/nummi/account/templates/account/account_form.html b/nummi/account/templates/account/account_form.html index 7045203..eaba124 100644 --- a/nummi/account/templates/account/account_form.html +++ b/nummi/account/templates/account/account_form.html @@ -10,13 +10,19 @@ {% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} {% block tables %} {% if not form.instance|adding %} -

    {% translate "Statements" %}

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

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} +
    +

    {% translate "Statements" %}

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

    {% translate "Transactions" %}

    + {% include "transaction/transaction_table.html" %} +
    {% if history %} -

    {% translate "History" %}

    - {% include "history/plot.html" %} +
    +

    {% translate "History" %}

    + {% include "history/plot.html" %} +
    {% endif %} {% endif %} {% endblock %} diff --git a/nummi/category/templates/category/category_form.html b/nummi/category/templates/category/category_form.html index ed9e399..f4aaa6d 100644 --- a/nummi/category/templates/category/category_form.html +++ b/nummi/category/templates/category/category_form.html @@ -9,10 +9,14 @@ {% endblock %} {% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} {% block tables %} -

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} +
    +

    {% translate "Transactions" %}

    + {% include "transaction/transaction_table.html" %} +
    {% if history %} -

    {% translate "History" %}

    - {% include "history/plot.html" %} +
    +

    {% translate "History" %}

    + {% include "history/plot.html" %} +
    {% endif %} {% endblock %} diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index df0474b..d3fd7d7 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -12,36 +12,46 @@ type="text/css"> {% endblock %} {% block body %} -

    {% translate "Accounts" %}

    -

    - {% for acc in accounts %} - {{ acc.icon|remix }}{{ acc }} - {% empty %} - {% translate "No account" %} - {% endfor %} -

    +
    +

    {% translate "Accounts" %}

    +

    + {% for acc in accounts %} + {{ acc.icon|remix }}{{ acc }} + {% empty %} + {% translate "No account" %} + {% endfor %} +

    +
    {% if transactions %} -

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} +
    +

    {% translate "Transactions" %}

    + {% include "transaction/transaction_table.html" %} +
    {% endif %} {% if categories %} -

    {% translate "Categories" %}

    - {% spaceless %} -

    - {% for cat in categories %} - {{ cat.icon|remix }}{{ cat }} - {% empty %} - {% translate "No category" %} - {% endfor %} -

    - {% endspaceless %} +
    +

    {% translate "Categories" %}

    + {% spaceless %} +

    + {% for cat in categories %} + {{ cat.icon|remix }}{{ cat }} + {% empty %} + {% translate "No category" %} + {% endfor %} +

    + {% endspaceless %} +
    {% endif %} {% if statements %} -

    {% translate "Statements" %}

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

    {% translate "Statements" %}

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

    {% translate "History" %}

    - {% include "history/plot.html" %} +
    +

    {% translate "History" %}

    + {% include "history/plot.html" %} +
    {% endif %} {% endblock %} diff --git a/nummi/statement/templates/statement/statement_form.html b/nummi/statement/templates/statement/statement_form.html index ef28ce5..b09e993 100644 --- a/nummi/statement/templates/statement/statement_form.html +++ b/nummi/statement/templates/statement/statement_form.html @@ -19,9 +19,13 @@ {% endblock %} {% block tables %} {% if not form.instance|adding %} -

    {% translate "Categories" %}

    - {% category_plot transactions %} -

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

    - {% include "transaction/transaction_table.html" %} +
    +

    {% translate "Categories" %}

    + {% category_plot transactions %} +
    +
    +

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

    + {% include "transaction/transaction_table.html" %} +
    {% endif %} {% endblock %} diff --git a/nummi/transaction/templates/transaction/transaction_form.html b/nummi/transaction/templates/transaction/transaction_form.html index 75eb0d4..8674742 100644 --- a/nummi/transaction/templates/transaction/transaction_form.html +++ b/nummi/transaction/templates/transaction/transaction_form.html @@ -16,7 +16,9 @@ {% endblock %} {% block tables %} {% if not form.instance|adding %} -

    {% translate "Invoices" %}

    - {% include "transaction/invoice_table.html" %} +
    +

    {% translate "Invoices" %}

    + {% include "transaction/invoice_table.html" %} +
    {% endif %} {% endblock %} From 47d8ff0382aaacd7bbd87d1e5110dbf246b9633d Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 30 Dec 2023 10:54:01 +0100 Subject: [PATCH 118/276] Create tag for css imports --- nummi/main/templates/main/base.html | 5 +++-- nummi/main/templates/main/confirm_delete.html | 8 ++------ nummi/main/templates/main/form/base.html | 12 +++--------- nummi/main/templates/main/index.html | 8 ++------ nummi/main/templates/main/list.html | 4 +--- nummi/main/templates/main/login.html | 8 ++------ nummi/main/templatetags/main_extras.py | 8 ++++++++ nummi/search/templates/search/search.html | 8 ++------ .../transaction/transaction_archive_month.html | 4 +--- 9 files changed, 24 insertions(+), 41 deletions(-) diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 6c60492..e725fcc 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -1,5 +1,6 @@ {% load static %} {% load i18n %} +{% load main_extras %} @@ -10,8 +11,8 @@ {% block link %} - - + {% css "main/css/main.css" %} + {% css "main/remixicon/remixicon.css" %} {% endblock %} diff --git a/nummi/main/templates/main/confirm_delete.html b/nummi/main/templates/main/confirm_delete.html index 7ad791e..19dd77b 100644 --- a/nummi/main/templates/main/confirm_delete.html +++ b/nummi/main/templates/main/confirm_delete.html @@ -4,12 +4,8 @@ {% load i18n %} {% block link %} {{ block.super }} - - + {% css "main/css/form.css" %} + {% css "main/css/table.css" %} {% endblock %} {% block body %} {% spaceless %} diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html index 65c6f6e..e2b273e 100644 --- a/nummi/main/templates/main/form/base.html +++ b/nummi/main/templates/main/form/base.html @@ -12,15 +12,9 @@ {% endblock %} {% block link %} {{ block.super }} - - - + {% css "main/css/form.css" %} + {% css "main/css/table.css" %} + {% css "main/css/plot.css" %} {% endblock %} {% block body %} {% with instance=form.instance %} diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index d3fd7d7..3f56a84 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -4,12 +4,8 @@ {% load i18n %} {% block link %} {{ block.super }} - - + {% css "main/css/table.css" %} + {% css "main/css/plot.css" %} {% endblock %} {% block body %}
    diff --git a/nummi/main/templates/main/list.html b/nummi/main/templates/main/list.html index aa90d98..ee3050e 100644 --- a/nummi/main/templates/main/list.html +++ b/nummi/main/templates/main/list.html @@ -13,9 +13,7 @@ {% endblock %} {% block link %} {{ block.super }} - + {% css "main/css/table.css" %} {% endblock %} {% block body %}

    diff --git a/nummi/main/templates/main/login.html b/nummi/main/templates/main/login.html index ab579cb..83258a1 100644 --- a/nummi/main/templates/main/login.html +++ b/nummi/main/templates/main/login.html @@ -3,12 +3,8 @@ {% load i18n %} {% block link %} {{ block.super }} - - + {% css "main/css/table.css" %} + {% css "main/css/form.css" %} {% endblock %} {% block body %}

    {% translate "Log in" %}

    diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 6b57178..a85d8db 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -1,4 +1,5 @@ from django import template +from django.templatetags.static import static from django.utils import formats from django.utils.safestring import mark_safe @@ -59,3 +60,10 @@ def verbose_name(obj): @register.filter def adding(obj): return obj._state.adding + + +@register.simple_tag +def css(href): + return mark_safe( + f"""""" + ) diff --git a/nummi/search/templates/search/search.html b/nummi/search/templates/search/search.html index 33e5ba1..18ecdc8 100644 --- a/nummi/search/templates/search/search.html +++ b/nummi/search/templates/search/search.html @@ -7,12 +7,8 @@ {% endblock %} {% block link %} {{ block.super }} - - + {% css "main/css/form.css" %} + {% css "main/css/table.css" %} {% endblock %} {% block body %}

    {% translate "Search" %}

    diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index 3f5dc1b..0b72f35 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -2,9 +2,7 @@ {% load i18n static category %} {% block link %} {{ block.super }} - + {% css "main/css/plot.css" %} {% endblock %} {% block h2 %}{{ month|date:"F Y"|capfirst }}{% endblock %} {% block table %} From fe95222474aef8724e7afbe9c1dd486d9c330bd9 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 30 Dec 2023 11:12:38 +0100 Subject: [PATCH 119/276] Fix bugs in html templates --- .../templates/category/category_plot.html | 8 +++- nummi/main/templates/main/form/base.html | 2 +- nummi/main/templates/main/form/form_base.html | 6 +-- nummi/main/templates/main/form/login.html | 6 +-- nummi/main/templates/main/login.html | 1 + .../templates/transaction/invoice_table.html | 40 ++++++++++--------- .../transaction_archive_month.html | 2 +- 7 files changed, 36 insertions(+), 29 deletions(-) diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index 91d919e..ca855a0 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -34,7 +34,9 @@ {{ cat.sum_m|pmrvalue }} -
    + {% if cat.sum_m %} +
    + {% endif %} {% if cat.sum < 0 %}
    {{ cat.sum|pmrvalue }} @@ -42,7 +44,9 @@ {% endif %} -
    + {% if cat.sum_p %} +
    + {% endif %} {% if cat.sum > 0 %}
    {{ cat.sum|pmrvalue }} diff --git a/nummi/main/templates/main/form/base.html b/nummi/main/templates/main/form/base.html index e2b273e..fc9a4e3 100644 --- a/nummi/main/templates/main/form/base.html +++ b/nummi/main/templates/main/form/base.html @@ -30,7 +30,7 @@ {% block pre %}{% endblock %} {% csrf_token %} - {% if instance|adding %}{% endif %} + {% if instance|adding %}{% endif %} {{ form }} {% block tables %}{% endblock %} diff --git a/nummi/main/templates/main/form/form_base.html b/nummi/main/templates/main/form/form_base.html index 7fb6952..4d61bf7 100644 --- a/nummi/main/templates/main/form/form_base.html +++ b/nummi/main/templates/main/form/form_base.html @@ -29,11 +29,11 @@ {% if not form.instance|adding %} {% translate "Delete" %} {% endif %} - + {% if form.instance|adding %} - + {% else %} - + {% endif %} {% endblock %} diff --git a/nummi/main/templates/main/form/login.html b/nummi/main/templates/main/form/login.html index 8a74bc7..38e1726 100644 --- a/nummi/main/templates/main/form/login.html +++ b/nummi/main/templates/main/form/login.html @@ -1,7 +1,7 @@ {% extends "main/form/form_base.html" %} {% load i18n %} {% block buttons %} - - - + + + {% endblock %} diff --git a/nummi/main/templates/main/login.html b/nummi/main/templates/main/login.html index 83258a1..0182a8c 100644 --- a/nummi/main/templates/main/login.html +++ b/nummi/main/templates/main/login.html @@ -1,4 +1,5 @@ {% extends "main/base.html" %} +{% load main_extras %} {% load static %} {% load i18n %} {% block link %} diff --git a/nummi/transaction/templates/transaction/invoice_table.html b/nummi/transaction/templates/transaction/invoice_table.html index 82a4406..444509c 100644 --- a/nummi/transaction/templates/transaction/invoice_table.html +++ b/nummi/transaction/templates/transaction/invoice_table.html @@ -6,9 +6,11 @@ - {% translate "Name" %} - {% translate "File" %} - {% translate "Delete" %} + + {% translate "Name" %} + {% translate "File" %} + {% translate "Delete" %} + {% for invoice in transaction.invoices %} @@ -20,21 +22,21 @@ {% translate "File" %} [{{ invoice.file|extension }}] - {% translate "Delete" %} - - - {% empty %} - - {% translate "No invoice" %} - - {% endfor %} - - - - - {% translate "Create invoice" %} + {% translate "Delete" %} - - -
    + {% empty %} + + {% translate "No invoice" %} + + {% endfor %} + + + + + {% translate "Create invoice" %} + + + + +
    diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index 0b72f35..3b71c4e 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -1,5 +1,5 @@ {% extends "transaction/transaction_list.html" %} -{% load i18n static category %} +{% load i18n main_extras static category %} {% block link %} {{ block.super }} {% css "main/css/plot.css" %} From ee043d3956c6cd882a834df6767ca945b1203a1f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 31 Dec 2023 09:03:49 +0100 Subject: [PATCH 120/276] Fix back link in transation form (fix #12) --- nummi/transaction/templates/transaction/transaction_form.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nummi/transaction/templates/transaction/transaction_form.html b/nummi/transaction/templates/transaction/transaction_form.html index 8674742..5b40a0e 100644 --- a/nummi/transaction/templates/transaction/transaction_form.html +++ b/nummi/transaction/templates/transaction/transaction_form.html @@ -8,9 +8,9 @@ {% translate "New transaction" %} {% endblock %} {% block pre %} - {% if snapshot %} + {% if statement %}

    - {{ snapshot }} + {{ statement }}

    {% endif %} {% endblock %} From e4e0f56be045af8afd66be037ab1f7e199418ff4 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 31 Dec 2023 09:37:29 +0100 Subject: [PATCH 121/276] Use css nesting --- nummi/main/static/main/css/form.css | 106 ++++++++------- nummi/main/static/main/css/main.css | 132 ++++++++++--------- nummi/main/static/main/css/plot.css | 185 ++++++++++++--------------- nummi/main/static/main/css/table.css | 74 ++++++----- 4 files changed, 249 insertions(+), 248 deletions(-) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 3f792b5..1ee72a6 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -5,56 +5,64 @@ form ul.errorlist { margin: 0; } -form > table > tbody > tr > th { - background: var(--bg-01); - background-clip: padding-box; +form { + > table > tbody > tr > th { + background: var(--bg-01); + background-clip: padding-box; + } + tbody :is(input, select, textarea) { + font: inherit; + border: none; + background: transparent; + width: 100%; + height: 100%; + line-height: 1.5; + } + input[type="checkbox"] { + width: initial; + } + tfoot { + text-align: right; + } } -form tbody :is(input, select, textarea) { - font: inherit; - border: none; - background: transparent; - width: 100%; - height: 100%; - line-height: 1.5; -} -form input[type="checkbox"] { - width: initial; -} -table.file-input tr { - border: none; -} -table.file-input th { - text-align: left; -} -table.file-input tr :first-child { - padding-left: 0; -} -table.file-input tr :last-child { - padding-right: 0; +table.file-input { + tr { + border: none; + + :first-child { + padding-left: 0; + } + :last-child { + padding-right: 0; + } + } + th { + text-align: left; + } } -form tfoot { - text-align: right; -} -.buttons input { - font: inherit; - line-height: 1.5; - margin-left: var(--gap); - border-radius: var(--radius); - padding: 0 var(--gap); - cursor: pointer; -} -.buttons input:hover { - text-decoration: underline; -} -.buttons input[type="submit"] { - border: 0.1rem solid var(--green); - background: var(--green-1); -} -.buttons input[type="reset"] { - border: 0.1rem solid var(--red); - background: var(--red-1); -} -.buttons a.del { - color: var(--red); +.buttons { + input { + font: inherit; + line-height: 1.5; + margin-left: var(--gap); + border-radius: var(--radius); + padding: 0 var(--gap); + cursor: pointer; + + &: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); + } + } + a.del { + color: var(--red); + } } diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index de17985..4ec7b8e 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -60,9 +60,17 @@ a { color: var(--text-link); text-decoration: none; display: inline-block; -} -a:is(:hover, :focus) { - text-decoration: underline; + + &:is(:hover, :focus) { + text-decoration: underline; + } + + &.big-link { + margin-right: 1em; + } + &.big-link [class^="ri-"] { + margin-right: 0.5em; + } } .red { @@ -83,6 +91,9 @@ main { grid-column: 2; grid-row: 1; overflow-x: hidden; + h2.new { + opacity: 0.8; + } } nav { grid-column: 1; @@ -95,32 +106,34 @@ nav { background: var(--bg-01); line-height: 2rem; -} -nav h1 img { - height: 1cap; -} -nav ul { - list-style: none; - padding: 0; - margin: 0; - position: relative; -} -nav .skip-link { - font-weight: 300; -} -nav .skip-link:is(:active, :focus) { - font-weight: 500; -} -nav a { - display: block; -} -nav a.cur { - font-weight: 550; -} -nav a.cur::after { - content: "◎"; - position: absolute; - right: 0; + + h1 img { + height: 1cap; + } + ul { + list-style: none; + padding: 0; + margin: 0; + position: relative; + } + a { + &.skip-link { + font-weight: 300; + + &:is(:active, :focus) { + font-weight: 500; + } + } + display: block; + &.cur { + font-weight: 550; + &::after { + content: "◎"; + position: absolute; + right: 0; + } + } + } } :is(nav, main) > :first-child { margin-top: 0; @@ -134,17 +147,19 @@ footer { .pagination { text-align: center; font-feature-settings: var(--num); -} -.pagination a { - min-width: 1rem; - padding: 0 0.5rem; -} -.pagination a.cur { - font-weight: 650; - text-decoration: underline dotted; -} -.pagination a.cur:is(:hover, :focus) { - text-decoration: underline; + a { + min-width: 1rem; + padding: 0 0.5rem; + + &.cur { + font-weight: 650; + text-decoration: underline dotted; + + &:is(:hover, :focus) { + text-decoration: underline; + } + } + } } @media (width < 1024px) { @@ -160,13 +175,6 @@ footer { height: initial; } } - -a.big-link { - margin-right: 1em; -} -a.big-link [class^="ri-"] { - margin-right: 0.5em; -} [class^="ri-"] { font-weight: normal; } @@ -188,9 +196,6 @@ h2 { h3 { font-size: 1.5rem; } -main h2.new { - opacity: 0.8; -} p { margin: 0.5em 0; } @@ -201,18 +206,19 @@ ul.messages { margin: 0; background: var(--bg-01); padding: 0; -} -ul.messages li { - padding: calc(var(--gap) / 2) var(--gap); - border-left: var(--border) solid var(--gray); -} -ul.messages li.msg-level-25 { - border-left-color: var(--green-1); -} -ul.messages li.msg-level-30 { - border-left-color: var(--red-1); -} -ul.messages li.msg-level-40 { - border-left-color: var(--red); + li { + padding: calc(var(--gap) / 2) var(--gap); + border-left: var(--border) solid var(--gray); + + &.msg-level-25 { + border-left-color: var(--green-1); + } + &.msg-level-30 { + border-left-color: var(--red-1); + } + &.msg-level-40 { + border-left-color: var(--red); + } + } } diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 1f025d2..631d3c4 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -1,116 +1,99 @@ table.full-width col.bar { width: auto; } + .plot { overflow-x: auto; -} -.plot td.bar { - position: relative; - padding: 0; -} -.plot td.bar div { - position: absolute; - height: 0.5rem; - top: 0; -} + td.bar { + position: relative; + padding: 0; + @media (width < 720px) { + width: 0; + overflow: hidden; + } -.plot td.bar div:not(.tot) { - width: 0; - box-sizing: border-box; - z-index: 1; - display: inline-block; -} -.plot td.bar.p div { - left: 0; - border-radius: 0 var(--radius) var(--radius) 0; -} -.plot td.bar.m div { - right: 0; - border-radius: var(--radius) 0 0 var(--radius); -} -.plot td.bar.m div { - background: var(--red-1); -} -.plot td.bar.p div { - background: var(--green-1); -} + div { + position: absolute; + height: 0.5rem; + top: 0; + &:not(.tot) { + width: 0; + box-sizing: border-box; + z-index: 1; + display: inline-block; + } + &.tot { + z-index: 10; + height: 0.5rem; -.plot td.bar div.tot { - z-index: 10; - height: 0.5rem; -} -.plot td.bar.m div.tot { - background: var(--red); -} -.plot td.bar.p div.tot { - background: var(--green); -} -.plot td.bar div.tot span { - position: absolute; - display: inline-block; - white-space: nowrap; - margin: 0 var(--gap); - font-weight: 650; - top: 0.5rem; - line-height: 1.5rem; - height: 1.5rem; - font-feature-settings: var(--num); -} -.plot td.bar.p div.tot span { - left: 0; -} -.plot td.bar.m div.tot span { - right: 0; -} -.plot tr.empty { - height: 0.5rem; -} -.plot tr.even { - background: #eeeeff; -} + span { + position: absolute; + display: inline-block; + white-space: nowrap; + margin: 0 var(--gap); + font-weight: 650; + top: 0.5rem; + line-height: 1.5rem; + height: 1.5rem; + font-feature-settings: var(--num); + } + } + } + &.p div { + left: 0; + border-radius: 0 var(--radius) var(--radius) 0; + background: var(--green-1); + &.tot { + background: var(--green); -@media (width < 720px) { - .plot .bar { - width: 0; - overflow: hidden; + span { + left: 0; + } + } + } + &.m div { + right: 0; + border-radius: var(--radius) 0 0 var(--radius); + background: var(--red-1); + &.tot { + background: var(--red); + + span { + right: 0; + } + } + } + } + tr.empty { + height: 0.5rem; + } + tr.even { + background: #eeeeff; + } + tfoot { + background: var(--bg-01); } } -.plot tfoot { - background: var(--bg-01); -} - -.calendar tbody tr { - background: initial; -} .calendar { margin-top: var(--gap); -} -.calendar .p { - background: var(--green); -} -.calendar .o-0 { - opacity: 0.1; -} -.calendar .o-1 { - opacity: 0.5; -} -.calendar .o-2 { - opacity: 0.75; -} -.calendar .o-3 { - opacity: 0.9; -} -.calendar .o-4 { - opacity: 1; -} -.calendar .m { - background: var(--red); -} -.calendar tbody tr:not(:last-child) { - border-bottom: none; -} -.calendar tbody tr:not(:first-child) { - border-top: none; + + tbody tr { + background: initial; + } + .p { + background: var(--green); + } + .m { + background: var(--red); + } + tbody tr { + &:not(:last-child) { + border-bottom: none; + } + &:not(:first-child) { + border-top: none; + } + } } diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index 133fb68..5116345 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -4,37 +4,46 @@ form { } table { border-collapse: collapse; + + &.more tbody:last-child tr:last-child { + border-bottom: 1px dashed var(--gray); + } + &.full-width { + width: 100%; + + col { + width: 8rem; + } + } + col.icon { + width: 1ch; + } + thead { + background: var(--bg-01); + } + tr { + border: 1px solid var(--gray); + height: 2rem; + line-height: 2rem; + + tbody &:nth-of-type(even) { + background: #eeeeff; + } + } + td, + th { + padding: 0 var(--gap); + position: relative; + white-space: nowrap; + text-overflow: ellipsis; + &.empty { + text-align: center; + opacity: 0.8; + font-weight: 300; + } + } } -table.more tbody:last-child tr:last-child { - border-bottom: 1px dashed var(--gray); -} -table.full-width { - width: 100%; -} -thead { - background: var(--bg-01); -} -table.full-width col { - width: 8rem; -} -table col.icon { - width: 1ch; -} -tr { - border: 1px solid var(--gray); - height: 2rem; - line-height: 2rem; -} -.table tbody tr:nth-of-type(even) { - background: #eeeeff; -} -td, -th { - padding: 0 var(--gap); - position: relative; - white-space: nowrap; - text-overflow: ellipsis; -} + .date, .value { font-feature-settings: var(--num); @@ -50,8 +59,3 @@ th { .date { text-align: center; } -td.empty { - text-align: center; - opacity: 0.8; - font-weight: 300; -} From 6b50de5e356ebdee7748bdacfe7aca4c361b8b1f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 31 Dec 2023 18:41:54 +0100 Subject: [PATCH 122/276] Fix opacity calculation on chrome --- nummi/history/templates/history/plot.html | 3 +-- nummi/main/templatetags/main_extras.py | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 1f0d858..399bc4e 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -102,8 +102,7 @@ {% endif %} {% if m %} - + style="opacity: {% opacity m.sum history.years_max %}"> {% else %} {% endif %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index a85d8db..670bda9 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -1,3 +1,5 @@ +import math + from django import template from django.templatetags.static import static from django.utils import formats @@ -67,3 +69,8 @@ def css(href): return mark_safe( f"""""" ) + + +@register.simple_tag +def opacity(v, vmax): + return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.2f}" From 0940904cd8dd651bec8b9acde927a79197e18c7e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 10:58:30 +0100 Subject: [PATCH 123/276] Refactor History plot --- nummi/history/templates/history/plot.html | 94 ++++++----------------- nummi/history/utils.py | 38 ++++----- nummi/main/static/main/css/plot.css | 34 ++++---- nummi/main/templatetags/main_extras.py | 69 ++++++++++++++++- 4 files changed, 130 insertions(+), 105 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 399bc4e..8d9bad4 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -18,57 +18,23 @@ - {% for y in history.years reversed %} - {% for date in y.d reversed %} - {% ifchanged %} - {% if date %} - - - - - - {% if date.has_transactions %} - {% if account %} - {{ date.month|date:"Y-m" }} - {% elif category %} - {{ date.month|date:"Y-m" }} - {% else %} - {{ date.month|date:"Y-m" }} - {% endif %} - {% else %} - {{ date.month|date:"Y-m" }} - {% endif %} - - {{ date.sum_m|pmrvalue }} - - {% if date.sum_m %} -
    - {% endif %} - {% if date.sum < 0 %} -
    - {{ date.sum|pmrvalue }} -
    - {% endif %} - - - {% if date.sum_p %} -
    - {% endif %} - {% if date.sum > 0 %} -
    - {{ date.sum|pmrvalue }} -
    - {% endif %} - - {{ date.sum_p|pmrvalue }} - - {% else %} - - - - {% endif %} - {% endifchanged %} - {% endfor %} + {% for date in history.data reversed %} + {% ifchanged %} + {% if date.sum_m or date.sum_p %} + + {% up_down_icon date.sum %} + {% month_url date.month account=account category=category %} + {{ date.sum_m|pmrvalue }} + {% plot_bar date.sum date.sum_m history.max.pm %} + {% plot_bar date.sum date.sum_p history.max.pm %} + {{ date.sum_p|pmrvalue }} + + {% else %} + + + + {% endif %} + {% endifchanged %} {% endfor %} @@ -78,36 +44,26 @@ {% translate "Year" %} - 01 - 02 - 03 - 04 - 05 - 06 - 07 - 08 - 09 - 10 - 11 - 12 + {% calendar_head %} - {% for year in history.years reversed %} + {% regroup history.data by month.year as years_list %} + {% for y, year in years_list reversed %} - {{ year.y }} - {% for m in year.d %} + {{ y }} + {% for m in year %} {% if forloop.parentloop.last and forloop.first %} - {% for _ in history.offset.0 %}{% endfor %} + {% empty_calendar_cells_start m.month.month %} {% endif %} {% if m %} + style="opacity: {% opacity m.sum history.max.sum %}"> {% else %} {% endif %} {% if forloop.parentloop.first and forloop.last %} - {% for _ in history.offset.1 %}{% endfor %} + {% empty_calendar_cells_end m.month.month %} {% endif %} {% endfor %} diff --git a/nummi/history/utils.py b/nummi/history/utils.py index b15e792..423d34e 100644 --- a/nummi/history/utils.py +++ b/nummi/history/utils.py @@ -1,6 +1,6 @@ import datetime -from django.db.models import Func, Max, Min, Q, Sum, Value +from django.db.models import Func, Max, Min, Q, Sum from django.db.models.functions import Abs, TruncMonth @@ -23,34 +23,30 @@ def history(transaction_set): sum_p=Sum("value", filter=Q(value__gt=0)), sum_m=Sum("value", filter=Q(value__lt=0)), sum=Sum("value"), - has_transactions=Value(1), ).order_by("-month") _data = [ - { - "y": y, - "d": [ - _history.filter(month=datetime.date(y, m + 1, 1)).first() - for m in range( - _first_month.month - 1 if _first_month.year == y else 0, - _last_month.month if _last_month.year == y else 12, - ) - ], - } + _history.filter(month=datetime.date(y, m + 1, 1)).first() + or {"month": datetime.date(y, m + 1, 1), "sum": 0} for y in range( _first_month.year, _last_month.year + 1, ) + for m in range( + _first_month.month - 1 if _first_month.year == y else 0, + _last_month.month if _last_month.year == y else 12, + ) ] return { - "years": _data, - "offset": [range(_first_month.month - 1), range(12 - _last_month.month)], - "max": max( - _history.aggregate( - max=Max("sum_p", default=0), - min=-Min("sum_m", default=0), - ).values(), - ), - "years_max": _history.aggregate(max=Max(Abs("sum")))["max"], + "data": _data, + "max": { + "pm": max( + _history.aggregate( + max=Max("sum_p", default=0), + min=-Min("sum_m", default=0), + ).values(), + ), + "sum": _history.aggregate(max=Max(Abs("sum")))["max"], + }, } diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 631d3c4..0ec7189 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -65,12 +65,17 @@ table.full-width col.bar { } } } - tr.empty { - height: 0.5rem; - } - tr.even { - background: #eeeeff; + + tbody tr { + background: initial; + &.empty { + height: 0.5rem; + } + &.even { + background: #eeeeff; + } } + tfoot { background: var(--bg-01); } @@ -78,22 +83,23 @@ table.full-width col.bar { .calendar { margin-top: var(--gap); + font-feature-settings: var(--num); - tbody tr { - background: initial; - } .p { background: var(--green); } .m { background: var(--red); } - tbody tr { - &:not(:last-child) { - border-bottom: none; - } - &:not(:first-child) { - border-top: none; + table { + tbody tr { + background: initial; + &:not(:last-child) { + border-bottom: none; + } + &:not(:first-child) { + border-top: none; + } } } } diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 670bda9..8fca38e 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -1,7 +1,10 @@ +import datetime import math from django import template +from django.template.defaultfilters import date from django.templatetags.static import static +from django.urls import reverse from django.utils import formats from django.utils.safestring import mark_safe @@ -73,4 +76,68 @@ def css(href): @register.simple_tag def opacity(v, vmax): - return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.2f}" + return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.3f}" + + +@register.simple_tag +def empty_calendar_cells(n): + return mark_safe(n * "") + + +@register.simple_tag +def empty_calendar_cells_start(n): + return empty_calendar_cells(n - 1) + + +@register.simple_tag +def empty_calendar_cells_end(n): + return empty_calendar_cells(12 - n) + + +@register.simple_tag +def up_down_icon(val): + if val > 0: + return mark_safe("""""") + elif val < 0: + return mark_safe("""""") + + +@register.simple_tag +def month_url(month, account=None, category=None): + url_name = "transaction_month" + url_params = {"year": month.year, "month": month.month} + + if account: + url_name = "account_" + url_name + url_params |= {"account": account.pk} + elif category: + url_name = "category_" + url_name + url_params |= {"category": category.pk} + + url = reverse(url_name, kwargs=url_params) + return mark_safe(f"""{ date(month, "Y-m") }""") + + +@register.simple_tag +def plot_bar(s, sum_pm, s_max): + _res = "" + + if sum_pm: + _w = abs(sum_pm / s_max) + _res += f"""
    """ + if sum_pm is not None and s * sum_pm > 0: + _w = abs(s / s_max) + _res += ( + f"""
    """ + f"""{pmrvalue(s)}
    """ + ) + + return mark_safe(_res) + + +@register.simple_tag +def calendar_head(): + months = (datetime.date(1, m + 1, 1) for m in range(12)) + th = (f"""{date(month, "m")}""" for month in months) + + return mark_safe("".join(th)) From 003902b43f9323c9a37f1867af0dc013f5b6e60d Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 11:00:56 +0100 Subject: [PATCH 124/276] Minor style updates --- nummi/main/static/main/css/main.css | 4 +++- nummi/main/static/main/css/table.css | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 4ec7b8e..62a7a6b 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -39,7 +39,8 @@ --border: 0.5em; --radius: 0.25em; - --num: "tnum", "ss01", "ss02", "case"; + --default-ffs: "dlig", "ss01", "ss04"; + --num: var(--default-ffs), "tnum", "case"; } body { @@ -51,6 +52,7 @@ body { display: grid; grid-template-columns: max-content 1fr; + font-feature-settings: var(--default-ffs); } p { diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index 5116345..0092937 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -26,7 +26,7 @@ table { height: 2rem; line-height: 2rem; - tbody &:nth-of-type(even) { + tbody &:where(:nth-of-type(even)) { background: #eeeeff; } } From 45b47dd1bac80335fece6ebfe34d41f0843ebadd Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 11:11:05 +0100 Subject: [PATCH 125/276] Move templatetags to history templatetags --- nummi/history/templates/history/plot.html | 3 +- nummi/history/templatetags/history_extras.py | 78 ++++++++++++++++++++ nummi/main/templatetags/main_extras.py | 74 ------------------- 3 files changed, 80 insertions(+), 75 deletions(-) create mode 100644 nummi/history/templatetags/history_extras.py diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 8d9bad4..49a52f2 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -1,4 +1,5 @@ {% load main_extras %} +{% load history_extras %} {% load i18n %}
    @@ -58,7 +59,7 @@ {% endif %} {% if m %} + style="opacity: {% calendar_opacity m.sum history.max.sum %}"> {% else %} {% endif %} diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py new file mode 100644 index 0000000..1efb7ec --- /dev/null +++ b/nummi/history/templatetags/history_extras.py @@ -0,0 +1,78 @@ +import math + +from django import template +from django.template.defaultfilters import date +from django.urls import reverse +from django.utils.safestring import mark_safe +from main.templatetags.main_extras import pmrvalue, remix + +register = template.Library() + + +@register.simple_tag +def calendar_opacity(v, vmax): + return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.3f}" + + +@register.simple_tag +def empty_calendar_cells(n): + return mark_safe(n * "") + + +@register.simple_tag +def empty_calendar_cells_start(n): + return empty_calendar_cells(n - 1) + + +@register.simple_tag +def empty_calendar_cells_end(n): + return empty_calendar_cells(12 - n) + + +@register.simple_tag +def up_down_icon(val): + if val > 0: + return remix("arrow-up-s", "green") + elif val < 0: + return remix("arrow-down-s", "red") + + +@register.simple_tag +def month_url(month, account=None, category=None): + url_name = "transaction_month" + url_params = {"year": month.year, "month": month.month} + + if account: + url_name = "account_" + url_name + url_params |= {"account": account.pk} + elif category: + url_name = "category_" + url_name + url_params |= {"category": category.pk} + + url = reverse(url_name, kwargs=url_params) + return mark_safe(f"""{ date(month, "Y-m") }""") + + +@register.simple_tag +def plot_bar(s, sum_pm, s_max): + _res = "" + + if sum_pm: + _w = abs(sum_pm / s_max) + _res += f"""
    """ + if sum_pm is not None and s * sum_pm > 0: + _w = abs(s / s_max) + _res += ( + f"""
    """ + f"""{pmrvalue(s)}
    """ + ) + + return mark_safe(_res) + + +@register.simple_tag +def calendar_head(): + months = range(1, 13) + th = (f"""""" for month in months) + + return mark_safe("".join(th)) diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 8fca38e..a85d8db 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -1,10 +1,5 @@ -import datetime -import math - from django import template -from django.template.defaultfilters import date from django.templatetags.static import static -from django.urls import reverse from django.utils import formats from django.utils.safestring import mark_safe @@ -72,72 +67,3 @@ def css(href): return mark_safe( f"""""" ) - - -@register.simple_tag -def opacity(v, vmax): - return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.3f}" - - -@register.simple_tag -def empty_calendar_cells(n): - return mark_safe(n * "") - - -@register.simple_tag -def empty_calendar_cells_start(n): - return empty_calendar_cells(n - 1) - - -@register.simple_tag -def empty_calendar_cells_end(n): - return empty_calendar_cells(12 - n) - - -@register.simple_tag -def up_down_icon(val): - if val > 0: - return mark_safe("""""") - elif val < 0: - return mark_safe("""""") - - -@register.simple_tag -def month_url(month, account=None, category=None): - url_name = "transaction_month" - url_params = {"year": month.year, "month": month.month} - - if account: - url_name = "account_" + url_name - url_params |= {"account": account.pk} - elif category: - url_name = "category_" + url_name - url_params |= {"category": category.pk} - - url = reverse(url_name, kwargs=url_params) - return mark_safe(f"""{ date(month, "Y-m") }""") - - -@register.simple_tag -def plot_bar(s, sum_pm, s_max): - _res = "" - - if sum_pm: - _w = abs(sum_pm / s_max) - _res += f"""
    """ - if sum_pm is not None and s * sum_pm > 0: - _w = abs(s / s_max) - _res += ( - f"""
    """ - f"""{pmrvalue(s)}
    """ - ) - - return mark_safe(_res) - - -@register.simple_tag -def calendar_head(): - months = (datetime.date(1, m + 1, 1) for m in range(12)) - th = (f"""""" for month in months) - - return mark_safe("".join(th)) From caa113859d24df4bbf8c10692977e531c7e48c49 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 11:45:06 +0100 Subject: [PATCH 126/276] Add year archive view --- .../transaction/transaction_archive_year.html | 23 +++++++++++ nummi/transaction/urls.py | 5 +++ nummi/transaction/views.py | 39 ++++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 nummi/transaction/templates/transaction/transaction_archive_year.html diff --git a/nummi/transaction/templates/transaction/transaction_archive_year.html b/nummi/transaction/templates/transaction/transaction_archive_year.html new file mode 100644 index 0000000..2f0cf71 --- /dev/null +++ b/nummi/transaction/templates/transaction/transaction_archive_year.html @@ -0,0 +1,23 @@ +{% extends "transaction/transaction_list.html" %} +{% load i18n main_extras static category %} +{% block link %} + {{ block.super }} + {% css "main/css/plot.css" %} +{% endblock %} +{% block h2 %}{{ year|date:"Y" }}{% endblock %} +{% block table %} + {% if history %} +
    +

    + {% translate "History" %} +

    + {% include "history/plot.html" %} +
    + {% endif %} + {% if not category %} +

    {% translate "Categories" %}

    + {% category_plot transactions month=month %} + {% endif %} +

    {% translate "Transactions" %}

    + {{ block.super }} +{% endblock %} diff --git a/nummi/transaction/urls.py b/nummi/transaction/urls.py index cba5e2e..7c62eb8 100644 --- a/nummi/transaction/urls.py +++ b/nummi/transaction/urls.py @@ -4,6 +4,11 @@ from . import views urlpatterns = [ path("list", views.TransactionListView.as_view(), name="transactions"), + path( + "history/", + views.TransactionYearView.as_view(), + name="transaction_year", + ), path( "history//", views.TransactionMonthView.as_view(), diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index f7464cd..fb1a900 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -2,7 +2,8 @@ from account.models import Account from category.models import Category from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy -from django.views.generic.dates import MonthArchiveView +from django.views.generic.dates import MonthArchiveView, YearArchiveView +from history.utils import history from main.views import ( NummiCreateView, NummiDeleteView, @@ -138,3 +139,39 @@ class TransactionMonthView(UserMixin, MonthArchiveView): if "account" in self.kwargs: return context_data | {"account": self.account} return context_data + + +class TransactionYearView(UserMixin, YearArchiveView): + model = Transaction + date_field = "date" + context_object_name = "transactions" + make_object_list = True + + def get_queryset(self): + if "account" in self.kwargs: + self.account = get_object_or_404( + Account.objects.filter(user=self.request.user), + pk=self.kwargs["account"], + ) + return super().get_queryset().filter(account=self.account) + if "category" in self.kwargs: + self.category = get_object_or_404( + Category.objects.filter(user=self.request.user), + pk=self.kwargs["category"], + ) + return super().get_queryset().filter(category=self.category) + + return super().get_queryset() + + def get_context_data(self, **kwargs): + context_data = super().get_context_data(**kwargs) + context_data |= { + "history": history( + context_data["transactions"].exclude(category__budget=False) + ), + } + if "category" in self.kwargs: + return context_data | {"category": self.category} + if "account" in self.kwargs: + return context_data | {"account": self.account} + return context_data From 3dbc31eebccc343a22f609405362d8d050a1b5a2 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 11:47:58 +0100 Subject: [PATCH 127/276] Fix row colors in category plot --- nummi/main/static/main/css/plot.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 0ec7189..4f7e7fa 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -66,8 +66,10 @@ table.full-width col.bar { } } - tbody tr { + .history & tbody tr { background: initial; + } + tbody tr { &.empty { height: 0.5rem; } From 75df57f42a62cd4cad477e447218f27e37292506 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 11:52:04 +0100 Subject: [PATCH 128/276] Update year view (add link) --- nummi/history/templates/history/plot.html | 4 +++- .../templates/transaction/transaction_archive_year.html | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 49a52f2..f8d041c 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -52,7 +52,9 @@ {% regroup history.data by month.year as years_list %} {% for y, year in years_list reversed %}
    - + {% for m in year %} {% if forloop.parentloop.last and forloop.first %} {% empty_calendar_cells_start m.month.month %} diff --git a/nummi/transaction/templates/transaction/transaction_archive_year.html b/nummi/transaction/templates/transaction/transaction_archive_year.html index 2f0cf71..671f13c 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_year.html +++ b/nummi/transaction/templates/transaction/transaction_archive_year.html @@ -1,9 +1,10 @@ -{% extends "transaction/transaction_list.html" %} +{% extends "main/list.html" %} {% load i18n main_extras static category %} {% block link %} {{ block.super }} {% css "main/css/plot.css" %} {% endblock %} +{% block name %}{{ year|date:"Y" }}{% endblock %} {% block h2 %}{{ year|date:"Y" }}{% endblock %} {% block table %} {% if history %} @@ -18,6 +19,4 @@

    {% translate "Categories" %}

    {% category_plot transactions month=month %} {% endif %} -

    {% translate "Transactions" %}

    - {{ block.super }} {% endblock %} From 309281f5e10496d0b772b8ce742741006cc0b939 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 11:55:54 +0100 Subject: [PATCH 129/276] Fix row colors in history plot --- nummi/history/templates/history/plot.html | 2 +- nummi/main/static/main/css/plot.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index f8d041c..6231995 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -1,7 +1,7 @@ {% load main_extras %} {% load history_extras %} {% load i18n %} -
    +
    {month:02d}{date(month, "m")}
    {{ y }} + {{ y }} +
    diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 4f7e7fa..c568d3d 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -66,7 +66,7 @@ table.full-width col.bar { } } - .history & tbody tr { + &.history tbody tr { background: initial; } tbody tr { From 8b9af8e1a487fe5187b2fada243656e9e2eda4b0 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 11:59:44 +0100 Subject: [PATCH 130/276] Add links to category in year category plot --- nummi/category/templates/category/category_plot.html | 2 ++ nummi/category/urls.py | 7 ++++++- .../templates/transaction/transaction_archive_year.html | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index ca855a0..63577af 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -24,6 +24,8 @@ {% if cat.category %} {% if month %} {{ cat.category__name }} + {% elif year %} + {{ cat.category__name }} {% else %} {{ cat.category__name }} {% endif %} diff --git a/nummi/category/urls.py b/nummi/category/urls.py index 5bdc9c1..64b8ae9 100644 --- a/nummi/category/urls.py +++ b/nummi/category/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from transaction.views import TransactionMonthView +from transaction.views import TransactionMonthView, TransactionYearView from . import views @@ -12,6 +12,11 @@ urlpatterns = [ name="category_transactions", ), path("/delete", views.CategoryDeleteView.as_view(), name="del_category"), + path( + "/history/", + TransactionYearView.as_view(), + name="category_transaction_year", + ), path( "/history//", TransactionMonthView.as_view(), diff --git a/nummi/transaction/templates/transaction/transaction_archive_year.html b/nummi/transaction/templates/transaction/transaction_archive_year.html index 671f13c..e602fc7 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_year.html +++ b/nummi/transaction/templates/transaction/transaction_archive_year.html @@ -17,6 +17,6 @@ {% endif %} {% if not category %}

    {% translate "Categories" %}

    - {% category_plot transactions month=month %} + {% category_plot transactions year=year %} {% endif %} {% endblock %} From 60f84fe20df2a29bf63ae828d78c9751f69399fd Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 12:06:20 +0100 Subject: [PATCH 131/276] Add pagination to year archive view --- nummi/main/templates/main/pagination.html | 13 +++++++++++++ nummi/main/templates/main/pagination_month.html | 2 +- nummi/main/templates/main/pagination_year.html | 5 +++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 nummi/main/templates/main/pagination_year.html diff --git a/nummi/main/templates/main/pagination.html b/nummi/main/templates/main/pagination.html index 7147be0..7f32ea2 100644 --- a/nummi/main/templates/main/pagination.html +++ b/nummi/main/templates/main/pagination.html @@ -20,3 +20,16 @@ {% endwith %}

    {% endif %} +{% if year %} +

    + {% with year=previous_year %} + {% include "main/pagination_year.html" %} + {% endwith %} + {% with cur=True %} + {% include "main/pagination_year.html" %} + {% endwith %} + {% with year=next_year %} + {% include "main/pagination_year.html" %} + {% endwith %} +

    +{% endif %} diff --git a/nummi/main/templates/main/pagination_month.html b/nummi/main/templates/main/pagination_month.html index 207b354..288427a 100644 --- a/nummi/main/templates/main/pagination_month.html +++ b/nummi/main/templates/main/pagination_month.html @@ -1,5 +1,5 @@ {% load i18n %} {% if month %} {{ month|date:"F Y"|capfirst }} + href="{% if account %}{% url "account_transaction_month" account.id month.year month.month %}{% elif category %}{% url "category_transaction_month" category.id month.year month.month %}{% else %}{% url "transaction_month" month.year month.month %}{% endif %}">{{ month|date:"F Y"|capfirst }} {% endif %} diff --git a/nummi/main/templates/main/pagination_year.html b/nummi/main/templates/main/pagination_year.html new file mode 100644 index 0000000..5c9bd3c --- /dev/null +++ b/nummi/main/templates/main/pagination_year.html @@ -0,0 +1,5 @@ +{% load i18n %} +{% if year %} + {{ year|date:"Y" }} +{% endif %} From 2f32c2b80f279d4c2c0060d10090e22e0d950094 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 12:13:57 +0100 Subject: [PATCH 132/276] Fix year urls on account pages --- nummi/account/urls.py | 7 +++++- nummi/history/templates/history/plot.html | 4 +--- nummi/history/templatetags/history_extras.py | 24 ++++++++++++-------- nummi/history/utils.py | 11 +++++++++ 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/nummi/account/urls.py b/nummi/account/urls.py index e15f4d4..d9b8c14 100644 --- a/nummi/account/urls.py +++ b/nummi/account/urls.py @@ -1,6 +1,6 @@ from django.urls import path from statement.views import StatementCreateView -from transaction.views import TransactionMonthView +from transaction.views import TransactionMonthView, TransactionYearView from . import views @@ -27,6 +27,11 @@ urlpatterns = [ views.AccountDeleteView.as_view(), name="del_account", ), + path( + "/history/", + TransactionYearView.as_view(), + name="account_transaction_year", + ), path( "/history//", TransactionMonthView.as_view(), diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 6231995..954c3ce 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -52,9 +52,7 @@ {% regroup history.data by month.year as years_list %} {% for y, year in years_list reversed %}
    - + {% for m in year %} {% if forloop.parentloop.last and forloop.first %} {% empty_calendar_cells_start m.month.month %} diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index 1efb7ec..3991f8f 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -6,6 +6,8 @@ from django.urls import reverse from django.utils.safestring import mark_safe from main.templatetags.main_extras import pmrvalue, remix +from ..utils import ac_url + register = template.Library() @@ -38,21 +40,23 @@ def up_down_icon(val): @register.simple_tag -def month_url(month, account=None, category=None): - url_name = "transaction_month" - url_params = {"year": month.year, "month": month.month} - - if account: - url_name = "account_" + url_name - url_params |= {"account": account.pk} - elif category: - url_name = "category_" + url_name - url_params |= {"category": category.pk} +def month_url(month, **kwargs): + url_name, url_params = ac_url( + "transaction_month", {"year": month.year, "month": month.month}, **kwargs + ) url = reverse(url_name, kwargs=url_params) return mark_safe(f"""{ date(month, "Y-m") }""") +@register.simple_tag +def year_url(year, **kwargs): + url_name, url_params = ac_url("transaction_year", {"year": year}, **kwargs) + + url = reverse(url_name, kwargs=url_params) + return mark_safe(f"""{ year }""") + + @register.simple_tag def plot_bar(s, sum_pm, s_max): _res = "" diff --git a/nummi/history/utils.py b/nummi/history/utils.py index 423d34e..530a44f 100644 --- a/nummi/history/utils.py +++ b/nummi/history/utils.py @@ -50,3 +50,14 @@ def history(transaction_set): "sum": _history.aggregate(max=Max(Abs("sum")))["max"], }, } + + +def ac_url(url_name, url_params, account=None, category=None): + if account: + url_name = "account_" + url_name + url_params |= {"account": account.pk} + elif category: + url_name = "category_" + url_name + url_params |= {"category": category.pk} + + return url_name, url_params From 1137cfc1ccaa696f18dbbcc7f9f9dae3c7880e72 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 12:21:36 +0100 Subject: [PATCH 133/276] Fix top-gap in main --- nummi/main/static/main/css/main.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 62a7a6b..d48a265 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -137,7 +137,8 @@ nav { } } } -:is(nav, main) > :first-child { +:is(nav, main) > :first-child, +main > section:first-child > :first-child { margin-top: 0; } footer { From 47c4de8fbb5cd813cbfb3f3064c47d566d29c3fc Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 14:18:57 +0100 Subject: [PATCH 134/276] Fix calendar scroll --- nummi/main/static/main/css/plot.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index c568d3d..dcf1771 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -84,6 +84,8 @@ table.full-width col.bar { } .calendar { + overflow-x: auto; + margin-top: var(--gap); font-feature-settings: var(--num); From 57279b1cda6abcc6ba7a3bfc64b2993f721dd596 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 14:21:58 +0100 Subject: [PATCH 135/276] Simplify duplicated code (archive views) --- nummi/transaction/views.py | 79 +++++++++++++++----------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index fb1a900..2421c12 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -110,68 +110,51 @@ class TransactionListView(NummiListView): context_object_name = "transactions" -class TransactionMonthView(UserMixin, MonthArchiveView): +class TransactionACMixin: + model = Transaction + + def get_queryset(self): + if "account" in self.kwargs: + self.account = get_object_or_404( + Account.objects.filter(user=self.request.user), + pk=self.kwargs["account"], + ) + return super().get_queryset().filter(account=self.account) + if "category" in self.kwargs: + self.category = get_object_or_404( + Category.objects.filter(user=self.request.user), + pk=self.kwargs["category"], + ) + return super().get_queryset().filter(category=self.category) + + return super().get_queryset() + + def get_context_data(self, **kwargs): + context_data = super().get_context_data(**kwargs) + if "category" in self.kwargs: + return context_data | {"category": self.category} + if "account" in self.kwargs: + return context_data | {"account": self.account} + return context_data + + +class TransactionMonthView(UserMixin, TransactionACMixin, MonthArchiveView): model = Transaction date_field = "date" context_object_name = "transactions" month_format = "%m" - def get_queryset(self): - if "account" in self.kwargs: - self.account = get_object_or_404( - Account.objects.filter(user=self.request.user), - pk=self.kwargs["account"], - ) - return super().get_queryset().filter(account=self.account) - if "category" in self.kwargs: - self.category = get_object_or_404( - Category.objects.filter(user=self.request.user), - pk=self.kwargs["category"], - ) - return super().get_queryset().filter(category=self.category) - return super().get_queryset() - - def get_context_data(self, **kwargs): - context_data = super().get_context_data(**kwargs) - if "category" in self.kwargs: - return context_data | {"category": self.category} - if "account" in self.kwargs: - return context_data | {"account": self.account} - return context_data - - -class TransactionYearView(UserMixin, YearArchiveView): +class TransactionYearView(UserMixin, TransactionACMixin, YearArchiveView): model = Transaction date_field = "date" context_object_name = "transactions" make_object_list = True - def get_queryset(self): - if "account" in self.kwargs: - self.account = get_object_or_404( - Account.objects.filter(user=self.request.user), - pk=self.kwargs["account"], - ) - return super().get_queryset().filter(account=self.account) - if "category" in self.kwargs: - self.category = get_object_or_404( - Category.objects.filter(user=self.request.user), - pk=self.kwargs["category"], - ) - return super().get_queryset().filter(category=self.category) - - return super().get_queryset() - def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) - context_data |= { + return context_data | { "history": history( context_data["transactions"].exclude(category__budget=False) ), } - if "category" in self.kwargs: - return context_data | {"category": self.category} - if "account" in self.kwargs: - return context_data | {"account": self.account} - return context_data From 35b26f2d1032e160aa3acb5e174a85c7ca7c6e1c Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 14:35:39 +0100 Subject: [PATCH 136/276] Add year link on month view --- nummi/history/templates/history/plot.html | 1 + nummi/history/templatetags/history_extras.py | 22 ------------- nummi/history/utils.py | 18 +---------- nummi/main/templates/main/list.html | 32 ++++++++++--------- nummi/main/templates/main/pagination.html | 3 +- .../transaction_archive_month.html | 2 +- .../templatetags/transaction_extras.py | 26 +++++++++++++++ nummi/transaction/utils.py | 9 ++++++ 8 files changed, 57 insertions(+), 56 deletions(-) create mode 100644 nummi/transaction/templatetags/transaction_extras.py create mode 100644 nummi/transaction/utils.py diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 954c3ce..23a5035 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -1,5 +1,6 @@ {% load main_extras %} {% load history_extras %} +{% load transaction_extras %} {% load i18n %}
    - {{ y }} - {% year_url y account=account category=category %}
    diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index 3991f8f..48a8aaf 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -1,13 +1,9 @@ import math from django import template -from django.template.defaultfilters import date -from django.urls import reverse from django.utils.safestring import mark_safe from main.templatetags.main_extras import pmrvalue, remix -from ..utils import ac_url - register = template.Library() @@ -39,24 +35,6 @@ def up_down_icon(val): return remix("arrow-down-s", "red") -@register.simple_tag -def month_url(month, **kwargs): - url_name, url_params = ac_url( - "transaction_month", {"year": month.year, "month": month.month}, **kwargs - ) - - url = reverse(url_name, kwargs=url_params) - return mark_safe(f"""{ date(month, "Y-m") }""") - - -@register.simple_tag -def year_url(year, **kwargs): - url_name, url_params = ac_url("transaction_year", {"year": year}, **kwargs) - - url = reverse(url_name, kwargs=url_params) - return mark_safe(f"""{ year }""") - - @register.simple_tag def plot_bar(s, sum_pm, s_max): _res = "" diff --git a/nummi/history/utils.py b/nummi/history/utils.py index 530a44f..320c10a 100644 --- a/nummi/history/utils.py +++ b/nummi/history/utils.py @@ -1,14 +1,9 @@ import datetime -from django.db.models import Func, Max, Min, Q, Sum +from django.db.models import Max, Min, Q, Sum from django.db.models.functions import Abs, TruncMonth -class GenerateMonth(Func): - function = "generate_series" - template = "%(function)s(%(expressions)s, '1 month')::date" - - def history(transaction_set): if not transaction_set.exists(): return None @@ -50,14 +45,3 @@ def history(transaction_set): "sum": _history.aggregate(max=Max(Abs("sum")))["max"], }, } - - -def ac_url(url_name, url_params, account=None, category=None): - if account: - url_name = "account_" + url_name - url_params |= {"account": account.pk} - elif category: - url_name = "category_" + url_name - url_params |= {"category": category.pk} - - return url_name, url_params diff --git a/nummi/main/templates/main/list.html b/nummi/main/templates/main/list.html index ee3050e..cd223b7 100644 --- a/nummi/main/templates/main/list.html +++ b/nummi/main/templates/main/list.html @@ -19,21 +19,23 @@

    {% block h2 %}{% endblock %}

    - {% if account %} -

    - {{ account.icon|remix }}{{ account }} -

    - {% endif %} - {% if category %} -

    - {{ category.icon|remix }}{{ category }} -

    - {% endif %} - {% if search %} -

    - {% translate "Search" %} -

    - {% endif %} + {% block backlinks %} + {% if account %} +

    + {{ account.icon|remix }}{{ account }} +

    + {% endif %} + {% if category %} +

    + {{ category.icon|remix }}{{ category }} +

    + {% endif %} + {% if search %} +

    + {% translate "Search" %} +

    + {% endif %} + {% endblock %} {% include "main/pagination.html" %} {% block table %}{% endblock %} {% include "main/pagination.html" %} diff --git a/nummi/main/templates/main/pagination.html b/nummi/main/templates/main/pagination.html index 7f32ea2..54e9e12 100644 --- a/nummi/main/templates/main/pagination.html +++ b/nummi/main/templates/main/pagination.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n transaction_extras %} {% if page_obj %}

    {% for page in paginator.page_range %} @@ -8,6 +8,7 @@

    {% endif %} {% if month %} +

    {% year_url month.year account=account category=category %}

    {% with month=previous_month %} {% include "main/pagination_month.html" %} diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index 3b71c4e..4ecaa53 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -1,5 +1,5 @@ {% extends "transaction/transaction_list.html" %} -{% load i18n main_extras static category %} +{% load i18n main_extras transaction_extras static category %} {% block link %} {{ block.super }} {% css "main/css/plot.css" %} diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py new file mode 100644 index 0000000..d55b3e0 --- /dev/null +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -0,0 +1,26 @@ +from django import template +from django.template.defaultfilters import date +from django.urls import reverse +from django.utils.safestring import mark_safe + +from ..utils import ac_url + +register = template.Library() + + +@register.simple_tag +def month_url(month, **kwargs): + url_name, url_params = ac_url( + "transaction_month", {"year": month.year, "month": month.month}, **kwargs + ) + + url = reverse(url_name, kwargs=url_params) + return mark_safe(f"""{ date(month, "Y-m") }""") + + +@register.simple_tag +def year_url(year, **kwargs): + url_name, url_params = ac_url("transaction_year", {"year": year}, **kwargs) + + url = reverse(url_name, kwargs=url_params) + return mark_safe(f"""{ year }""") diff --git a/nummi/transaction/utils.py b/nummi/transaction/utils.py new file mode 100644 index 0000000..25102e9 --- /dev/null +++ b/nummi/transaction/utils.py @@ -0,0 +1,9 @@ +def ac_url(url_name, url_params, account=None, category=None): + if account: + url_name = "account_" + url_name + url_params |= {"account": account.pk} + elif category: + url_name = "category_" + url_name + url_params |= {"category": category.pk} + + return url_name, url_params From 2ba21fbd10cce71942bbe5579505d6c9c5686d01 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 14:56:13 +0100 Subject: [PATCH 137/276] Refactor pagination for month/year --- nummi/history/templates/history/plot.html | 4 +- nummi/main/static/main/css/main.css | 17 +++++++++ nummi/main/templates/main/pagination.html | 38 +++++++++---------- .../main/templates/main/pagination_month.html | 5 --- .../main/templates/main/pagination_year.html | 5 --- .../templatetags/transaction_extras.py | 20 ++++++---- nummi/transaction/utils.py | 6 +-- 7 files changed, 51 insertions(+), 44 deletions(-) delete mode 100644 nummi/main/templates/main/pagination_month.html delete mode 100644 nummi/main/templates/main/pagination_year.html diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 23a5035..fdc756d 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -25,7 +25,7 @@ {% if date.sum_m or date.sum_p %}

    - + @@ -53,7 +53,7 @@ {% regroup history.data by month.year as years_list %} {% for y, year in years_list reversed %} - + {% for m in year %} {% if forloop.parentloop.last and forloop.first %} {% empty_calendar_cells_start m.month.month %} diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index d48a265..3fa85f8 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -163,6 +163,23 @@ footer { } } } + + &.n3 { + display: grid; + grid-template-columns: repeat(3, 1fr); + width: max-content; + margin: auto; + + .prev { + grid-column: 1; + } + .cur { + grid-column: 2; + } + .next { + grid-column: 3; + } + } } @media (width < 1024px) { diff --git a/nummi/main/templates/main/pagination.html b/nummi/main/templates/main/pagination.html index 54e9e12..0370ae0 100644 --- a/nummi/main/templates/main/pagination.html +++ b/nummi/main/templates/main/pagination.html @@ -8,29 +8,25 @@

    {% endif %} {% if month %} -

    {% year_url month.year account=account category=category %}

    -

    - {% with month=previous_month %} - {% include "main/pagination_month.html" %} - {% endwith %} - {% with cur=True %} - {% include "main/pagination_month.html" %} - {% endwith %} - {% with month=next_month %} - {% include "main/pagination_month.html" %} - {% endwith %} +

    {% year_url month %}

    +

    + {% if previous_month %} + {% month_url previous_month %} + {% endif %} + {% month_url month cls="cur" %} + {% if next_month %} + {% month_url next_month %} + {% endif %}

    {% endif %} {% if year %} -

    - {% with year=previous_year %} - {% include "main/pagination_year.html" %} - {% endwith %} - {% with cur=True %} - {% include "main/pagination_year.html" %} - {% endwith %} - {% with year=next_year %} - {% include "main/pagination_year.html" %} - {% endwith %} +

    + {% if previous_year %} + {% year_url previous_year cls="prev" %} + {% endif %} + {% year_url year cls="cur" %} + {% if next_year %} + {% year_url next_year cls="next" %} + {% endif %}

    {% endif %} diff --git a/nummi/main/templates/main/pagination_month.html b/nummi/main/templates/main/pagination_month.html deleted file mode 100644 index 288427a..0000000 --- a/nummi/main/templates/main/pagination_month.html +++ /dev/null @@ -1,5 +0,0 @@ -{% load i18n %} -{% if month %} - {{ month|date:"F Y"|capfirst }} -{% endif %} diff --git a/nummi/main/templates/main/pagination_year.html b/nummi/main/templates/main/pagination_year.html deleted file mode 100644 index 5c9bd3c..0000000 --- a/nummi/main/templates/main/pagination_year.html +++ /dev/null @@ -1,5 +0,0 @@ -{% load i18n %} -{% if year %} - {{ year|date:"Y" }} -{% endif %} diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index d55b3e0..f42aafe 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -1,3 +1,5 @@ +import datetime + from django import template from django.template.defaultfilters import date from django.urls import reverse @@ -8,19 +10,21 @@ from ..utils import ac_url register = template.Library() -@register.simple_tag -def month_url(month, **kwargs): +@register.simple_tag(takes_context=True) +def month_url(context, month, cls=""): url_name, url_params = ac_url( - "transaction_month", {"year": month.year, "month": month.month}, **kwargs + "transaction_month", {"year": month.year, "month": month.month}, context ) url = reverse(url_name, kwargs=url_params) - return mark_safe(f"""{ date(month, "Y-m") }""") + return mark_safe(f"""{ date(month, "Y-m") }""") -@register.simple_tag -def year_url(year, **kwargs): - url_name, url_params = ac_url("transaction_year", {"year": year}, **kwargs) +@register.simple_tag(takes_context=True) +def year_url(context, year, cls=""): + if isinstance(year, datetime.date): + year = year.year + url_name, url_params = ac_url("transaction_year", {"year": year}, context) url = reverse(url_name, kwargs=url_params) - return mark_safe(f"""{ year }""") + return mark_safe(f"""{ year }""") diff --git a/nummi/transaction/utils.py b/nummi/transaction/utils.py index 25102e9..399dcc2 100644 --- a/nummi/transaction/utils.py +++ b/nummi/transaction/utils.py @@ -1,8 +1,8 @@ -def ac_url(url_name, url_params, account=None, category=None): - if account: +def ac_url(url_name, url_params, context): + if account := context.get("account"): url_name = "account_" + url_name url_params |= {"account": account.pk} - elif category: + elif category := context.get("category"): url_name = "category_" + url_name url_params |= {"category": category.pk} From f8c0f9dced6a763b113012cbc33281dc53267643 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 15:00:27 +0100 Subject: [PATCH 138/276] Change date format in month pagination --- nummi/main/templates/main/pagination.html | 6 +++--- nummi/transaction/templatetags/transaction_extras.py | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nummi/main/templates/main/pagination.html b/nummi/main/templates/main/pagination.html index 0370ae0..ffb5d3e 100644 --- a/nummi/main/templates/main/pagination.html +++ b/nummi/main/templates/main/pagination.html @@ -11,11 +11,11 @@

    {% year_url month %}

    {% if previous_month %} - {% month_url previous_month %} + {% month_url previous_month fmt="F Y" %} {% endif %} - {% month_url month cls="cur" %} + {% month_url month cls="cur" fmt="F Y" %} {% if next_month %} - {% month_url next_month %} + {% month_url next_month fmt="F Y" %} {% endif %}

    {% endif %} diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index f42aafe..a042cd3 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -11,13 +11,15 @@ register = template.Library() @register.simple_tag(takes_context=True) -def month_url(context, month, cls=""): +def month_url(context, month, cls="", fmt="Y-m"): url_name, url_params = ac_url( "transaction_month", {"year": month.year, "month": month.month}, context ) url = reverse(url_name, kwargs=url_params) - return mark_safe(f"""{ date(month, "Y-m") }""") + return mark_safe( + f"""{date(month, fmt).capitalize()}""" + ) @register.simple_tag(takes_context=True) From 3fd87ff370689e1082757a097e7ba20861812472 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 15:03:45 +0100 Subject: [PATCH 139/276] Update month page template --- .../transaction/transaction_archive_month.html | 6 ++++-- .../transaction/transaction_archive_year.html | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index 4ecaa53..7c6b8a6 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -6,10 +6,12 @@ {% endblock %} {% block h2 %}{{ month|date:"F Y"|capfirst }}{% endblock %} {% block table %} -

    {% translate "Transactions" %}

    - {{ block.super }} {% if not category %}

    {% translate "Categories" %}

    {% category_plot transactions month=month %} {% endif %} +
    +

    {% translate "Transactions" %}

    + {{ block.super }} +
    {% endblock %} diff --git a/nummi/transaction/templates/transaction/transaction_archive_year.html b/nummi/transaction/templates/transaction/transaction_archive_year.html index e602fc7..da069d7 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_year.html +++ b/nummi/transaction/templates/transaction/transaction_archive_year.html @@ -9,14 +9,14 @@ {% block table %} {% if history %}
    -

    - {% translate "History" %} -

    +

    {% translate "History" %}

    {% include "history/plot.html" %}
    {% endif %} {% if not category %} -

    {% translate "Categories" %}

    - {% category_plot transactions year=year %} +
    +

    {% translate "Categories" %}

    + {% category_plot transactions year=year %} +
    {% endif %} {% endblock %} From 5ccae7f4a33d9990885b5884a521644b5dba748d Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 15:07:42 +0100 Subject: [PATCH 140/276] Fix spacing with pagination --- nummi/main/static/main/css/main.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 3fa85f8..38500c5 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -150,6 +150,7 @@ footer { .pagination { text-align: center; font-feature-settings: var(--num); + a { min-width: 1rem; padding: 0 0.5rem; @@ -168,7 +169,7 @@ footer { display: grid; grid-template-columns: repeat(3, 1fr); width: max-content; - margin: auto; + margin: 0.5rem auto; .prev { grid-column: 1; @@ -180,6 +181,10 @@ footer { grid-column: 3; } } + + & + section :first-child { + margin-top: 0; + } } @media (width < 1024px) { From 8ebb940f5b40ad5ce10884d0d17b1376d950c80e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 15:09:50 +0100 Subject: [PATCH 141/276] Add missing section block in month page --- .../templates/transaction/transaction_archive_month.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index 7c6b8a6..e6a2600 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -7,8 +7,10 @@ {% block h2 %}{{ month|date:"F Y"|capfirst }}{% endblock %} {% block table %} {% if not category %} -

    {% translate "Categories" %}

    - {% category_plot transactions month=month %} +
    +

    {% translate "Categories" %}

    + {% category_plot transactions month=month %} +
    {% endif %}

    {% translate "Transactions" %}

    From 87c12f47e9c03dcc16b23f0f372e32c029fbc201 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 15:15:57 +0100 Subject: [PATCH 142/276] Fix pagination on small devices --- nummi/main/static/main/css/main.css | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 38500c5..8a2244f 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -165,20 +165,22 @@ footer { } } - &.n3 { - display: grid; - grid-template-columns: repeat(3, 1fr); - width: max-content; - margin: 0.5rem auto; + @media (width > 720px) { + &.n3 { + display: grid; + grid-template-columns: repeat(3, 1fr); + width: max-content; + margin: 0.5rem auto; - .prev { - grid-column: 1; - } - .cur { - grid-column: 2; - } - .next { - grid-column: 3; + .prev { + grid-column: 1; + } + .cur { + grid-column: 2; + } + .next { + grid-column: 3; + } } } From b4c7f88b3d3d29dcab83cd97cc5b099bfea917aa Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 15:28:24 +0100 Subject: [PATCH 143/276] Add time elements for accessibility --- nummi/statement/templates/statement/statement_table.html | 4 +++- .../templates/transaction/transaction_table.html | 4 +++- nummi/transaction/templatetags/transaction_extras.py | 8 ++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index 0139811..57b4b8c 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -41,7 +41,9 @@ {% if snap.file %}{{ "attachment"|remix }}{% endif %}
    {% if not account %} diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index 86ba914..fb89dbf 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -43,7 +43,9 @@ - + diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index a042cd3..30cba01 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -18,7 +18,8 @@ def month_url(context, month, cls="", fmt="Y-m"): url = reverse(url_name, kwargs=url_params) return mark_safe( - f"""{date(month, fmt).capitalize()}""" + f"""""" ) @@ -29,4 +30,7 @@ def year_url(context, year, cls=""): url_name, url_params = ac_url("transaction_year", {"year": year}, context) url = reverse(url_name, kwargs=url_params) - return mark_safe(f"""{ year }""") + return mark_safe( + f"""""" + f"""""" + ) From e5cdf19fa38bc8c7ac5997ae73531df9cdb9252d Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 15:42:51 +0100 Subject: [PATCH 144/276] Add transaction list on year view --- .../templates/transaction/transaction_archive_year.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nummi/transaction/templates/transaction/transaction_archive_year.html b/nummi/transaction/templates/transaction/transaction_archive_year.html index da069d7..ea5b395 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_year.html +++ b/nummi/transaction/templates/transaction/transaction_archive_year.html @@ -1,4 +1,4 @@ -{% extends "main/list.html" %} +{% extends "transaction/transaction_list.html" %} {% load i18n main_extras static category %} {% block link %} {{ block.super }} @@ -19,4 +19,8 @@ {% category_plot transactions year=year %} {% endif %} +
    +

    {% translate "Transactions" %}

    + {{ block.super }} +
    {% endblock %} From 6b08113f60a404df539546bf6c6e2ae476dc015e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 16:04:04 +0100 Subject: [PATCH 145/276] Add back link on month and year page --- .../locale/fr_FR/LC_MESSAGES/django.po | 8 +-- .../locale/fr_FR/LC_MESSAGES/django.po | 10 ++-- .../locale/fr_FR/LC_MESSAGES/django.po | 10 ++-- nummi/main/locale/fr_FR/LC_MESSAGES/django.po | 44 ++++++++-------- nummi/main/static/main/css/main.css | 30 +++++++++-- nummi/main/templates/main/list.html | 36 ++++++------- .../search/locale/fr_FR/LC_MESSAGES/django.po | 4 +- .../locale/fr_FR/LC_MESSAGES/django.po | 20 ++++---- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 1431 -> 1494 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 48 +++++++++++------- .../transaction_archive_month.html | 11 ++++ .../transaction/transaction_archive_year.html | 8 +++ 12 files changed, 142 insertions(+), 87 deletions(-) diff --git a/nummi/account/locale/fr_FR/LC_MESSAGES/django.po b/nummi/account/locale/fr_FR/LC_MESSAGES/django.po index 0d61184..e2b3dd5 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: 2023-04-22 15:16+0200\n" +"POT-Creation-Date: 2024-01-02 15:52+0100\n" "PO-Revision-Date: 2023-04-22 15:17+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -45,14 +45,14 @@ msgstr "Créer un compte" msgid "New account" msgstr "Nouveau compte" -#: .\account\templates\account\account_form.html:13 +#: .\account\templates\account\account_form.html:14 msgid "Statements" msgstr "Relevés" -#: .\account\templates\account\account_form.html:15 +#: .\account\templates\account\account_form.html:18 msgid "Transactions" msgstr "Transactions" -#: .\account\templates\account\account_form.html:18 +#: .\account\templates\account\account_form.html:23 msgid "History" msgstr "Historique" diff --git a/nummi/category/locale/fr_FR/LC_MESSAGES/django.po b/nummi/category/locale/fr_FR/LC_MESSAGES/django.po index e5881ca..d59e917 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: 2023-11-25 12:05+0100\n" +"POT-Creation-Date: 2024-01-02 15:52+0100\n" "PO-Revision-Date: 2023-04-22 15:18+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -46,11 +46,11 @@ msgstr "Créer une catégorie" msgid "New category" msgstr "Nouvelle catégorie" -#: .\category\templates\category\category_form.html:12 +#: .\category\templates\category\category_form.html:13 msgid "Transactions" msgstr "Transactions" -#: .\category\templates\category\category_form.html:15 +#: .\category\templates\category\category_form.html:18 msgid "History" msgstr "Historique" @@ -62,10 +62,10 @@ msgstr "Dépenses" msgid "Income" msgstr "Revenus" -#: .\category\templates\category\category_plot.html:56 +#: .\category\templates\category\category_plot.html:62 msgid "No transaction" msgstr "Aucune transaction" -#: .\category\templates\category\category_plot.html:64 +#: .\category\templates\category\category_plot.html:70 msgid "Total" msgstr "Total" diff --git a/nummi/history/locale/fr_FR/LC_MESSAGES/django.po b/nummi/history/locale/fr_FR/LC_MESSAGES/django.po index 3df2dfa..50fde9f 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: 2023-12-28 15:45+0100\n" +"POT-Creation-Date: 2024-01-02 15:52+0100\n" "PO-Revision-Date: 2023-04-22 15:18+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -17,18 +17,18 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.2.2\n" -#: .\history\templates\history\plot.html:15 +#: .\history\templates\history\plot.html:17 msgid "Month" msgstr "Mois" -#: .\history\templates\history\plot.html:16 +#: .\history\templates\history\plot.html:18 msgid "Expenses" msgstr "Dépenses" -#: .\history\templates\history\plot.html:17 +#: .\history\templates\history\plot.html:19 msgid "Income" msgstr "Revenus" -#: .\history\templates\history\plot.html:68 +#: .\history\templates\history\plot.html:48 msgid "Year" msgstr "Année" diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.po b/nummi/main/locale/fr_FR/LC_MESSAGES/django.po index 18f7b5d..378d5ba 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: 2023-11-25 12:05+0100\n" +"POT-Creation-Date: 2024-01-02 15:52+0100\n" "PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -21,67 +21,67 @@ msgstr "" msgid "User" msgstr "Utilisateur" -#: .\main\templates\main\base.html:27 +#: .\main\templates\main\base.html:28 msgid "Skip to main content" msgstr "Aller au contenu principal" -#: .\main\templates\main\base.html:33 +#: .\main\templates\main\base.html:34 msgid "Home" msgstr "Accueil" -#: .\main\templates\main\base.html:38 .\main\templates\main\index.html:40 +#: .\main\templates\main\base.html:39 .\main\templates\main\index.html:43 msgid "Statements" msgstr "Relevés" -#: .\main\templates\main\base.html:44 .\main\templates\main\index.html:24 +#: .\main\templates\main\base.html:45 .\main\templates\main\index.html:23 msgid "Transactions" msgstr "Transactions" -#: .\main\templates\main\base.html:50 +#: .\main\templates\main\base.html:51 msgid "Create account" msgstr "Créer un compte" -#: .\main\templates\main\base.html:55 +#: .\main\templates\main\base.html:56 msgid "Create statement" msgstr "Créer un relevé" -#: .\main\templates\main\base.html:60 +#: .\main\templates\main\base.html:61 msgid "Create category" msgstr "Créer une catégorie" -#: .\main\templates\main\base.html:65 +#: .\main\templates\main\base.html:66 msgid "Create transaction" msgstr "Créer une transaction" -#: .\main\templates\main\base.html:70 .\main\templates\main\list.html:10 +#: .\main\templates\main\base.html:71 .\main\templates\main\list.html:10 #: .\main\templates\main\list.html:36 msgid "Search" msgstr "Rechercher" -#: .\main\templates\main\base.html:73 +#: .\main\templates\main\base.html:74 msgid "Log out" msgstr "Se déconnecter" -#: .\main\templates\main\base.html:78 .\main\templates\main\form\login.html:6 -#: .\main\templates\main\login.html:14 +#: .\main\templates\main\base.html:79 .\main\templates\main\form\login.html:6 +#: .\main\templates\main\login.html:11 msgid "Log in" msgstr "Se connecter" -#: .\main\templates\main\base.html:84 +#: .\main\templates\main\base.html:85 #, python-format msgid "Logged in as %(user)s" msgstr "Connecté en tant que %(user)s" -#: .\main\templates\main\confirm_delete.html:19 +#: .\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:23 +#: .\main\templates\main\confirm_delete.html:19 msgid "Cancel" msgstr "Annuler" -#: .\main\templates\main\confirm_delete.html:24 +#: .\main\templates\main\confirm_delete.html:20 msgid "Confirm" msgstr "Confirmer" @@ -105,23 +105,23 @@ msgstr "Créer" msgid "Save" msgstr "Enregistrer" -#: .\main\templates\main\index.html:15 +#: .\main\templates\main\index.html:12 msgid "Accounts" msgstr "Comptes" -#: .\main\templates\main\index.html:20 +#: .\main\templates\main\index.html:17 msgid "No account" msgstr "Aucun compte" -#: .\main\templates\main\index.html:28 +#: .\main\templates\main\index.html:29 msgid "Categories" msgstr "Catégories" -#: .\main\templates\main\index.html:34 +#: .\main\templates\main\index.html:35 msgid "No category" msgstr "Aucune catégorie" -#: .\main\templates\main\index.html:44 +#: .\main\templates\main\index.html:49 msgid "History" msgstr "Historique" diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 8a2244f..6952422 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -69,9 +69,10 @@ a { &.big-link { margin-right: 1em; - } - &.big-link [class^="ri-"] { - margin-right: 0.5em; + + [class^="ri-"] { + margin-right: 0.5em; + } } } @@ -249,3 +250,26 @@ ul.messages { } } } + +.backlinks { + display: grid; + grid-template-columns: repeat(2, 1fr); + + p { + grid-column: 1; + + &.back { + grid-column: 2; + text-align: right; + + .big-link { + margin-right: 0; + margin-left: 1em; + [class^="ri-"] { + margin-right: 0em; + margin-left: 0.5em; + } + } + } + } +} diff --git a/nummi/main/templates/main/list.html b/nummi/main/templates/main/list.html index cd223b7..57adec0 100644 --- a/nummi/main/templates/main/list.html +++ b/nummi/main/templates/main/list.html @@ -19,23 +19,25 @@

    {% block h2 %}{% endblock %}

    - {% block backlinks %} - {% if account %} -

    - {{ account.icon|remix }}{{ account }} -

    - {% endif %} - {% if category %} -

    - {{ category.icon|remix }}{{ category }} -

    - {% endif %} - {% if search %} -

    - {% translate "Search" %} -

    - {% endif %} - {% endblock %} + {% include "main/pagination.html" %} {% block table %}{% endblock %} {% include "main/pagination.html" %} diff --git a/nummi/search/locale/fr_FR/LC_MESSAGES/django.po b/nummi/search/locale/fr_FR/LC_MESSAGES/django.po index 5517665..21455e6 100644 --- a/nummi/search/locale/fr_FR/LC_MESSAGES/django.po +++ b/nummi/search/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: 2023-04-22 15:16+0200\n" +"POT-Creation-Date: 2024-01-02 15:52+0100\n" "PO-Revision-Date: 2023-04-22 15:20+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -18,7 +18,7 @@ msgstr "" "X-Generator: Poedit 3.2.2\n" #: .\search\forms.py:7 .\search\templates\search\search.html:6 -#: .\search\templates\search\search.html:18 +#: .\search\templates\search\search.html:14 #: .\search\templates\search\search_form.html:5 msgid "Search" msgstr "Rechercher" diff --git a/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po index c9ae392..69017a9 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: 2023-11-25 12:05+0100\n" +"POT-Creation-Date: 2024-01-02 15:52+0100\n" "PO-Revision-Date: 2023-04-22 15:22+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -38,7 +38,7 @@ msgid "Start value" msgstr "Valeur initiale" #: .\statement\models.py:29 -#: .\statement\templates\statement\statement_table.html:27 +#: .\statement\templates\statement\statement_table.html:28 msgid "Difference" msgstr "Différence" @@ -74,32 +74,32 @@ msgstr "Créer un relevé" msgid "New statement" msgstr "Nouveau relevé" -#: .\statement\templates\statement\statement_form.html:22 +#: .\statement\templates\statement\statement_form.html:23 msgid "Categories" msgstr "Catégories" -#: .\statement\templates\statement\statement_form.html:24 -#: .\statement\templates\statement\statement_table.html:28 +#: .\statement\templates\statement\statement_form.html:27 +#: .\statement\templates\statement\statement_table.html:29 msgid "Transactions" msgstr "Transactions" -#: .\statement\templates\statement\statement_table.html:22 +#: .\statement\templates\statement\statement_table.html:23 msgid "Date" msgstr "Date" -#: .\statement\templates\statement\statement_table.html:24 +#: .\statement\templates\statement\statement_table.html:25 msgid "Account" msgstr "Compte" -#: .\statement\templates\statement\statement_table.html:26 +#: .\statement\templates\statement\statement_table.html:27 msgid "Value" msgstr "Valeur" -#: .\statement\templates\statement\statement_table.html:56 +#: .\statement\templates\statement\statement_table.html:60 msgid "No statement" msgstr "Aucun relevé" -#: .\statement\templates\statement\statement_table.html:64 +#: .\statement\templates\statement\statement_table.html:68 msgid "View all statements" msgstr "Voir tous les relevés" diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo index b34179b90b20d71fd55363e1e3f5378f254f26e2..6d872a83dba4ee847d3dc60f6792146778c8c726 100644 GIT binary patch delta 687 zcmX}pKP&@b7{~Fqby`|~OZ_P(kkH-ueyd2voHS($G$jr@EC& zPSe${Rk%iiPSuqpog1zkK96Qf#c|JH5o_6vDH&;5}3qA!x Xb9TWk=E_;ij?~^eYel>KXx;n)p^QOj delta 610 zcmX}pKS;ws6vy$`m|AVL*7&Cix+#K_t6RGWf;e>&oGRj=c2E}`vWSagkWz4KM+K?4 z>f$1TOFf+43;p9 z>o|mE%;7eMxQ{+YIEd#M;H7(CL+x{oTHoxwkL;F#Cbn=4A5n$gPz63v3%fXs-_BoD zA%CE^KEM(F7ch+rIEBl&L^DOy{5?*odR5F?WOl|t9bDiv){&px@le4B)Pg6}L0+A0 zR41Rv-qyi9c5xDaPz~fbjrN~M&0j$sbQLqyw>1VTu<6`#6LwK2K0V?d9T~j zhFrvdsG#;zsK_{3Aa$et(5>`iW0aA;y*gLu=@!(vVun;V3U!-AsZyz) V93IC9UitpTYp3FdzY~Y)=RfapE)D\n" "Language-Team: \n" @@ -22,8 +22,8 @@ msgid "Transaction" msgstr "Transaction" #: .\transaction\models.py:19 .\transaction\models.py:89 -#: .\transaction\templates\transaction\invoice_table.html:9 -#: .\transaction\templates\transaction\transaction_table.html:28 +#: .\transaction\templates\transaction\invoice_table.html:10 +#: .\transaction\templates\transaction\transaction_table.html:29 msgid "Name" msgstr "Nom" @@ -32,12 +32,12 @@ msgid "Description" msgstr "Description" #: .\transaction\models.py:23 -#: .\transaction\templates\transaction\transaction_table.html:29 +#: .\transaction\templates\transaction\transaction_table.html:30 msgid "Value" msgstr "Valeur" #: .\transaction\models.py:25 -#: .\transaction\templates\transaction\transaction_table.html:27 +#: .\transaction\templates\transaction\transaction_table.html:28 msgid "Date" msgstr "Date" @@ -46,7 +46,7 @@ msgid "Real date" msgstr "Date réelle" #: .\transaction\models.py:28 -#: .\transaction\templates\transaction\transaction_table.html:30 +#: .\transaction\templates\transaction\transaction_table.html:31 msgid "Trader" msgstr "Commerçant" @@ -55,7 +55,7 @@ msgid "Payment" msgstr "Paiement" #: .\transaction\models.py:38 -#: .\transaction\templates\transaction\transaction_table.html:32 +#: .\transaction\templates\transaction\transaction_table.html:33 msgid "Category" msgstr "Catégorie" @@ -64,12 +64,13 @@ msgid "Statement" msgstr "Relevé" #: .\transaction\models.py:48 -#: .\transaction\templates\transaction\transaction_table.html:35 +#: .\transaction\templates\transaction\transaction_table.html:36 msgid "Account" msgstr "Compte" #: .\transaction\models.py:83 -#: .\transaction\templates\transaction\transaction_archive_month.html:11 +#: .\transaction\templates\transaction\transaction_archive_month.html:24 +#: .\transaction\templates\transaction\transaction_archive_year.html:23 #: .\transaction\templates\transaction\transaction_list.html:4 #: .\transaction\templates\transaction\transaction_list.html:7 msgid "Transactions" @@ -80,18 +81,18 @@ msgid "Invoice" msgstr "Facture" #: .\transaction\models.py:94 -#: .\transaction\templates\transaction\invoice_table.html:10 -#: .\transaction\templates\transaction\invoice_table.html:20 +#: .\transaction\templates\transaction\invoice_table.html:11 +#: .\transaction\templates\transaction\invoice_table.html:22 msgid "File" msgstr "Fichier" #: .\transaction\models.py:123 -#: .\transaction\templates\transaction\transaction_form.html:19 +#: .\transaction\templates\transaction\transaction_form.html:20 msgid "Invoices" msgstr "Factures" #: .\transaction\templates\transaction\invoice_form.html:4 -#: .\transaction\templates\transaction\invoice_table.html:35 +#: .\transaction\templates\transaction\invoice_table.html:37 msgid "Create invoice" msgstr "Créer une facture" @@ -99,19 +100,28 @@ msgstr "Créer une facture" msgid "New invoice" msgstr "Nouvelle facture" -#: .\transaction\templates\transaction\invoice_table.html:11 -#: .\transaction\templates\transaction\invoice_table.html:23 +#: .\transaction\templates\transaction\invoice_table.html:12 +#: .\transaction\templates\transaction\invoice_table.html:25 msgid "Delete" msgstr "Supprimer" -#: .\transaction\templates\transaction\invoice_table.html:28 +#: .\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:12 +msgid "Back" +msgstr "Retour" + +#: .\transaction\templates\transaction\transaction_archive_month.html:19 +#: .\transaction\templates\transaction\transaction_archive_year.html:18 msgid "Categories" msgstr "Catégories" +#: .\transaction\templates\transaction\transaction_archive_year.html:12 +msgid "History" +msgstr "Historique" + #: .\transaction\templates\transaction\transaction_form.html:5 #: .\transaction\templates\transaction\transaction_table.html:5 msgid "Create transaction" @@ -121,10 +131,10 @@ msgstr "Créer une transaction" msgid "New transaction" msgstr "Nouvelle transaction" -#: .\transaction\templates\transaction\transaction_table.html:71 +#: .\transaction\templates\transaction\transaction_table.html:75 msgid "No transaction" msgstr "Aucune transaction" -#: .\transaction\templates\transaction\transaction_table.html:80 +#: .\transaction\templates\transaction\transaction_table.html:84 msgid "View all transactions" msgstr "Voir toutes les transactions" diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index e6a2600..5b85799 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -5,6 +5,17 @@ {% css "main/css/plot.css" %} {% endblock %} {% block h2 %}{{ month|date:"F Y"|capfirst }}{% endblock %} +{% block backlinks %} + {{ block.super }} + {% if account or category %} +

    + + {% translate "Back" %}{{ "arrow-go-back"|remix }} + +

    + {% endif %} +{% endblock %} {% block table %} {% if not category %}
    diff --git a/nummi/transaction/templates/transaction/transaction_archive_year.html b/nummi/transaction/templates/transaction/transaction_archive_year.html index ea5b395..4b979d8 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_year.html +++ b/nummi/transaction/templates/transaction/transaction_archive_year.html @@ -6,6 +6,14 @@ {% endblock %} {% block name %}{{ year|date:"Y" }}{% endblock %} {% block h2 %}{{ year|date:"Y" }}{% endblock %} +{% block backlinks %} + {{ block.super }} + {% if account or category %} +

    + {% translate "Back" %}{{ "arrow-go-back"|remix }} +

    + {% endif %} +{% endblock %} {% block table %} {% if history %}
    From 65d97f523c20f1b619362ada679648792b40e389 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 16:58:24 +0100 Subject: [PATCH 146/276] Add title attribute to calendar plot for accessibility --- nummi/history/templates/history/plot.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index fdc756d..8501506 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -60,7 +60,8 @@ {% endif %} {% if m %}
    + style="opacity: {% calendar_opacity m.sum history.max.sum %}" + title="{{ m.sum|pmrvalue }}"> {% else %} {% endif %} From e44e3a51f0fec244caa37977a1bc0e68bcd7f98c Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 2 Jan 2024 17:03:55 +0100 Subject: [PATCH 147/276] Fix contrast issues with table icons --- nummi/history/templatetags/history_extras.py | 4 ++-- nummi/statement/templates/statement/statement_table.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index 48a8aaf..56b01c4 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -30,9 +30,9 @@ def empty_calendar_cells_end(n): @register.simple_tag def up_down_icon(val): if val > 0: - return remix("arrow-up-s", "green") + return remix("arrow-up-s") elif val < 0: - return remix("arrow-down-s", "red") + return remix("arrow-down-s") @register.simple_tag diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index 57b4b8c..322653a 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -33,9 +33,9 @@ {% for snap in statements %} {% if snap.sum == snap.diff %} - + {% else %} - + {% endif %} + {% if new_statement_url %} + + + + {% endif %} @@ -23,13 +30,6 @@ - {% if new_statement_url %} - - - - {% endif %} {% for snap in statements %} diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.po index 3384c25..efd15fd 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-02 15:52+0100\n" +"POT-Creation-Date: 2024-01-03 14:32+0100\n" "PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -23,7 +23,7 @@ msgstr "Transaction" #: .\transaction\models.py:19 .\transaction\models.py:89 #: .\transaction\templates\transaction\invoice_table.html:10 -#: .\transaction\templates\transaction\transaction_table.html:29 +#: .\transaction\templates\transaction\transaction_table.html:24 msgid "Name" msgstr "Nom" @@ -32,12 +32,12 @@ msgid "Description" msgstr "Description" #: .\transaction\models.py:23 -#: .\transaction\templates\transaction\transaction_table.html:30 +#: .\transaction\templates\transaction\transaction_table.html:25 msgid "Value" msgstr "Valeur" #: .\transaction\models.py:25 -#: .\transaction\templates\transaction\transaction_table.html:28 +#: .\transaction\templates\transaction\transaction_table.html:23 msgid "Date" msgstr "Date" @@ -46,7 +46,7 @@ msgid "Real date" msgstr "Date réelle" #: .\transaction\models.py:28 -#: .\transaction\templates\transaction\transaction_table.html:31 +#: .\transaction\templates\transaction\transaction_table.html:26 msgid "Trader" msgstr "Commerçant" @@ -55,7 +55,7 @@ msgid "Payment" msgstr "Paiement" #: .\transaction\models.py:38 -#: .\transaction\templates\transaction\transaction_table.html:33 +#: .\transaction\templates\transaction\transaction_table.html:28 msgid "Category" msgstr "Catégorie" @@ -64,13 +64,13 @@ msgid "Statement" msgstr "Relevé" #: .\transaction\models.py:48 -#: .\transaction\templates\transaction\transaction_table.html:36 +#: .\transaction\templates\transaction\transaction_table.html:31 msgid "Account" msgstr "Compte" #: .\transaction\models.py:83 -#: .\transaction\templates\transaction\transaction_archive_month.html:24 -#: .\transaction\templates\transaction\transaction_archive_year.html:23 +#: .\transaction\templates\transaction\transaction_archive_month.html:27 +#: .\transaction\templates\transaction\transaction_archive_year.html:31 #: .\transaction\templates\transaction\transaction_list.html:4 #: .\transaction\templates\transaction\transaction_list.html:7 msgid "Transactions" @@ -109,21 +109,22 @@ msgstr "Supprimer" msgid "No invoice" msgstr "Aucune facture" -#: .\transaction\templates\transaction\transaction_archive_month.html:12 +#: .\transaction\templates\transaction\transaction_archive_month.html:14 +#: .\transaction\templates\transaction\transaction_archive_year.html:13 msgid "Back" msgstr "Retour" -#: .\transaction\templates\transaction\transaction_archive_month.html:19 -#: .\transaction\templates\transaction\transaction_archive_year.html:18 +#: .\transaction\templates\transaction\transaction_archive_month.html:22 +#: .\transaction\templates\transaction\transaction_archive_year.html:26 msgid "Categories" msgstr "Catégories" -#: .\transaction\templates\transaction\transaction_archive_year.html:12 +#: .\transaction\templates\transaction\transaction_archive_year.html:20 msgid "History" msgstr "Historique" #: .\transaction\templates\transaction\transaction_form.html:5 -#: .\transaction\templates\transaction\transaction_table.html:5 +#: .\transaction\templates\transaction\transaction_table.html:37 msgid "Create transaction" msgstr "Créer une transaction" @@ -131,10 +132,10 @@ msgstr "Créer une transaction" msgid "New transaction" msgstr "Nouvelle transaction" -#: .\transaction\templates\transaction\transaction_table.html:75 +#: .\transaction\templates\transaction\transaction_table.html:77 msgid "No transaction" msgstr "Aucune transaction" -#: .\transaction\templates\transaction\transaction_table.html:84 +#: .\transaction\templates\transaction\transaction_table.html:86 msgid "View all transactions" msgstr "Voir toutes les transactions" diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index ff31a73..a9a0ddf 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -18,6 +18,13 @@ {% endif %} + {% if new_transaction_url %} + + + + {% endif %} @@ -31,13 +38,6 @@ {% endif %} - {% if new_transaction_url %} - - - - {% endif %} {% for trans in transactions %} From 05b74181620effe48f556403a83e431ca96e7333 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 14:51:40 +0100 Subject: [PATCH 150/276] Reshape home page --- nummi/main/static/main/css/main.css | 22 ++++++++++++++++++++-- nummi/main/templates/main/index.html | 16 ++-------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 6952422..6bd5292 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -94,9 +94,27 @@ main { grid-column: 2; grid-row: 1; overflow-x: hidden; + + display: grid; + grid-template-columns: max-content 1fr; + grid-gap: var(--gap); + h2.new { opacity: 0.8; } + + & > * { + grid-column: 1 / -1; + } + + & > section { + &.accounts { + grid-column: 1; + } + &.categories { + grid-column: 2; + } + } } nav { grid-column: 1; @@ -139,7 +157,7 @@ nav { } } :is(nav, main) > :first-child, -main > section:first-child > :first-child { +main > section > :first-child { margin-top: 0; } footer { @@ -211,7 +229,7 @@ h1, h2, h3 { font-weight: 300; - margin-top: 1em; + margin-top: var(--gap); margin-bottom: 0.5em; line-height: 1cap; } diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index d3f1add..53a6632 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -8,7 +8,7 @@ {% css "main/css/plot.css" %} {% endblock %} {% block body %} -
    +

    {% translate "Accounts" %}

    {% up_down_icon date.sum %}{% month_url date.month account=account category=category %}{% month_url date.month %} {{ date.sum_m|pmrvalue }} {% plot_bar date.sum date.sum_m history.max.pm %} {% plot_bar date.sum date.sum_p history.max.pm %}
    {% year_url y account=account category=category %}{% year_url y %} - {{ snap.date|date:"Y-m-d" }} + + + {{ snap.account.icon|remix }} {% for invoice in trans.invoices %}{{ "attachment"|remix }}{% endfor %} {{ trans.date|date:"Y-m-d" }} + + {{ trans.name }}
    {{ "check"|remix }}{{ "check"|remix }}{{ "close"|remix }}{{ "close"|remix }} {% if snap.file %}{{ "attachment"|remix }}{% endif %} From 9927cbbab415e713766e461f22c6ed51dec2cf98 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 14:25:25 +0100 Subject: [PATCH 148/276] Move new and more links to tables --- nummi/main/static/main/css/table.css | 12 +++++--- .../templates/statement/statement_table.html | 26 ++++++++++------- .../transaction/transaction_table.html | 28 +++++++++++-------- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index 0092937..155a75d 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -5,9 +5,6 @@ form { table { border-collapse: collapse; - &.more tbody:last-child tr:last-child { - border-bottom: 1px dashed var(--gray); - } &.full-width { width: 100%; @@ -18,7 +15,7 @@ table { col.icon { width: 1ch; } - thead { + thead tr:not(.new) { background: var(--bg-01); } tr { @@ -29,6 +26,13 @@ table { tbody &:where(:nth-of-type(even)) { background: #eeeeff; } + tfoot &.more, + thead &.new { + text-align: center; + } + tfoot &.more { + border-style: dashed; + } } td, th { diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index 322653a..34c87a6 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -1,10 +1,5 @@ {% load main_extras %} {% load i18n %} -{% if new_statement_url %} -

    - {% translate "Create statement" %} -

    -{% endif %}
    @@ -28,6 +23,13 @@ + {% if new_statement_url %} + + + + {% endif %} {% for snap in statements %} @@ -61,10 +63,14 @@ {% endfor %} + {% if statements_url %} + + + + + + {% endif %}
    {% translate "Difference" %} {% translate "Transactions" %}
    + {% translate "Create statement" %} +
    + {% translate "View all statements" %} +
    -{% if statements_url %} -

    - {% translate "View all statements" %} -

    -{% endif %} diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index fb89dbf..ff31a73 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -1,12 +1,7 @@ {% load main_extras %} {% load i18n %} -{% if new_transaction_url %} -

    - {% translate "Create transaction" %} -

    -{% endif %}
    - +
    @@ -36,6 +31,13 @@ {% endif %} + {% if new_transaction_url %} + + + + {% endif %} {% for trans in transactions %} @@ -77,10 +79,14 @@ {% endfor %} + {% if transactions_url %} + + + + + + {% endif %}
    {% translate "Account" %}
    + {% translate "Create transaction" %} +
    + {% translate "View all transactions" %} +
    -{% if transactions_url %} -

    - {% translate "View all transactions" %} -

    -{% endif %} From d48818e4552aa6e4e194d6d06a368d63c398972b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 14:40:48 +0100 Subject: [PATCH 149/276] Create table for accounts --- nummi/main/locale/fr_FR/LC_MESSAGES/django.mo | Bin 1751 -> 1828 bytes nummi/main/locale/fr_FR/LC_MESSAGES/django.po | 20 +++++++--- nummi/main/static/main/css/table.css | 6 +-- nummi/main/templates/main/index.html | 37 ++++++++++++++---- .../locale/fr_FR/LC_MESSAGES/django.po | 18 ++++----- .../templates/statement/statement_table.html | 14 +++---- .../locale/fr_FR/LC_MESSAGES/django.po | 33 ++++++++-------- .../transaction/transaction_table.html | 14 +++---- 8 files changed, 86 insertions(+), 56 deletions(-) diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo index 7f2a8f069af009197b2d2fc8c0ebda76e0fe89e9..c963419eca93ed0d64d1b1c6058b7de5609b7c13 100644 GIT binary patch delta 722 zcmYk)y-Px26vy$$n^(=$%Iw8nY_l37+G=RXp%;iC0;d+a2@RqSxdkraKOhZF6%-L| z88rnF4MDUOZH+-R)X>mi)c0qX=)mL0dwe|SdAX0_{Yd#K>fJI#g3-?id5jssIx2Q8 zWBj;>0X)JfJaO~y=;QelgZS;{gH6BrC`QP~u@#504JR;aOxeuyMFm#Siw>&cn)_UI ze=oWD4NUU;CaU2vw%{4sc!jESgQ|OnF}z33f58xbpqKu}!|WasJ{)4#gQ!2wQcVk4 z#1y7c4Yu9<4yy4XHe(I-zYEk3udx#!upM8q3%^kFV@&qb-z4~=l_izHanuSYQAaj| zW0-dH6;#10>Szy83pqvAxx`+)MP1o5s@@xFULE@|K(n&0nR{py)F~)5Sp~St#sWC0 zM(7>U%C#%S07DJbR7bAR4i(x+oWW)r(ZQ##f!_aP^zT7C(W=4>y@)!(ZiZHDrX8nH j%CA?8)|@|;$z}3Rwsvl%t*Xynu$K$D)og9au6TX`YfCye delta 662 zcmYk)y)Q#y6vy$?t5p~6rQYc#UN#nkM28Cg03i`;69x&DdPxj!B9U}4mBB(H78A)e zNDN{jv9U-KVKEr|0hJhhf9)Nd6+JQ{9gbfdV7x{6A@Yt{ z1FmBRcd#CBuo3Stj!)t97ffd*D!?g!C8YYZ|&nIw@ z_fx0|SFj1UFot_bAuFO9JHU24MCD&#GhUhYpI4BQs{rB#VE9EYS6N|+}h^qM!~YBz{vy^XWl8z#ioPP*nZ>> DdYClT diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.po b/nummi/main/locale/fr_FR/LC_MESSAGES/django.po index 378d5ba..32068a8 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-02 15:52+0100\n" +"POT-Creation-Date: 2024-01-03 14:32+0100\n" "PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -29,11 +29,11 @@ msgstr "Aller au contenu principal" msgid "Home" msgstr "Accueil" -#: .\main\templates\main\base.html:39 .\main\templates\main\index.html:43 +#: .\main\templates\main\base.html:39 .\main\templates\main\index.html:60 msgid "Statements" msgstr "Relevés" -#: .\main\templates\main\base.html:45 .\main\templates\main\index.html:23 +#: .\main\templates\main\base.html:45 .\main\templates\main\index.html:40 msgid "Transactions" msgstr "Transactions" @@ -113,15 +113,23 @@ msgstr "Comptes" msgid "No account" msgstr "Aucun compte" -#: .\main\templates\main\index.html:29 +#: .\main\templates\main\index.html:23 +msgid "Account" +msgstr "Compte" + +#: .\main\templates\main\index.html:24 +msgid "Balance" +msgstr "Solde" + +#: .\main\templates\main\index.html:46 msgid "Categories" msgstr "Catégories" -#: .\main\templates\main\index.html:35 +#: .\main\templates\main\index.html:52 msgid "No category" msgstr "Aucune catégorie" -#: .\main\templates\main\index.html:49 +#: .\main\templates\main\index.html:66 msgid "History" msgstr "Historique" diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index 155a75d..3e38ad6 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -26,11 +26,9 @@ table { tbody &:where(:nth-of-type(even)) { background: #eeeeff; } - tfoot &.more, - thead &.new { + &.more, + &.new { text-align: center; - } - tfoot &.more { border-style: dashed; } } diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 3f56a84..d3f1add 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -10,13 +10,36 @@ {% block body %}

    {% translate "Accounts" %}

    -

    - {% for acc in accounts %} - {{ acc.icon|remix }}{{ acc }} - {% empty %} - {% translate "No account" %} - {% endfor %} -

    + + + + + + + + + {% for acc in accounts %} + + + + + + {% empty %} + + + + {% endfor %} + + + + + + +
    {% translate "Account" %}{% translate "Balance" %}
    {{ acc.icon|remix }} + {{ acc }} + {{ acc.statement_set.first.value|value }}
    {% translate "No account" %}
    + {% translate "Create account" %} +
    {% if transactions %}
    diff --git a/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po index 69017a9..cd3180d 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-02 15:52+0100\n" +"POT-Creation-Date: 2024-01-03 14:32+0100\n" "PO-Revision-Date: 2023-04-22 15:22+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -38,7 +38,7 @@ msgid "Start value" msgstr "Valeur initiale" #: .\statement\models.py:29 -#: .\statement\templates\statement\statement_table.html:28 +#: .\statement\templates\statement\statement_table.html:23 msgid "Difference" msgstr "Différence" @@ -66,7 +66,7 @@ msgid "Statements" msgstr "Relevés" #: .\statement\templates\statement\statement_form.html:4 -#: .\statement\templates\statement\statement_table.html:5 +#: .\statement\templates\statement\statement_table.html:29 msgid "Create statement" msgstr "Créer un relevé" @@ -79,27 +79,27 @@ msgid "Categories" msgstr "Catégories" #: .\statement\templates\statement\statement_form.html:27 -#: .\statement\templates\statement\statement_table.html:29 +#: .\statement\templates\statement\statement_table.html:24 msgid "Transactions" msgstr "Transactions" -#: .\statement\templates\statement\statement_table.html:23 +#: .\statement\templates\statement\statement_table.html:18 msgid "Date" msgstr "Date" -#: .\statement\templates\statement\statement_table.html:25 +#: .\statement\templates\statement\statement_table.html:20 msgid "Account" msgstr "Compte" -#: .\statement\templates\statement\statement_table.html:27 +#: .\statement\templates\statement\statement_table.html:22 msgid "Value" msgstr "Valeur" -#: .\statement\templates\statement\statement_table.html:60 +#: .\statement\templates\statement\statement_table.html:62 msgid "No statement" msgstr "Aucun relevé" -#: .\statement\templates\statement\statement_table.html:68 +#: .\statement\templates\statement\statement_table.html:70 msgid "View all statements" msgstr "Voir tous les relevés" diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index 34c87a6..514dc16 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -12,6 +12,13 @@
    + {% translate "Create statement" %} +
    {{ "check"|remix }} {{ "attachment"|remix }}{% translate "Difference" %} {% translate "Transactions" %}
    - {% translate "Create statement" %} -
    + {% translate "Create transaction" %} +
    {{ "attachment"|remix }} {% translate "Date" %}{% translate "Account" %}
    - {% translate "Create transaction" %} -
    @@ -41,14 +41,8 @@
    - {% if transactions %} -
    -

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} -
    - {% endif %} {% if categories %} -
    +

    {% translate "Categories" %}

    {% spaceless %}

    @@ -61,12 +55,6 @@ {% endspaceless %}

    {% endif %} - {% if statements %} -
    -

    {% translate "Statements" %}

    - {% include "statement/statement_table.html" %} -
    - {% endif %} {% if history %}

    {% translate "History" %}

    From 0f1aba45ea03bd97ac2768ccc2243c24a183786a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 14:54:24 +0100 Subject: [PATCH 151/276] Add class value to balance in account table --- nummi/main/templates/main/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 53a6632..0fe5318 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -24,7 +24,7 @@ {{ acc }} - {{ acc.statement_set.first.value|value }} + {{ acc.statement_set.first.value|value }} {% empty %} From 6d14602dd26cf2d1968bed1f0c9bf7adbccdaa51 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 14:59:08 +0100 Subject: [PATCH 152/276] Fix homepage on small screens --- nummi/main/static/main/css/main.css | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 6bd5292..e5b4b07 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -107,12 +107,14 @@ main { grid-column: 1 / -1; } - & > section { - &.accounts { - grid-column: 1; - } - &.categories { - grid-column: 2; + @media (width > 720px) { + & > section { + &.accounts { + grid-column: 1; + } + &.categories { + grid-column: 2; + } } } } From b478286f47f82dfa55a01be7edd5cfeeb034f4da Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 15:13:54 +0100 Subject: [PATCH 153/276] Fix page layout --- nummi/main/static/main/css/main.css | 30 ++++------ nummi/main/templates/main/index.html | 86 ++++++++++++++-------------- nummi/main/templates/main/list.html | 34 +++++------ 3 files changed, 71 insertions(+), 79 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index e5b4b07..d094d26 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -94,27 +94,21 @@ main { grid-column: 2; grid-row: 1; overflow-x: hidden; - - display: grid; - grid-template-columns: max-content 1fr; - grid-gap: var(--gap); - h2.new { opacity: 0.8; } - & > * { - grid-column: 1 / -1; - } + .split { + display: grid; + column-gap: var(--gap); + row-gap: var(--gap); + grid-template-columns: 1fr; + @media (width > 720px) { + grid-template-columns: max-content 1fr; + } - @media (width > 720px) { - & > section { - &.accounts { - grid-column: 1; - } - &.categories { - grid-column: 2; - } + & > section > :first-child { + margin-top: 0; } } } @@ -159,7 +153,7 @@ nav { } } :is(nav, main) > :first-child, -main > section > :first-child { +main > section:first-child > :first-child { margin-top: 0; } footer { @@ -231,7 +225,7 @@ h1, h2, h3 { font-weight: 300; - margin-top: var(--gap); + margin-top: 1em; margin-bottom: 0.5em; line-height: 1cap; } diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 0fe5318..f88b5b0 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -8,53 +8,55 @@ {% css "main/css/plot.css" %} {% endblock %} {% block body %} -
    -

    {% translate "Accounts" %}

    - - - - - - - - - {% for acc in accounts %} +
    +
    +

    {% translate "Accounts" %}

    +
    {% translate "Account" %}{% translate "Balance" %}
    + - - - + + - {% empty %} - - - - {% endfor %} - - - - - - -
    {{ acc.icon|remix }} - {{ acc }} - {{ acc.statement_set.first.value|value }}{% translate "Account" %}{% translate "Balance" %}
    {% translate "No account" %}
    - {% translate "Create account" %} -
    -
    - {% if categories %} -
    -

    {% translate "Categories" %}

    - {% spaceless %} -

    - {% for cat in categories %} - {{ cat.icon|remix }}{{ cat }} + + + {% for acc in accounts %} + + {{ acc.icon|remix }} + + {{ acc }} + + {{ acc.statement_set.first.value|value }} + {% empty %} - {% translate "No category" %} + + {% translate "No account" %} + {% endfor %} -

    - {% endspaceless %} + + + + + {% translate "Create account" %} + + + +
    - {% endif %} + {% if categories %} +
    +

    {% translate "Categories" %}

    + {% spaceless %} +

    + {% for cat in categories %} + {{ cat.icon|remix }}{{ cat }} + {% empty %} + {% translate "No category" %} + {% endfor %} +

    + {% endspaceless %} +
    + {% endif %} + {% if history %}

    {% translate "History" %}

    diff --git a/nummi/main/templates/main/list.html b/nummi/main/templates/main/list.html index 57adec0..ee3050e 100644 --- a/nummi/main/templates/main/list.html +++ b/nummi/main/templates/main/list.html @@ -19,25 +19,21 @@

    {% block h2 %}{% endblock %}

    - + {% if account %} +

    + {{ account.icon|remix }}{{ account }} +

    + {% endif %} + {% if category %} +

    + {{ category.icon|remix }}{{ category }} +

    + {% endif %} + {% if search %} +

    + {% translate "Search" %} +

    + {% endif %} {% include "main/pagination.html" %} {% block table %}{% endblock %} {% include "main/pagination.html" %} From b3c9642adce2c528d69dbd2b316c2b19c60166d7 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 15:20:45 +0100 Subject: [PATCH 154/276] Update year history (remove year in plot lines) --- nummi/history/templates/history/plot.html | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 8501506..680686d 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -25,7 +25,13 @@ {% if date.sum_m or date.sum_p %} {% up_down_icon date.sum %} - {% month_url date.month %} + + {% if year %} + {% month_url date.month fmt="F" %} + {% else %} + {% month_url date.month %} + {% endif %} + {{ date.sum_m|pmrvalue }} {% plot_bar date.sum date.sum_m history.max.pm %} {% plot_bar date.sum date.sum_p history.max.pm %} @@ -45,7 +51,9 @@ - + {% if not year %} + + {% endif %} {% calendar_head %} @@ -53,7 +61,9 @@ {% regroup history.data by month.year as years_list %} {% for y, year in years_list reversed %} - + {% if not year %} + + {% endif %} {% for m in year %} {% if forloop.parentloop.last and forloop.first %} {% empty_calendar_cells_start m.month.month %} From cc551c536cc4f158eb299408361849877baccc2d Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 15:35:06 +0100 Subject: [PATCH 155/276] Change date format in transaction table on month and year page --- .../templates/transaction/transaction_table.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index a9a0ddf..4ae44a0 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -46,7 +46,15 @@ {% for invoice in trans.invoices %}{{ "attachment"|remix }}{% endfor %} {% regroup history.data by month.year as years_list %} - {% for y, year in years_list reversed %} + {% for y, y_data in years_list reversed %} {% if not year %} {% endif %} - {% for m in year %} + {% for m in y_data %} {% if forloop.parentloop.last and forloop.first %} {% empty_calendar_cells_start m.month.month %} {% endif %} From e4169bd1c39be99871984f2b7ef59c37fa41bdfa Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 15:52:36 +0100 Subject: [PATCH 157/276] Split columns for income and expenses --- .../locale/fr_FR/LC_MESSAGES/django.po | 4 +-- nummi/main/locale/fr_FR/LC_MESSAGES/django.po | 28 +++++++++--------- .../locale/fr_FR/LC_MESSAGES/django.po | 14 ++++----- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 1494 -> 1577 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 27 ++++++++++------- .../transaction/transaction_table.html | 17 ++++++----- .../templatetags/transaction_extras.py | 10 +++++++ 7 files changed, 59 insertions(+), 41 deletions(-) diff --git a/nummi/history/locale/fr_FR/LC_MESSAGES/django.po b/nummi/history/locale/fr_FR/LC_MESSAGES/django.po index 50fde9f..c0e51e2 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-02 15:52+0100\n" +"POT-Creation-Date: 2024-01-03 15:51+0100\n" "PO-Revision-Date: 2023-04-22 15:18+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -29,6 +29,6 @@ msgstr "Dépenses" msgid "Income" msgstr "Revenus" -#: .\history\templates\history\plot.html:48 +#: .\history\templates\history\plot.html:55 msgid "Year" msgstr "Année" diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.po b/nummi/main/locale/fr_FR/LC_MESSAGES/django.po index 32068a8..1404f9b 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-03 14:32+0100\n" +"POT-Creation-Date: 2024-01-03 15:51+0100\n" "PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -29,15 +29,15 @@ msgstr "Aller au contenu principal" msgid "Home" msgstr "Accueil" -#: .\main\templates\main\base.html:39 .\main\templates\main\index.html:60 +#: .\main\templates\main\base.html:39 msgid "Statements" msgstr "Relevés" -#: .\main\templates\main\base.html:45 .\main\templates\main\index.html:40 +#: .\main\templates\main\base.html:45 msgid "Transactions" msgstr "Transactions" -#: .\main\templates\main\base.html:51 +#: .\main\templates\main\base.html:51 .\main\templates\main\index.html:39 msgid "Create account" msgstr "Créer un compte" @@ -54,7 +54,7 @@ msgid "Create transaction" msgstr "Créer une transaction" #: .\main\templates\main\base.html:71 .\main\templates\main\list.html:10 -#: .\main\templates\main\list.html:36 +#: .\main\templates\main\list.html:34 msgid "Search" msgstr "Rechercher" @@ -105,31 +105,31 @@ msgstr "Créer" msgid "Save" msgstr "Enregistrer" -#: .\main\templates\main\index.html:12 +#: .\main\templates\main\index.html:13 msgid "Accounts" msgstr "Comptes" #: .\main\templates\main\index.html:17 -msgid "No account" -msgstr "Aucun compte" - -#: .\main\templates\main\index.html:23 msgid "Account" msgstr "Compte" -#: .\main\templates\main\index.html:24 +#: .\main\templates\main\index.html:18 msgid "Balance" msgstr "Solde" -#: .\main\templates\main\index.html:46 +#: .\main\templates\main\index.html:32 +msgid "No account" +msgstr "Aucun compte" + +#: .\main\templates\main\index.html:47 msgid "Categories" msgstr "Catégories" -#: .\main\templates\main\index.html:52 +#: .\main\templates\main\index.html:53 msgid "No category" msgstr "Aucune catégorie" -#: .\main\templates\main\index.html:66 +#: .\main\templates\main\index.html:62 msgid "History" msgstr "Historique" diff --git a/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po b/nummi/statement/locale/fr_FR/LC_MESSAGES/django.po index cd3180d..f969e8b 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 14:32+0100\n" +"POT-Creation-Date: 2024-01-03 15:51+0100\n" "PO-Revision-Date: 2023-04-22 15:22+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -38,7 +38,7 @@ msgid "Start value" msgstr "Valeur initiale" #: .\statement\models.py:29 -#: .\statement\templates\statement\statement_table.html:23 +#: .\statement\templates\statement\statement_table.html:30 msgid "Difference" msgstr "Différence" @@ -66,7 +66,7 @@ msgid "Statements" msgstr "Relevés" #: .\statement\templates\statement\statement_form.html:4 -#: .\statement\templates\statement\statement_table.html:29 +#: .\statement\templates\statement\statement_table.html:18 msgid "Create statement" msgstr "Créer un relevé" @@ -79,19 +79,19 @@ msgid "Categories" msgstr "Catégories" #: .\statement\templates\statement\statement_form.html:27 -#: .\statement\templates\statement\statement_table.html:24 +#: .\statement\templates\statement\statement_table.html:31 msgid "Transactions" msgstr "Transactions" -#: .\statement\templates\statement\statement_table.html:18 +#: .\statement\templates\statement\statement_table.html:25 msgid "Date" msgstr "Date" -#: .\statement\templates\statement\statement_table.html:20 +#: .\statement\templates\statement\statement_table.html:27 msgid "Account" msgstr "Compte" -#: .\statement\templates\statement\statement_table.html:22 +#: .\statement\templates\statement\statement_table.html:29 msgid "Value" msgstr "Valeur" diff --git a/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/transaction/locale/fr_FR/LC_MESSAGES/django.mo index 6d872a83dba4ee847d3dc60f6792146778c8c726..cf6c6811b2a425ceddb1e39654b793780693ed22 100644 GIT binary patch delta 739 zcmYk)y)Q#i7{~GBYQ42ey}wi}L>i;PV6Y%Mk{BdRHzzcq)!ve_bg@~8h*-qhK_vVI zh(uyVNJN@go0ufh$@f<`;z>?F=id9A^PK12K8K3o!h2ib$PfdJ7$Z<)OdQLo*msPn z#{&%D2{z#=HeeAWc!_Ozjmmq(I()?tmQeX+|M?fPZ^2ZUDAB3?E!2ogjG{K`Lv1jK zEjWrgzyzxB4C;Uj*oe!%E2w=lsP%aa<1PmA06VE~j+uYDmyoe8zg7PjIJcHkbW!!uOj3)BWT zs7@YG1zu2vOTK0Q`8TSOAV2!FVboVuZj_-z75ezm<_f+IR`s71}`m5fxVS zFmz}gP>0o<_#f1+h8k-3=Q_MXpHXj&h*Uf1XV%Q+P q&TgAT%1y7^t9xhM8J@Z={-wC9Nt^Yvl9^3w=B;aUIqMxpgLgmOX-8!M delta 655 zcmX}py)Q#i7{~GB_LWv~sh46BxwA?C0BI}+lZAxTP22>HBGIWy6NAAZO$Uh$Lnp#S zECz#w!D2A5Sj5O?;QMP&o%Fe%dvEV^p68rXPd+EguWqe^7;@ zq2>f>Z0C8@>Y)lwpz^br#3_v6ET(Y|Jr=iyQByXiM!QNpLM0xf3Z9`>bdCM^i2Teu z7Zv_MegB18(Jv}5Mm0UVsD)*aT4oHp(ZfE>Vv71^h8q={M%Gw;tO6 zPf?9uppNbeb!s|F<@FG1l#TlzYNXRXX*U;oxeO61&>91+8`Ke~Yc-^youE!*D^;9D yH&(62H8O-&H%PeEJtv>c1*^W94mP(6#nNRwH0ijxAiom$Mf)\n" "Language-Team: \n" @@ -23,7 +23,7 @@ msgstr "Transaction" #: .\transaction\models.py:19 .\transaction\models.py:89 #: .\transaction\templates\transaction\invoice_table.html:10 -#: .\transaction\templates\transaction\transaction_table.html:24 +#: .\transaction\templates\transaction\transaction_table.html:32 msgid "Name" msgstr "Nom" @@ -32,12 +32,11 @@ msgid "Description" msgstr "Description" #: .\transaction\models.py:23 -#: .\transaction\templates\transaction\transaction_table.html:25 msgid "Value" msgstr "Valeur" #: .\transaction\models.py:25 -#: .\transaction\templates\transaction\transaction_table.html:23 +#: .\transaction\templates\transaction\transaction_table.html:31 msgid "Date" msgstr "Date" @@ -46,7 +45,7 @@ msgid "Real date" msgstr "Date réelle" #: .\transaction\models.py:28 -#: .\transaction\templates\transaction\transaction_table.html:26 +#: .\transaction\templates\transaction\transaction_table.html:35 msgid "Trader" msgstr "Commerçant" @@ -55,7 +54,7 @@ msgid "Payment" msgstr "Paiement" #: .\transaction\models.py:38 -#: .\transaction\templates\transaction\transaction_table.html:28 +#: .\transaction\templates\transaction\transaction_table.html:37 msgid "Category" msgstr "Catégorie" @@ -64,7 +63,7 @@ msgid "Statement" msgstr "Relevé" #: .\transaction\models.py:48 -#: .\transaction\templates\transaction\transaction_table.html:31 +#: .\transaction\templates\transaction\transaction_table.html:40 msgid "Account" msgstr "Compte" @@ -124,7 +123,7 @@ msgid "History" msgstr "Historique" #: .\transaction\templates\transaction\transaction_form.html:5 -#: .\transaction\templates\transaction\transaction_table.html:37 +#: .\transaction\templates\transaction\transaction_table.html:25 msgid "Create transaction" msgstr "Créer une transaction" @@ -132,10 +131,18 @@ msgstr "Créer une transaction" msgid "New transaction" msgstr "Nouvelle transaction" -#: .\transaction\templates\transaction\transaction_table.html:77 +#: .\transaction\templates\transaction\transaction_table.html:33 +msgid "Expenses" +msgstr "Dépenses" + +#: .\transaction\templates\transaction\transaction_table.html:34 +msgid "Income" +msgstr "Recettes" + +#: .\transaction\templates\transaction\transaction_table.html:87 msgid "No transaction" msgstr "Aucune transaction" -#: .\transaction\templates\transaction\transaction_table.html:86 +#: .\transaction\templates\transaction\transaction_table.html:95 msgid "View all transactions" msgstr "Voir toutes les transactions" diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index 4ae44a0..1654e6f 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -1,4 +1,4 @@ -{% load main_extras %} +{% load main_extras transaction_extras %} {% load i18n %}
    {% translate "Year" %}{% translate "Year" %}
    {% year_url y %}{% year_url y %} - + {{ trans.name }} From e2f7a1dcc36553be75147c86b128575d0d15f9aa Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 15:37:48 +0100 Subject: [PATCH 156/276] Fix history calendar --- nummi/history/templates/history/plot.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 680686d..9d79dc8 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -59,12 +59,12 @@
    {% year_url y %}
    @@ -7,6 +7,7 @@ + {% if not category %} @@ -20,7 +21,7 @@ {% if new_transaction_url %} - @@ -29,7 +30,8 @@ - + + {% if not category %} @@ -59,7 +61,9 @@ + {% if trans.value >= 0 %}{% endif %} + {% if trans.value < 0 %}{% endif %} {% if not category %} {% if trans.category %} @@ -80,17 +84,14 @@ {% empty %} - + {% endfor %} {% if transactions_url %} - diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index 30cba01..313fb15 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -34,3 +34,13 @@ def year_url(context, year, cls=""): f"""""" f"""""" ) + + +@register.simple_tag(takes_context=True) +def tr_colspan(context): + ncol = 10 + if context.get("category"): + ncol -= 2 + if context.get("account"): + ncol -= 2 + return ncol From ef90d5280709972c1786b60d99f8ffdd57be0928 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 16:07:10 +0100 Subject: [PATCH 158/276] Fix history regarding category__budget --- nummi/account/views.py | 2 +- nummi/transaction/views.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nummi/account/views.py b/nummi/account/views.py index 446ac30..691bc78 100644 --- a/nummi/account/views.py +++ b/nummi/account/views.py @@ -41,7 +41,7 @@ class AccountUpdateView(NummiUpdateView): "new_statement", kwargs={"account": account.pk} ), "statements": _statements[:8], - "history": history(account.transaction_set), + "history": history(account.transaction_set.exclude(category__budget=False)), } diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 2421c12..ad917e2 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -153,8 +153,8 @@ class TransactionYearView(UserMixin, TransactionACMixin, YearArchiveView): def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) - return context_data | { - "history": history( - context_data["transactions"].exclude(category__budget=False) - ), - } + h_data = context_data["transactions"] + if "account" not in self.kwargs and "category" not in self.kwargs: + h_data = h_data.exclude(category__budget=False) + + return context_data | {"history": history(h_data)} From bf1d15574d1d81fee75325661adbcc49b4d3f6f2 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 16:14:54 +0100 Subject: [PATCH 159/276] Fix previous commit --- nummi/account/views.py | 2 +- nummi/transaction/views.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nummi/account/views.py b/nummi/account/views.py index 691bc78..446ac30 100644 --- a/nummi/account/views.py +++ b/nummi/account/views.py @@ -41,7 +41,7 @@ class AccountUpdateView(NummiUpdateView): "new_statement", kwargs={"account": account.pk} ), "statements": _statements[:8], - "history": history(account.transaction_set.exclude(category__budget=False)), + "history": history(account.transaction_set), } diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index ad917e2..6b1f56e 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -153,8 +153,8 @@ class TransactionYearView(UserMixin, TransactionACMixin, YearArchiveView): def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) - h_data = context_data["transactions"] - if "account" not in self.kwargs and "category" not in self.kwargs: + h_data = context_data.get("transactions") + if not (context_data.get("account") or context_data.get("category")): h_data = h_data.exclude(category__budget=False) return context_data | {"history": history(h_data)} From 08b234a0700b66f7ab1f422b03e1bab0a1d0f722 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 18:29:14 +0100 Subject: [PATCH 160/276] Add colors back to history --- nummi/history/templatetags/history_extras.py | 6 ++++-- nummi/main/static/main/css/main.css | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index 56b01c4..5289f5d 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -30,9 +30,11 @@ def empty_calendar_cells_end(n): @register.simple_tag def up_down_icon(val): if val > 0: - return remix("arrow-up-s") + return remix("arrow-up-s", "green w") elif val < 0: - return remix("arrow-down-s") + return remix("arrow-down-s", "red w") + + return "" @register.simple_tag diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index d094d26..f950aef 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -219,6 +219,21 @@ footer { } [class^="ri-"] { font-weight: normal; + + &.green, + &.red { + color: white; + &.green { + background: var(--green); + } + &.red { + background: var(--red); + } + border-radius: var(--radius); + height: 1.5rem; + width: 1.5rem; + line-height: 1.5rem; + } } h1, From 954ee9ce1714404039b9d03b47af1750957c4a16 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 18:35:31 +0100 Subject: [PATCH 161/276] Update big links styling --- nummi/main/static/main/css/main.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index f950aef..3094450 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -69,6 +69,13 @@ a { &.big-link { margin-right: 1em; + padding: 0 0.5em; + + color: white; + background: var(--green); + border-radius: var(--radius); + height: 1.5rem; + line-height: 1.5rem; [class^="ri-"] { margin-right: 0.5em; From 8db2720f7510c5a420b9376cda7e6210e79b088c Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 18:49:24 +0100 Subject: [PATCH 162/276] Remove create links from navbar; Add a link to create categories in the category list on the homepage --- nummi/main/static/main/css/main.css | 4 ++++ nummi/main/templates/main/base.html | 20 -------------------- nummi/main/templates/main/index.html | 25 +++++++++++-------------- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 3094450..b05218a 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -80,6 +80,10 @@ a { [class^="ri-"] { margin-right: 0.5em; } + &.add { + color: var(--text-link); + background: var(--bg-01); + } } } diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index e725fcc..a07113a 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -45,26 +45,6 @@ {% translate "Transactions" %} -
  • - {% translate "Create account" %} -
  • -
  • - {% translate "Create statement" %} -
  • -
  • - {% translate "Create category" %} -
  • -
  • - {% translate "Create transaction" %} -
  • + {% translate "Create transaction" %}
    {{ "attachment"|remix }} {% translate "Date" %} {% translate "Name" %}{% translate "Value" %}{% translate "Expenses" %}{% translate "Income" %} {% translate "Trader" %}{% translate "Category" %} {{ trans.name }} {{ trans.value|pmvalue }}{{ trans.trader|default_if_none:"" }}
    - {% translate "No transaction" %} - {% translate "No transaction" %}
    + {% translate "View all transactions" %}
    - {% if categories %} -
    -

    {% translate "Categories" %}

    - {% spaceless %} -

    - {% for cat in categories %} - {{ cat.icon|remix }}{{ cat }} - {% empty %} - {% translate "No category" %} - {% endfor %} -

    - {% endspaceless %} -
    - {% endif %} +
    +

    {% translate "Categories" %}

    + {% spaceless %} +

    + {% for cat in categories %} + {{ cat.icon|remix }}{{ cat }} + {% endfor %} + {{ "add"|remix }}{% translate "Create category" %} +

    + {% endspaceless %} +
    {% if history %}
    From b0716a65b72838e8d7f56318727a4af93a68364b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 3 Jan 2024 19:02:08 +0100 Subject: [PATCH 163/276] Add icons on calendar plot --- nummi/history/templates/history/plot.html | 4 ++-- nummi/history/templatetags/history_extras.py | 2 +- nummi/main/static/main/css/plot.css | 16 ++++++++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 9d79dc8..b96e510 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -70,8 +70,8 @@ {% endif %} {% if m %} + style="background-color: color-mix(in hsl, currentcolor {% calendar_opacity m.sum history.max.sum %}, white)" + title="{{ m.sum|pmrvalue }}">{% up_down_icon m.sum %} {% else %} {% endif %} diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index 5289f5d..0b02d24 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -9,7 +9,7 @@ register = template.Library() @register.simple_tag def calendar_opacity(v, vmax): - return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.3f}" + return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.0%}" @register.simple_tag diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index dcf1771..3f7cf6b 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -89,12 +89,6 @@ table.full-width col.bar { margin-top: var(--gap); font-feature-settings: var(--num); - .p { - background: var(--green); - } - .m { - background: var(--red); - } table { tbody tr { background: initial; @@ -104,6 +98,16 @@ table.full-width col.bar { &:not(:first-child) { border-top: none; } + + td { + text-align: center; + &.p { + color: var(--green); + } + &.m { + color: var(--red); + } + } } } } From 6bd83feafed8da6ce574684470dccd2a5d8ef5b7 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 Jan 2024 15:38:53 +0100 Subject: [PATCH 164/276] Update up-down icons --- nummi/history/templates/history/plot.html | 2 +- nummi/history/templatetags/history_extras.py | 6 +++--- nummi/main/static/main/css/main.css | 9 +++++++-- nummi/main/static/main/css/plot.css | 9 +++++++-- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index b96e510..6ef6804 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -70,7 +70,7 @@ {% endif %} {% if m %} {% up_down_icon m.sum %} {% else %} diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index 0b02d24..eaefea3 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -30,11 +30,11 @@ def empty_calendar_cells_end(n): @register.simple_tag def up_down_icon(val): if val > 0: - return remix("arrow-up-s", "green w") + return remix("arrow-up-s", "green") elif val < 0: - return remix("arrow-down-s", "red w") + return remix("arrow-down-s", "red") - return "" + return remix("equal", "white") @register.simple_tag diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index b05218a..35dd9d1 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -232,14 +232,19 @@ footer { font-weight: normal; &.green, - &.red { - color: white; + &.red, + &.white { + color: var(--bg); &.green { background: var(--green); } &.red { background: var(--red); } + &.white { + background: var(--bg-01); + color: var(--text); + } border-radius: var(--radius); height: 1.5rem; width: 1.5rem; diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 3f7cf6b..9a2bc7a 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -101,11 +101,16 @@ table.full-width col.bar { td { text-align: center; + background-color: color-mix( + in hsl, + var(--td-bg, var(--bg)) var(--opacity), + var(--bg) + ); &.p { - color: var(--green); + --td-bg: var(--green); } &.m { - color: var(--red); + --td-bg: var(--red); } } } From 218a6aca6f340fe4baf24486a0f7b0d7bf81b6f7 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 Jan 2024 16:05:38 +0100 Subject: [PATCH 165/276] Separate detail and edit view for account --- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 762 -> 810 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 30 +++++---- .../templates/account/account_detail.html | 30 +++++++++ .../templates/account/account_form.html | 18 ------ nummi/account/urls.py | 3 +- nummi/account/views.py | 37 +++++++++++- nummi/main/locale/fr_FR/LC_MESSAGES/django.mo | Bin 1828 -> 1687 bytes nummi/main/locale/fr_FR/LC_MESSAGES/django.po | 57 +++++++++--------- nummi/main/static/main/css/form.css | 1 + nummi/main/static/main/css/main.css | 14 +++-- nummi/main/templates/main/index.html | 8 ++- nummi/main/views.py | 5 ++ 12 files changed, 135 insertions(+), 68 deletions(-) create mode 100644 nummi/account/templates/account/account_detail.html diff --git a/nummi/account/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/account/locale/fr_FR/LC_MESSAGES/django.mo index 1829874bb523006c772335f1cf9474c1f6b15411..d0a97cf8b790f1183168d8f4c957f9c7d6f0f41f 100644 GIT binary patch delta 336 zcmXYrF-yZh7>4g+lBP|SHgvRjPEv5{;^N}ap@XYqpN_F5ljNk;Gz^SX>KSKKw&(gd!OKXeQ=Z`D>rjy$u&F}N< Q#XM%l7Eh}(tG~VLKNxZ#NdN!< delta 289 zcmXxfEfWDz6vpv$*g?5;EO z%x~_Ud(S=h7<~Qs+48nV)kue|Nknc$6*{vLMp(u+Hn5lP4|7N8u|LV3qI%A-iVLjc z68mID0<+k5h3tVnh8#HL>oe-&1vzL}EaDwCzymeV3)T3I^h`_VDntXYotB2vNB*U| WzBRL@9#REK*9}f{w{gj*F#Z8PtQB+s diff --git a/nummi/account/locale/fr_FR/LC_MESSAGES/django.po b/nummi/account/locale/fr_FR/LC_MESSAGES/django.po index e2b3dd5..e0baa15 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-02 15:52+0100\n" +"POT-Creation-Date: 2024-01-04 16:04+0100\n" "PO-Revision-Date: 2023-04-22 15:17+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -37,6 +37,22 @@ msgstr "Défaut" msgid "Accounts" msgstr "Comptes" +#: .\account\templates\account\account_detail.html:15 +msgid "Edit account" +msgstr "Modifier le compte" + +#: .\account\templates\account\account_detail.html:17 +msgid "Statements" +msgstr "Relevés" + +#: .\account\templates\account\account_detail.html:21 +msgid "Transactions" +msgstr "Transactions" + +#: .\account\templates\account\account_detail.html:26 +msgid "History" +msgstr "Historique" + #: .\account\templates\account\account_form.html:5 msgid "Create account" msgstr "Créer un compte" @@ -44,15 +60,3 @@ msgstr "Créer un compte" #: .\account\templates\account\account_form.html:8 msgid "New account" msgstr "Nouveau compte" - -#: .\account\templates\account\account_form.html:14 -msgid "Statements" -msgstr "Relevés" - -#: .\account\templates\account\account_form.html:18 -msgid "Transactions" -msgstr "Transactions" - -#: .\account\templates\account\account_form.html:23 -msgid "History" -msgstr "Historique" diff --git a/nummi/account/templates/account/account_detail.html b/nummi/account/templates/account/account_detail.html new file mode 100644 index 0000000..419669e --- /dev/null +++ b/nummi/account/templates/account/account_detail.html @@ -0,0 +1,30 @@ +{% extends "main/base.html" %} +{% load main_extras %} +{% load i18n %} +{% block title %}{{ object }} – {{ block.super }}{% endblock %} +{% block link %} + {{ block.super }} + {% css "main/css/form.css" %} + {% css "main/css/table.css" %} + {% css "main/css/plot.css" %} +{% endblock %} +{% block body %} +

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

    +

    + {% translate "Edit account" %} +

    +
    +

    {% translate "Statements" %}

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

    {% translate "Transactions" %}

    + {% include "transaction/transaction_table.html" %} +
    + {% if history %} +
    +

    {% translate "History" %}

    + {% include "history/plot.html" %} +
    + {% endif %} +{% endblock %} diff --git a/nummi/account/templates/account/account_form.html b/nummi/account/templates/account/account_form.html index eaba124..b24800e 100644 --- a/nummi/account/templates/account/account_form.html +++ b/nummi/account/templates/account/account_form.html @@ -8,21 +8,3 @@ {% translate "New account" %} {% endblock %} {% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} -{% block tables %} - {% if not form.instance|adding %} -
    -

    {% translate "Statements" %}

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

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} -
    - {% if history %} -
    -

    {% translate "History" %}

    - {% include "history/plot.html" %} -
    - {% endif %} - {% endif %} -{% endblock %} diff --git a/nummi/account/urls.py b/nummi/account/urls.py index d9b8c14..f6b4f2b 100644 --- a/nummi/account/urls.py +++ b/nummi/account/urls.py @@ -6,7 +6,8 @@ from . import views urlpatterns = [ path("new", views.AccountCreateView.as_view(), name="new_account"), - path("", views.AccountUpdateView.as_view(), name="account"), + path("", views.AccountDetailView.as_view(), name="account"), + path("/edit", views.AccountUpdateView.as_view(), name="edit_account"), path( "/transactions", views.AccountTListView.as_view(), diff --git a/nummi/account/views.py b/nummi/account/views.py index 446ac30..a07bd8b 100644 --- a/nummi/account/views.py +++ b/nummi/account/views.py @@ -1,7 +1,12 @@ from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from history.utils import history -from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView +from main.views import ( + NummiCreateView, + NummiDeleteView, + NummiDetailView, + NummiUpdateView, +) from statement.views import StatementListView from transaction.views import TransactionListView @@ -50,6 +55,36 @@ class AccountDeleteView(NummiDeleteView): pk_url_kwarg = "account" +class AccountDetailView(NummiDetailView): + model = Account + pk_url_kwarg = "account" + + def get_context_data(self, **kwargs): + _max = 8 + data = super().get_context_data(**kwargs) + account = data.get("object") + + _transactions = account.transaction_set.all() + if _transactions.count() > _max: + data["transactions_url"] = reverse_lazy( + "account_transactions", args=(account.pk,) + ) + _statements = account.statement_set.all() + if _statements.count() > _max: + data["statements_url"] = reverse_lazy( + "account_statements", args=(account.pk,) + ) + + return data | { + "transactions": _transactions[:8], + "new_statement_url": reverse_lazy( + "new_statement", kwargs={"account": account.pk} + ), + "statements": _statements[:8], + "history": history(account.transaction_set), + } + + class AccountMixin: def get_queryset(self): self.account = get_object_or_404( diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/main/locale/fr_FR/LC_MESSAGES/django.mo index c963419eca93ed0d64d1b1c6058b7de5609b7c13..258a1ed85a97ab8bc46848e7e6b7e75135ee0dd7 100644 GIT binary patch delta 694 zcmYk)J1j$C7{>9pEp@58x)h5+8-p0cNJ7|{7)VULkdvU*RH6(gu@gxn!s@bglo&fwyHxT`dt3t0QnN&LF9sFHMoJbSi~{h zb@ewGrhJd}_~`1NT>Tq1QU8w3_~q(rLuOTGzBT{7SQNET%$5656USZs5XLEwq83`h zYFt4kvNcryJVvpI>bH*#c#P_If!$cfM8G$@<3$5MXw?QjaT32#3ykwn{S-!U2J3JE zH7Urjh&#zpQ1KYcI7Kdt{b&*0yV)1_TV^b zfkjmRB&uH)dvOQ#6*Z=vRA@fFK&4$}pFY+_YM??_q{$UJ4VP0noldN4i;&!GOL+F#-`I=p_B~GU*3iWgDrEL Yxm?~`%j9h~oA#EyQg3*xbRPD90g5L*WB>pF delta 821 zcmYk)ziU%b7{>9_Bu&$%X=_?rqc&bTRGY=R)uDq{6qhVYCYKOSHDGSS?Y*K<2!i+z zT#AT8Td+uQv!$C*jDsLbtf3m-bI&dJd*t%#IM44-Pz_&W7WZ)w-=XS!K-K+(Q}`J*|2vN2PfXF@3^4l? zi8P*P*XK}wyg@ZBq>NXwf@<*G8#RB5$wTxv zMZRcd^Ge_))Cw=7j_f*K#EQ#5MHP%uN4tYs$R4WB0iMQ>s4M%1s&|N**TXZIp;=qk z%suoA>J${3tO8tRe*v6SKlF}h<=T~EmZ1h}sv}ouhYD?^z+kienB-H}K=0!*_y0jV z(W-I`y@)!(8Adj4BwmzO0$UBOw-)l*uzqM\n" "Language-Team: \n" @@ -37,37 +37,21 @@ msgstr "Relevés" msgid "Transactions" msgstr "Transactions" -#: .\main\templates\main\base.html:51 .\main\templates\main\index.html:39 -msgid "Create account" -msgstr "Créer un compte" - -#: .\main\templates\main\base.html:56 -msgid "Create statement" -msgstr "Créer un relevé" - -#: .\main\templates\main\base.html:61 -msgid "Create category" -msgstr "Créer une catégorie" - -#: .\main\templates\main\base.html:66 -msgid "Create transaction" -msgstr "Créer une transaction" - -#: .\main\templates\main\base.html:71 .\main\templates\main\list.html:10 +#: .\main\templates\main\base.html:51 .\main\templates\main\list.html:10 #: .\main\templates\main\list.html:34 msgid "Search" msgstr "Rechercher" -#: .\main\templates\main\base.html:74 +#: .\main\templates\main\base.html:54 msgid "Log out" msgstr "Se déconnecter" -#: .\main\templates\main\base.html:79 .\main\templates\main\form\login.html:6 +#: .\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:85 +#: .\main\templates\main\base.html:65 #, python-format msgid "Logged in as %(user)s" msgstr "Connecté en tant que %(user)s" @@ -117,22 +101,39 @@ msgstr "Compte" msgid "Balance" msgstr "Solde" -#: .\main\templates\main\index.html:32 +#: .\main\templates\main\index.html:19 .\main\templates\main\index.html:30 +msgid "Edit" +msgstr "Modifier" + +#: .\main\templates\main\index.html:34 msgid "No account" msgstr "Aucun compte" -#: .\main\templates\main\index.html:47 +#: .\main\templates\main\index.html:41 +msgid "Create account" +msgstr "Créer un compte" + +#: .\main\templates\main\index.html:48 msgid "Categories" msgstr "Catégories" -#: .\main\templates\main\index.html:53 -msgid "No category" -msgstr "Aucune catégorie" +#: .\main\templates\main\index.html:54 +msgid "Create category" +msgstr "Créer une catégorie" -#: .\main\templates\main\index.html:62 +#: .\main\templates\main\index.html:61 msgid "History" msgstr "Historique" -#: .\main\views.py:68 +#: .\main\views.py:69 msgid "was created successfully" msgstr "a été créé avec succès" + +#~ msgid "Create statement" +#~ msgstr "Créer un relevé" + +#~ msgid "Create transaction" +#~ msgstr "Créer une transaction" + +#~ msgid "No category" +#~ msgstr "Aucune catégorie" diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 1ee72a6..7dc2378 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -9,6 +9,7 @@ form { > table > tbody > tr > th { background: var(--bg-01); background-clip: padding-box; + border-right: 1px solid var(--gray); } tbody :is(input, select, textarea) { font: inherit; diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 35dd9d1..e81f77a 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -234,21 +234,25 @@ footer { &.green, &.red, &.white { - color: var(--bg); &.green { background: var(--green); + color: var(--bg); } &.red { background: var(--red); + color: var(--bg); } &.white { background: var(--bg-01); - color: var(--text); } border-radius: var(--radius); - height: 1.5rem; - width: 1.5rem; - line-height: 1.5rem; + height: 1.5em; + width: 1.5em; + line-height: 1.5em; + } + + h2 & { + margin-right: 0.5em; } } diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 6a42983..b637297 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -16,6 +16,7 @@ {% translate "Account" %} {% translate "Balance" %} + {% translate "Edit" %} @@ -26,16 +27,19 @@ {{ acc }} {{ acc.statement_set.first.value|value }} + + {% translate "Edit" %} + {% empty %} - {% translate "No account" %} + {% translate "No account" %} {% endfor %} - + {% translate "Create account" %} diff --git a/nummi/main/views.py b/nummi/main/views.py index e1d52a3..a6361ab 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -9,6 +9,7 @@ from django.utils.translation import gettext as _ from django.views.generic import ( CreateView, DeleteView, + DetailView, ListView, TemplateView, UpdateView, @@ -76,6 +77,10 @@ class NummiUpdateView(UserMixin, UpdateView): pass +class NummiDetailView(UserMixin, DetailView): + pass + + class NummiDeleteView(UserMixin, DeleteView): template_name = "main/confirm_delete.html" success_url = reverse_lazy("index") From 9d50dc715449120cfa7a69a9c7833c96a8011977 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 Jan 2024 16:51:48 +0100 Subject: [PATCH 166/276] Separate detail and edit view for category, update statement page --- .../locale/fr_FR/LC_MESSAGES/django.po | 10 ++-- .../templates/account/account_detail.html | 1 - nummi/account/views.py | 25 --------- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 899 -> 968 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 22 ++++---- .../templates/category/category_detail.html | 24 +++++++++ .../templates/category/category_form.html | 12 ----- .../templates/category/category_plot.html | 48 +++++++++++++----- nummi/category/templatetags/category.py | 18 +++++-- nummi/category/urls.py | 3 +- nummi/category/views.py | 14 ++++- nummi/main/locale/fr_FR/LC_MESSAGES/django.po | 14 ++--- nummi/main/static/main/css/plot.css | 2 +- nummi/main/templatetags/main_extras.py | 14 ++--- .../templates/statement/statement_form.html | 9 ++-- 15 files changed, 120 insertions(+), 96 deletions(-) create mode 100644 nummi/category/templates/category/category_detail.html diff --git a/nummi/account/locale/fr_FR/LC_MESSAGES/django.po b/nummi/account/locale/fr_FR/LC_MESSAGES/django.po index e0baa15..14f7e1a 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:04+0100\n" +"POT-Creation-Date: 2024-01-04 16:18+0100\n" "PO-Revision-Date: 2023-04-22 15:17+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -37,19 +37,19 @@ msgstr "Défaut" msgid "Accounts" msgstr "Comptes" -#: .\account\templates\account\account_detail.html:15 +#: .\account\templates\account\account_detail.html:13 msgid "Edit account" msgstr "Modifier le compte" -#: .\account\templates\account\account_detail.html:17 +#: .\account\templates\account\account_detail.html:16 msgid "Statements" msgstr "Relevés" -#: .\account\templates\account\account_detail.html:21 +#: .\account\templates\account\account_detail.html:20 msgid "Transactions" msgstr "Transactions" -#: .\account\templates\account\account_detail.html:26 +#: .\account\templates\account\account_detail.html:25 msgid "History" msgstr "Historique" diff --git a/nummi/account/templates/account/account_detail.html b/nummi/account/templates/account/account_detail.html index 419669e..92c3c7f 100644 --- a/nummi/account/templates/account/account_detail.html +++ b/nummi/account/templates/account/account_detail.html @@ -4,7 +4,6 @@ {% block title %}{{ object }} – {{ block.super }}{% endblock %} {% block link %} {{ block.super }} - {% css "main/css/form.css" %} {% css "main/css/table.css" %} {% css "main/css/plot.css" %} {% endblock %} diff --git a/nummi/account/views.py b/nummi/account/views.py index a07bd8b..902db10 100644 --- a/nummi/account/views.py +++ b/nummi/account/views.py @@ -24,31 +24,6 @@ class AccountUpdateView(NummiUpdateView): form_class = AccountForm pk_url_kwarg = "account" - def get_context_data(self, **kwargs): - _max = 8 - data = super().get_context_data(**kwargs) - account = data["form"].instance - - _transactions = account.transaction_set.all() - if _transactions.count() > _max: - data["transactions_url"] = reverse_lazy( - "account_transactions", args=(account.pk,) - ) - _statements = account.statement_set.all() - if _statements.count() > _max: - data["statements_url"] = reverse_lazy( - "account_statements", args=(account.pk,) - ) - - return data | { - "transactions": _transactions[:8], - "new_statement_url": reverse_lazy( - "new_statement", kwargs={"account": account.pk} - ), - "statements": _statements[:8], - "history": history(account.transaction_set), - } - class AccountDeleteView(NummiDeleteView): model = Account diff --git a/nummi/category/locale/fr_FR/LC_MESSAGES/django.mo b/nummi/category/locale/fr_FR/LC_MESSAGES/django.mo index 32338f9c0ed8d8e5f6168003298a21f3684d5ae9..53d58b5e6341c6055989605e6b469824c309374e 100644 GIT binary patch delta 417 zcmYk&u};EJ6vpvW3KRt~!GI<@7#BBpSD6V<;LxO{Tw^T3Ix&!_lY zxyLP>=I>`1u%2TP7pVHzeEp6!)*o0m8`~F?5-)!855IU!{*fx&_syz!glgafn|O*X z?BWLY^6vvw-3ZmlE%Mj{!wx=T17}!be|u)4j^2+5Ux*uPU86jJ?1M+;TE-S z7taq^V17o`iZE@qwrl+2jt%lbYT-L!=Hmxd*pg;d@G*yV)bl3l`!=e94suzSn+_6U zfFq=!%~6dk(OHiXp^mi4zYwZ`LJM>ndiV!xtQe0~OdIKM_{XVh-6D5!$60dx0~w\n" "Language-Team: \n" @@ -38,6 +38,18 @@ msgstr "Budget" msgid "Categories" msgstr "Catégories" +#: .\category\templates\category\category_detail.html:12 +msgid "Edit category" +msgstr "Modifier la catégorie" + +#: .\category\templates\category\category_detail.html:15 +msgid "Transactions" +msgstr "Transactions" + +#: .\category\templates\category\category_detail.html:20 +msgid "History" +msgstr "Historique" + #: .\category\templates\category\category_form.html:5 msgid "Create category" msgstr "Créer une catégorie" @@ -46,14 +58,6 @@ msgstr "Créer une catégorie" msgid "New category" msgstr "Nouvelle catégorie" -#: .\category\templates\category\category_form.html:13 -msgid "Transactions" -msgstr "Transactions" - -#: .\category\templates\category\category_form.html:18 -msgid "History" -msgstr "Historique" - #: .\category\templates\category\category_plot.html:15 msgid "Expenses" msgstr "Dépenses" diff --git a/nummi/category/templates/category/category_detail.html b/nummi/category/templates/category/category_detail.html new file mode 100644 index 0000000..b0ea18f --- /dev/null +++ b/nummi/category/templates/category/category_detail.html @@ -0,0 +1,24 @@ +{% extends "main/base.html" %} +{% load main_extras i18n %} +{% block title %}{{ object }} – {{ block.super }}{% endblock %} +{% block link %} + {{ block.super }} + {% css "main/css/table.css" %} + {% css "main/css/plot.css" %} +{% endblock %} +{% block body %} +

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

    +

    + {% translate "Edit category" %} +

    +
    +

    {% translate "Transactions" %}

    + {% include "transaction/transaction_table.html" %} +
    + {% if history %} +
    +

    {% translate "History" %}

    + {% include "history/plot.html" %} +
    + {% endif %} +{% endblock %} diff --git a/nummi/category/templates/category/category_form.html b/nummi/category/templates/category/category_form.html index f4aaa6d..7e2748e 100644 --- a/nummi/category/templates/category/category_form.html +++ b/nummi/category/templates/category/category_form.html @@ -8,15 +8,3 @@ {% translate "New category" %} {% endblock %} {% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %} -{% block tables %} -
    -

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} -
    - {% if history %} -
    -

    {% translate "History" %}

    - {% include "history/plot.html" %} -
    - {% endif %} -{% endblock %} diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index 63577af..c152c10 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -1,4 +1,4 @@ -{% load main_extras %} +{% load main_extras category %} {% load i18n %}
    @@ -34,14 +34,14 @@ - + @@ -51,11 +51,11 @@ {% endif %} {% if cat.sum > 0 %}
    - {{ cat.sum|pmrvalue }} + {{ cat.sum|pmvalue }}
    {% endif %} - + {% empty %} @@ -64,16 +64,16 @@ {% endfor %} {% endspaceless %} - {% if categories %} - + + {% if categories %} - + @@ -81,13 +81,35 @@
    {% if total > 0 %}
    - {{ total|pmrvalue }} + {{ total|pmvalue }}
    {% endif %} - + - - {% endif %} + {% endif %} + {% if statement %} + + + + + + + + + {% endif %} +
    {% if cat.category %}{{ cat.category__icon|remix }}{% endif %} {{ cat.sum_m|pmrvalue }}{{ cat.sum_m|pmvalue }} {% if cat.sum_m %}
    {% endif %} {% if cat.sum < 0 %}
    - {{ cat.sum|pmrvalue }} + {{ cat.sum|pmvalue }}
    {% endif %}
    {{ cat.sum_p|pmrvalue }}{{ cat.sum_p|pmvalue }}
    {% translate "Total" %}{{ total_m|pmrvalue }}{{ total_m|pmvalue }}
    {% if total < 0 %}
    - {{ total|pmrvalue }} + {{ total|pmvalue }}
    {% endif %}
    {{ total_p|pmrvalue }}{{ total_p|pmvalue }}
    {% translate "Expected total" %}{{ total|check:statement.diff }} + {% if statement.diff < 0 %} +
    + {{ statement.diff|pmvalue }} +
    + {% endif %} +
    + {% if statement.diff >= 0 %} +
    + {{ statement.diff|pmvalue }} +
    + {% endif %} +
    diff --git a/nummi/category/templatetags/category.py b/nummi/category/templatetags/category.py index 5f76341..aaf3bf9 100644 --- a/nummi/category/templatetags/category.py +++ b/nummi/category/templatetags/category.py @@ -1,15 +1,17 @@ from django import template from django.db import models from django.db.models.functions import Greatest +from main.templatetags.main_extras import remix register = template.Library() @register.inclusion_tag("category/category_plot.html") -def category_plot(transactions, **kwargs): +def category_plot(transactions, budget=True, **kwargs): + if budget: + transactions = transactions.exclude(category__budget=False) categories = ( - transactions.exclude(category__budget=False) - .values("category", "category__name", "category__icon") + transactions.values("category", "category__name", "category__icon") .annotate( sum=models.Sum("value"), sum_m=models.Sum("value", filter=models.Q(value__lt=0)), @@ -29,3 +31,13 @@ def category_plot(transactions, **kwargs): total=models.Sum("sum"), ) ) + + +@register.filter +def check(s, diff): + if s is None: + s = 0 + if s == diff: + return remix("check", "green") + else: + return remix("close", "red") diff --git a/nummi/category/urls.py b/nummi/category/urls.py index 64b8ae9..9936065 100644 --- a/nummi/category/urls.py +++ b/nummi/category/urls.py @@ -5,7 +5,8 @@ from . import views urlpatterns = [ path("new", views.CategoryCreateView.as_view(), name="new_category"), - path("", views.CategoryUpdateView.as_view(), name="category"), + path("", views.CategoryDetailView.as_view(), name="category"), + path("/edit", views.CategoryUpdateView.as_view(), name="edit_category"), path( "/transactions", views.CategoryTListView.as_view(), diff --git a/nummi/category/views.py b/nummi/category/views.py index 36fe0d0..f01d908 100644 --- a/nummi/category/views.py +++ b/nummi/category/views.py @@ -1,7 +1,12 @@ from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from history.utils import history -from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView +from main.views import ( + NummiCreateView, + NummiDeleteView, + NummiDetailView, + NummiUpdateView, +) from transaction.views import TransactionListView from .forms import CategoryForm @@ -18,10 +23,15 @@ class CategoryUpdateView(NummiUpdateView): form_class = CategoryForm pk_url_kwarg = "category" + +class CategoryDetailView(NummiDetailView): + model = Category + pk_url_kwarg = "category" + def get_context_data(self, **kwargs): _max = 8 data = super().get_context_data(**kwargs) - category = data["form"].instance + category = data["object"] data["transactions"] = category.transaction_set.all()[:_max] if len(data["transactions"]) == _max: diff --git a/nummi/main/locale/fr_FR/LC_MESSAGES/django.po b/nummi/main/locale/fr_FR/LC_MESSAGES/django.po index fbf05c3..4ff140a 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:04+0100\n" +"POT-Creation-Date: 2024-01-04 16:18+0100\n" "PO-Revision-Date: 2023-04-23 08:03+0200\n" "Last-Translator: Edgar P. Burkhart \n" "Language-Team: \n" @@ -101,27 +101,27 @@ msgstr "Compte" msgid "Balance" msgstr "Solde" -#: .\main\templates\main\index.html:19 .\main\templates\main\index.html:30 +#: .\main\templates\main\index.html:19 .\main\templates\main\index.html:31 msgid "Edit" msgstr "Modifier" -#: .\main\templates\main\index.html:34 +#: .\main\templates\main\index.html:36 msgid "No account" msgstr "Aucun compte" -#: .\main\templates\main\index.html:41 +#: .\main\templates\main\index.html:43 msgid "Create account" msgstr "Créer un compte" -#: .\main\templates\main\index.html:48 +#: .\main\templates\main\index.html:50 msgid "Categories" msgstr "Catégories" -#: .\main\templates\main\index.html:54 +#: .\main\templates\main\index.html:56 msgid "Create category" msgstr "Créer une catégorie" -#: .\main\templates\main\index.html:61 +#: .\main\templates\main\index.html:63 msgid "History" msgstr "Historique" diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 9a2bc7a..4a9eb36 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -8,9 +8,9 @@ table.full-width col.bar { td.bar { position: relative; padding: 0; + overflow: hidden; @media (width < 720px) { width: 0; - overflow: hidden; } div { diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index a85d8db..fd29a10 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -8,16 +8,16 @@ register = template.Library() @register.filter def value(val, pm=False, r=2): - if not val: + if val is None: return "" _prefix = "" _suffix = " €" - _val = formats.number_format(round(val, r), r, use_l10n=True, force_grouping=True) + _val = formats.number_format(val, decimal_pos=r, use_l10n=True, force_grouping=True) if val > 0: if pm: _prefix += "+ " - else: + elif val < 0: _val = _val[1:] _prefix += "− " @@ -39,14 +39,6 @@ def remix(icon, cls=""): return mark_safe(f"""""") -@register.filter -def check(sum, diff): - if sum == diff: - return remix("check", "green") - else: - return remix("close", "red") - - @register.filter def extension(file): return file.name.split(".")[-1].upper() diff --git a/nummi/statement/templates/statement/statement_form.html b/nummi/statement/templates/statement/statement_form.html index b09e993..f5e04c8 100644 --- a/nummi/statement/templates/statement/statement_form.html +++ b/nummi/statement/templates/statement/statement_form.html @@ -6,10 +6,7 @@ {% block h2_new %} {% translate "New statement" %} {% endblock %} -{% block h2 %} - {{ form.instance.sum|check:form.instance.diff }} - {{ form.instance }} -{% endblock %} +{% block h2 %}{{ form.instance }}{% endblock %} {% block pre %} {% if account %}

    @@ -21,10 +18,10 @@ {% if not form.instance|adding %}

    {% translate "Categories" %}

    - {% category_plot transactions %} + {% category_plot transactions budget=False statement=object %}
    -

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

    +

    {% translate "Transactions" %}

    {% include "transaction/transaction_table.html" %}
    {% endif %} From ca7cd790b51045ab9743d00dc39f998794cd0ddc Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 Jan 2024 16:58:47 +0100 Subject: [PATCH 167/276] Update check icons in statement table --- .../category/templates/category/category_plot.html | 2 +- nummi/category/templatetags/category.py | 11 ----------- .../templates/statement/statement_form.html | 2 +- .../templates/statement/statement_table.html | 9 ++------- nummi/statement/templatetags/statement_extras.py | 14 ++++++++++++++ 5 files changed, 18 insertions(+), 20 deletions(-) create mode 100644 nummi/statement/templatetags/statement_extras.py diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index c152c10..2b76bc5 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -1,4 +1,4 @@ -{% load main_extras category %} +{% load main_extras statement_extras %} {% load i18n %}
    diff --git a/nummi/category/templatetags/category.py b/nummi/category/templatetags/category.py index aaf3bf9..b085bf2 100644 --- a/nummi/category/templatetags/category.py +++ b/nummi/category/templatetags/category.py @@ -1,7 +1,6 @@ from django import template from django.db import models from django.db.models.functions import Greatest -from main.templatetags.main_extras import remix register = template.Library() @@ -31,13 +30,3 @@ def category_plot(transactions, budget=True, **kwargs): total=models.Sum("sum"), ) ) - - -@register.filter -def check(s, diff): - if s is None: - s = 0 - if s == diff: - return remix("check", "green") - else: - return remix("close", "red") diff --git a/nummi/statement/templates/statement/statement_form.html b/nummi/statement/templates/statement/statement_form.html index f5e04c8..cfe210b 100644 --- a/nummi/statement/templates/statement/statement_form.html +++ b/nummi/statement/templates/statement/statement_form.html @@ -1,5 +1,5 @@ {% extends "main/form/base.html" %} -{% load i18n main_extras category %} +{% load i18n main_extras statement_extras category %} {% block title_new %} {% translate "Create statement" %} {% endblock %} diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index 514dc16..32a83f3 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -1,5 +1,4 @@ -{% load main_extras %} -{% load i18n %} +{% load i18n main_extras statement_extras %}
    @@ -34,11 +33,7 @@ {% for snap in statements %} - {% if snap.sum == snap.diff %} - - {% else %} - - {% endif %} + diff --git a/nummi/statement/templatetags/statement_extras.py b/nummi/statement/templatetags/statement_extras.py new file mode 100644 index 0000000..a8631d1 --- /dev/null +++ b/nummi/statement/templatetags/statement_extras.py @@ -0,0 +1,14 @@ +from django import template +from main.templatetags.main_extras import remix + +register = template.Library() + + +@register.filter +def check(s, diff): + if s is None: + s = 0 + if s == diff: + return remix("check", "green") + else: + return remix("close", "red") From e41b9898624a14c6172e6a5544ae62f9edc9e0fe Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 Jan 2024 17:04:37 +0100 Subject: [PATCH 168/276] Add creation links to statement and transaction list pages --- nummi/statement/templates/statement/statement_list.html | 1 + nummi/transaction/templates/transaction/transaction_list.html | 1 + 2 files changed, 2 insertions(+) diff --git a/nummi/statement/templates/statement/statement_list.html b/nummi/statement/templates/statement/statement_list.html index 73bc595..cd19d8b 100644 --- a/nummi/statement/templates/statement/statement_list.html +++ b/nummi/statement/templates/statement/statement_list.html @@ -7,5 +7,6 @@ {% translate "Statements" %} {% endblock %} {% block table %} + {% url "new_statement" as new_statement_url %} {% include "statement/statement_table.html" %} {% endblock %} diff --git a/nummi/transaction/templates/transaction/transaction_list.html b/nummi/transaction/templates/transaction/transaction_list.html index a7e8dea..c75751c 100644 --- a/nummi/transaction/templates/transaction/transaction_list.html +++ b/nummi/transaction/templates/transaction/transaction_list.html @@ -7,5 +7,6 @@ {% translate "Transactions" %} {% endblock %} {% block table %} + {% url "new_transaction" as new_transaction_url %} {% include "transaction/transaction_table.html" %} {% endblock %} From 67e71b717b16527dc4add88f0edd53fed9d0642f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 Jan 2024 17:16:13 +0100 Subject: [PATCH 169/276] Fix back link --- nummi/main/static/main/css/main.css | 2 +- nummi/main/templates/main/list.html | 34 +++++++++++-------- .../transaction_archive_month.html | 6 ++-- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index e81f77a..2a236d0 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -311,7 +311,7 @@ ul.messages { grid-column: 2; text-align: right; - .big-link { + a { margin-right: 0; margin-left: 1em; [class^="ri-"] { diff --git a/nummi/main/templates/main/list.html b/nummi/main/templates/main/list.html index ee3050e..ddde7ec 100644 --- a/nummi/main/templates/main/list.html +++ b/nummi/main/templates/main/list.html @@ -19,20 +19,26 @@

    {% block h2 %}{% endblock %}

    - {% if account %} -

    - {{ account.icon|remix }}{{ account }} -

    - {% endif %} - {% if category %} -

    - {{ category.icon|remix }}{{ category }} -

    - {% endif %} - {% if search %} -

    - {% translate "Search" %} -

    + {% if account or category or search %} + {% endif %} {% include "main/pagination.html" %} {% block table %}{% endblock %} diff --git a/nummi/transaction/templates/transaction/transaction_archive_month.html b/nummi/transaction/templates/transaction/transaction_archive_month.html index 5b85799..7d9691d 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_month.html +++ b/nummi/transaction/templates/transaction/transaction_archive_month.html @@ -4,15 +4,13 @@ {{ block.super }} {% css "main/css/plot.css" %} {% endblock %} +{% block name %}{{ month|date:"F Y"|capfirst }}{% endblock %} {% block h2 %}{{ month|date:"F Y"|capfirst }}{% endblock %} {% block backlinks %} {{ block.super }} {% if account or category %}

    - - {% translate "Back" %}{{ "arrow-go-back"|remix }} - + {% translate "Back" %}{{ "arrow-go-back"|remix }}

    {% endif %} {% endblock %} From 63258147ee108736b429b831f706a83b95b941e4 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 Jan 2024 18:33:56 +0100 Subject: [PATCH 170/276] Add icons to navbar --- nummi/main/static/main/css/main.css | 22 +++++++++++++++++----- nummi/main/templates/main/base.html | 22 ++++++++++++++++------ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 2a236d0..d532a1c 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -1,4 +1,5 @@ @import "https://rsms.me/inter/inter.css"; +@import "https://cdn.jsdelivr.net/npm/remixicon@4.0.0/fonts/remixicon.css"; *, *::before, @@ -152,13 +153,22 @@ nav { font-weight: 500; } } - display: block; + display: grid; + grid-template-columns: 1fr max-content; + align-items: baseline; + + [class^="ri-"] { + height: 1.5em; + width: 1.5em; + line-height: 1.5em; + border-radius: var(--radius); + } + &.cur { font-weight: 550; - &::after { - content: "◎"; - position: absolute; - right: 0; + [class^="ri-"] { + background: var(--text-link); + color: var(--bg); } } } @@ -229,6 +239,8 @@ footer { } } [class^="ri-"] { + display: inline-block; + text-align: center; font-weight: normal; &.green, diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index a07113a..1ac1bb0 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -12,7 +12,6 @@ {% block link %} {% css "main/css/main.css" %} - {% css "main/remixicon/remixicon.css" %} {% endblock %} @@ -31,27 +30,38 @@
  • {% translate "Home" %} + accesskey="h"> + {% translate "Home" %} + {{ "home"|remix }} +
  • - {% translate "Statements" %} + {% translate "Statements" %} + {{ "file"|remix }}
  • - {% translate "Transactions" %} + {% translate "Transactions" %} + {{ "receipt"|remix }}
  • {% translate "Search" %} + accesskey="r"> + {% translate "Search" %} + {{ "search"|remix }} +
  • - {% translate "Log out" %} + + {% translate "Log out" %} + {{ "close-circle"|remix }} +
  • {% else %}
  • From 7369b36ab112c00726969e886ff885b9341187cc Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 Jan 2024 18:45:17 +0100 Subject: [PATCH 171/276] Move connected as item in navbar --- nummi/main/templates/main/base.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 1ac1bb0..1b01ac0 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -57,6 +57,9 @@ {{ "search"|remix }}
  • +
  • + {% blocktranslate %}Logged in as {{ user }}{% endblocktranslate %} +
  • {% translate "Log out" %} @@ -70,11 +73,6 @@
  • {% endif %} - {% if user.is_authenticated %} -

    - {% blocktranslate %}Logged in as {{ user }}{% endblocktranslate %} -

    - {% endif %} {% endspaceless %} {% endblock %} From a6e84fbc1381ae01e8119c653c11f472e4ae98d1 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Thu, 4 Jan 2024 18:45:42 +0100 Subject: [PATCH 172/276] Fix back link styling on year page --- .../templates/transaction/transaction_archive_year.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/transaction/templates/transaction/transaction_archive_year.html b/nummi/transaction/templates/transaction/transaction_archive_year.html index 4b979d8..f8afb5e 100644 --- a/nummi/transaction/templates/transaction/transaction_archive_year.html +++ b/nummi/transaction/templates/transaction/transaction_archive_year.html @@ -10,7 +10,7 @@ {{ block.super }} {% if account or category %}

    - {% translate "Back" %}{{ "arrow-go-back"|remix }} + {% translate "Back" %}{{ "arrow-go-back"|remix }}

    {% endif %} {% endblock %} From 0ff191e0a4eb0e2f307f32a0ed762baed874aad1 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 5 Jan 2024 08:56:16 +0100 Subject: [PATCH 173/276] Fix account table display on small devices --- nummi/main/static/main/css/main.css | 2 +- nummi/main/static/main/css/table.css | 1 + nummi/main/templates/main/index.html | 64 ++++++++++++++-------------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index d532a1c..f562459 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -114,7 +114,7 @@ main { display: grid; column-gap: var(--gap); row-gap: var(--gap); - grid-template-columns: 1fr; + grid-template-columns: 100%; @media (width > 720px) { grid-template-columns: max-content 1fr; } diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index 3e38ad6..c2c9fec 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -1,6 +1,7 @@ .table, form { overflow-x: auto; + width: 100%; } table { border-collapse: collapse; diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index b637297..c5b457b 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -11,40 +11,42 @@

    {% translate "Accounts" %}

    -
    {{ "check"|remix }}{{ "close"|remix }}{{ snap.sum|check:snap.diff }} {% if snap.file %}{{ "attachment"|remix }}{% endif %}
    - - - - - - - - - {% for acc in accounts %} +
    +
    {% translate "Account" %}{% translate "Balance" %}{% translate "Edit" %}
    + - - - - + + + + + + {% for acc in accounts %} + + + + + + + {% empty %} + + + + {% endfor %} + + + + - {% empty %} - - - - {% endfor %} - - - - - - -
    {{ acc.icon|remix }} - {{ acc }} - {{ acc.statement_set.first.value|value }} - {% translate "Edit" %} + {% translate "Account" %}{% translate "Balance" %}{% translate "Edit" %}
    {{ acc.icon|remix }} + {{ acc }} + {{ acc.statement_set.first.value|value }} + {% translate "Edit" %} +
    {% translate "No account" %}
    + {% translate "Create account" %}
    {% translate "No account" %}
    - {% translate "Create account" %} -
    + + +

    {% translate "Categories" %}

    From ad020e476b523e153e1494444bb475d9c1a61841 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 5 Jan 2024 09:00:08 +0100 Subject: [PATCH 174/276] Add icon to log in link in navbar --- nummi/main/templates/main/base.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 1b01ac0..2a497f9 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -69,7 +69,10 @@ {% else %}
  • {% translate "Log in" %} + href="{% url "login" %}"> + {% translate "Log in" %} + {{ "user"|remix }} +
  • {% endif %} From ee3ec2152767113b81049ff2dbea88b8739a2a83 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Fri, 5 Jan 2024 09:11:38 +0100 Subject: [PATCH 175/276] Update styling on invoice table --- nummi/transaction/templates/transaction/invoice_table.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/transaction/templates/transaction/invoice_table.html b/nummi/transaction/templates/transaction/invoice_table.html index 444509c..ac9e154 100644 --- a/nummi/transaction/templates/transaction/invoice_table.html +++ b/nummi/transaction/templates/transaction/invoice_table.html @@ -32,7 +32,7 @@ {% endfor %} - + {% translate "Create invoice" %} From e93f6f5d2baec805378bb1b066e0b86cdd902b9e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 23 Mar 2024 10:52:28 +0100 Subject: [PATCH 176/276] Fix empty accounts producing error 500 --- nummi/main/templates/main/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index c5b457b..1c278b0 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -27,7 +27,9 @@ {{ acc }} - {{ acc.statement_set.first.value|value }} + + {% if acc.statement_set.first %}{{ acc.statement_set.first.value|value }}{% endif %} + {% translate "Edit" %} From 18a58783c8c0fbaf4a17eb60110eca70e712b0b1 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 23 Mar 2024 10:56:22 +0100 Subject: [PATCH 177/276] Fix form table header background (fix #15) --- nummi/main/static/main/css/form.css | 1 - 1 file changed, 1 deletion(-) diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 7dc2378..470220a 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -8,7 +8,6 @@ form ul.errorlist { form { > table > tbody > tr > th { background: var(--bg-01); - background-clip: padding-box; border-right: 1px solid var(--gray); } tbody :is(input, select, textarea) { From 50ae922a9992d59602695e8569ffcf42c56f2e97 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sat, 23 Mar 2024 11:51:37 +0100 Subject: [PATCH 178/276] Add total line in account list on index page --- nummi/main/static/main/css/plot.css | 4 ---- nummi/main/static/main/css/table.css | 3 +++ nummi/main/templates/main/index.html | 6 ++++++ nummi/main/templatetags/main_extras.py | 7 +++++++ nummi/main/views.py | 3 ++- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 4a9eb36..3ec218b 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -77,10 +77,6 @@ table.full-width col.bar { background: #eeeeff; } } - - tfoot { - background: var(--bg-01); - } } .calendar { diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index c2c9fec..e93c777 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -45,6 +45,9 @@ table { font-weight: 300; } } + tfoot tr:not(.new) { + background: var(--bg-01); + } } .date, diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 1c278b0..19a5b54 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -41,6 +41,12 @@ {% endfor %} + + {{ "functions"|remix }} + {% translate "Total" %} + {{ accounts|balance|value }} + + {% translate "Create account" %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index fd29a10..8234b3f 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -59,3 +59,10 @@ def css(href): return mark_safe( f"""""" ) + + +@register.filter +def balance(accounts): + return sum( + statement.value for acc in accounts if (statement := acc.statement_set.first()) + ) diff --git a/nummi/main/views.py b/nummi/main/views.py index a6361ab..72e6840 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -26,9 +26,10 @@ class IndexView(LoginRequiredMixin, TemplateView): _max = 8 _transactions = Transaction.objects.filter(user=self.request.user) _statements = Statement.objects.filter(user=self.request.user) + _accounts = Account.objects.filter(user=self.request.user) res = { - "accounts": Account.objects.filter(user=self.request.user), + "accounts": _accounts, "transactions": _transactions[:_max], "categories": Category.objects.filter(user=self.request.user), "statements": _statements[:_max], From cf25fd1826e2028f1a3ac67abd7e158b47313c59 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Sun, 29 Dec 2024 10:38:25 +0100 Subject: [PATCH 179/276] Enable archiving accounts --- nummi/account/forms.py | 1 + .../account/migrations/0003_account_archived.py | 17 +++++++++++++++++ nummi/account/models.py | 1 + nummi/main/static/main/css/table.css | 3 +++ nummi/main/static/main/js/index.js | 8 ++++++++ nummi/main/templates/main/index.html | 8 +++++++- nummi/main/templatetags/main_extras.py | 5 +++++ 7 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 nummi/account/migrations/0003_account_archived.py create mode 100644 nummi/main/static/main/js/index.js diff --git a/nummi/account/forms.py b/nummi/account/forms.py index 3225ab0..4b66f90 100644 --- a/nummi/account/forms.py +++ b/nummi/account/forms.py @@ -10,4 +10,5 @@ class AccountForm(NummiForm): "name", "icon", "default", + "archived", ] diff --git a/nummi/account/migrations/0003_account_archived.py b/nummi/account/migrations/0003_account_archived.py new file mode 100644 index 0000000..564297f --- /dev/null +++ b/nummi/account/migrations/0003_account_archived.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2 on 2024-12-29 09:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("account", "0002_alter_account_table"), + ] + + operations = [ + migrations.AddField( + model_name="account", + name="archived", + field=models.BooleanField(default=False, verbose_name="Archived"), + ), + ] diff --git a/nummi/account/models.py b/nummi/account/models.py index 4709ef5..5321ad5 100644 --- a/nummi/account/models.py +++ b/nummi/account/models.py @@ -15,6 +15,7 @@ class Account(UserModel): verbose_name=_("Icon"), ) default = models.BooleanField(default=False, verbose_name=_("Default")) + archived = models.BooleanField(default=False, verbose_name=_("Archived")) def save(self, *args, **kwargs): if self.default: diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index e93c777..478dd33 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -33,6 +33,9 @@ table { border-style: dashed; } } + &:not(.show-archive) tr.archived { + display: none; + } td, th { padding: 0 var(--gap); diff --git a/nummi/main/static/main/js/index.js b/nummi/main/static/main/js/index.js new file mode 100644 index 0000000..d506bf1 --- /dev/null +++ b/nummi/main/static/main/js/index.js @@ -0,0 +1,8 @@ +for (let table of document.querySelectorAll("table")) { + let btn = table.querySelector("button.show-archive"); + if (btn) { + btn.addEventListener("click", (event) => { + table.classList.toggle("show-archive"); + }); + } +} diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 19a5b54..5a55b85 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -6,6 +6,7 @@ {{ block.super }} {% css "main/css/table.css" %} {% css "main/css/plot.css" %} + {% js "main/js/index.js" %} {% endblock %} {% block body %}
    @@ -22,7 +23,7 @@ {% for acc in accounts %} - + {{ acc.icon|remix }} {{ acc }} @@ -52,6 +53,11 @@ {% translate "Create account" %} + + + + +
    diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 8234b3f..15cc7d0 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -61,6 +61,11 @@ def css(href): ) +@register.simple_tag +def js(href): + return mark_safe(f"""""") + + @register.filter def balance(accounts): return sum( From 93c4b43fa39f0875de95c3b9e3a705d832b9b86b Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 30 Dec 2024 16:11:43 +0100 Subject: [PATCH 180/276] Improve account list Closes #19 --- nummi/main/static/main/css/main.css | 22 +++++++++- nummi/main/static/main/css/table.css | 3 -- nummi/main/static/main/js/index.js | 16 ++++--- nummi/main/templates/main/index.html | 64 +++++++--------------------- 4 files changed, 46 insertions(+), 59 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index f562459..602f2de 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -116,7 +116,7 @@ main { row-gap: var(--gap); grid-template-columns: 100%; @media (width > 720px) { - grid-template-columns: max-content 1fr; + grid-template-columns: minmax(20rem, max-content) 1fr; } & > section > :first-child { @@ -334,3 +334,23 @@ ul.messages { } } } + +.accounts { + display: grid; + grid-row-gap: 0.5rem; + + dl { + margin: 0; + .account { + padding: 0.5rem; + margin-bottom: 0.5rem; + border: 1px solid var(--gray); + + display: grid; + grid-template-columns: 1fr min-content; + } + &:not(.show-archive) .account.archived { + display: none; + } + } +} diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index 478dd33..e93c777 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -33,9 +33,6 @@ table { border-style: dashed; } } - &:not(.show-archive) tr.archived { - display: none; - } td, th { padding: 0 var(--gap); diff --git a/nummi/main/static/main/js/index.js b/nummi/main/static/main/js/index.js index d506bf1..88438b2 100644 --- a/nummi/main/static/main/js/index.js +++ b/nummi/main/static/main/js/index.js @@ -1,8 +1,10 @@ -for (let table of document.querySelectorAll("table")) { - let btn = table.querySelector("button.show-archive"); - if (btn) { - btn.addEventListener("click", (event) => { - table.classList.toggle("show-archive"); - }); - } +let accounts = document.querySelector(".accounts"); +let toggle = accounts.querySelector("input#show-archived-accounts"); +let dl = accounts.querySelector("dl"); + +dl.classList.toggle("show-archive", toggle.checked); +if (toggle) { + toggle.addEventListener("change", (event) => { + dl.classList.toggle("show-archive", toggle.checked); + }); } diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 5a55b85..01006f1 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -12,55 +12,23 @@

    {% translate "Accounts" %}

    -
    - - - - - - - - - - {% for acc in accounts %} - - - - - - - {% empty %} - - - - {% endfor %} - - - - - - - - - - - - - - - -
    {% translate "Account" %}{% translate "Balance" %}{% translate "Edit" %}
    {{ acc.icon|remix }} - {{ acc }} - - {% if acc.statement_set.first %}{{ acc.statement_set.first.value|value }}{% endif %} - - {% translate "Edit" %} -
    {% translate "No account" %}
    {{ "functions"|remix }}{% translate "Total" %}{{ accounts|balance|value }}
    - {% translate "Create account" %} -
    - -
    +
    + {% for acc in accounts %} + + {% endfor %} +
    +
    + +
    + {% translate "Create account" %}

    {% translate "Categories" %}

    From 46ea394422e086e589843de3eb00430d88cf05a7 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 30 Dec 2024 16:35:03 +0100 Subject: [PATCH 181/276] Allow orphan transactions Closes #18 --- nummi/main/templates/main/confirm_delete.html | 1 + nummi/statement/forms.py | 4 +- .../templates/statement/confirm_delete.html | 5 +++ nummi/statement/views.py | 1 + ...0003_alter_transaction_account_and_more.py | 38 +++++++++++++++++++ nummi/transaction/models.py | 12 ++++-- 6 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 nummi/statement/templates/statement/confirm_delete.html create mode 100644 nummi/transaction/migrations/0003_alter_transaction_account_and_more.py diff --git a/nummi/main/templates/main/confirm_delete.html b/nummi/main/templates/main/confirm_delete.html index 19dd77b..db59293 100644 --- a/nummi/main/templates/main/confirm_delete.html +++ b/nummi/main/templates/main/confirm_delete.html @@ -14,6 +14,7 @@

    {% blocktranslate %}Are you sure you want do delete {{ object }} ?{% endblocktranslate %}

    + {% block additionalinfo %}{% endblock %} {{ form }}
    {% translate "Cancel" %} diff --git a/nummi/statement/forms.py b/nummi/statement/forms.py index 328d921..8467680 100644 --- a/nummi/statement/forms.py +++ b/nummi/statement/forms.py @@ -23,7 +23,9 @@ class StatementForm(NummiForm): self.fields["transactions"] = forms.MultipleChoiceField( choices=( ((_transaction.id), _transaction) - for _transaction in Transaction.objects.filter(user=_user) + for _transaction in Transaction.objects.filter( + user=_user, statement=None + ) ), label=_("Add transactions"), required=False, diff --git a/nummi/statement/templates/statement/confirm_delete.html b/nummi/statement/templates/statement/confirm_delete.html new file mode 100644 index 0000000..ebdaf4b --- /dev/null +++ b/nummi/statement/templates/statement/confirm_delete.html @@ -0,0 +1,5 @@ +{% extends "main/confirm_delete.html" %} +{% load i18n %} +{% block additionalinfo %} +

    {% blocktranslate %}This will delete all transactions in this statement.{% endblocktranslate %}

    +{% endblock %} diff --git a/nummi/statement/views.py b/nummi/statement/views.py index 435cb85..4d393d2 100644 --- a/nummi/statement/views.py +++ b/nummi/statement/views.py @@ -52,6 +52,7 @@ class StatementUpdateView(NummiUpdateView): class StatementDeleteView(NummiDeleteView): + template_name = "statement/confirm_delete.html" model = Statement pk_url_kwarg = "statement" diff --git a/nummi/transaction/migrations/0003_alter_transaction_account_and_more.py b/nummi/transaction/migrations/0003_alter_transaction_account_and_more.py new file mode 100644 index 0000000..6346f5a --- /dev/null +++ b/nummi/transaction/migrations/0003_alter_transaction_account_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2 on 2024-12-30 15:29 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("statement", "0002_alter_statement_table"), + ("account", "0003_account_archived"), + ("transaction", "0002_alter_invoice_table_alter_transaction_table"), + ] + + operations = [ + migrations.AlterField( + model_name="transaction", + name="account", + field=models.ForeignKey( + blank=True, + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="account.account", + verbose_name="Account", + ), + ), + migrations.AlterField( + model_name="transaction", + name="statement", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="statement.statement", + verbose_name="Statement", + ), + ), + ] diff --git a/nummi/transaction/models.py b/nummi/transaction/models.py index 46cdd20..f831f22 100644 --- a/nummi/transaction/models.py +++ b/nummi/transaction/models.py @@ -40,11 +40,15 @@ class Transaction(UserModel): statement = models.ForeignKey( Statement, on_delete=models.CASCADE, + blank=True, + null=True, verbose_name=_("Statement"), ) account = models.ForeignKey( Account, on_delete=models.CASCADE, + blank=True, + null=True, verbose_name=_("Account"), editable=False, ) @@ -54,11 +58,13 @@ class Transaction(UserModel): prev_self = Transaction.objects.get(pk=self.pk) else: prev_self = None - self.account = self.statement.account + if self.statement: + self.account = self.statement.account super().save(*args, **kwargs) - if prev_self is not None: + if prev_self is not None and prev_self.statement: prev_self.statement.update_sum() - self.statement.update_sum() + if self.statement: + self.statement.update_sum() def __str__(self): return f"{self.name}" From a3e598acb6c97952c15e8afffccb581a1847d999 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 30 Dec 2024 16:57:00 +0100 Subject: [PATCH 182/276] Improve history view with outliers --- nummi/history/templates/history/plot.html | 74 ++++++++++---------- nummi/history/templatetags/history_extras.py | 2 +- nummi/history/utils.py | 8 +-- nummi/main/static/main/css/plot.css | 2 +- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index 6ef6804..bd7c2e6 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -2,6 +2,43 @@ {% load history_extras %} {% load transaction_extras %} {% load i18n %} +
    + + + + {% if not year %} + + {% endif %} + {% calendar_head %} + + + + {% regroup history.data by month.year as years_list %} + {% for y, y_data in years_list reversed %} + + {% if not year %} + + {% endif %} + {% for m in y_data %} + {% if forloop.parentloop.last and forloop.first %} + {% empty_calendar_cells_start m.month.month %} + {% endif %} + {% if m %} + + {% else %} + + {% endif %} + {% if forloop.parentloop.first and forloop.last %} + {% empty_calendar_cells_end m.month.month %} + {% endif %} + {% endfor %} + + {% endfor %} + +
    {% translate "Year" %}
    {% year_url y %}{% up_down_icon m.sum %}
    +
    @@ -47,40 +84,3 @@
    -
    - - - - {% if not year %} - - {% endif %} - {% calendar_head %} - - - - {% regroup history.data by month.year as years_list %} - {% for y, y_data in years_list reversed %} - - {% if not year %} - - {% endif %} - {% for m in y_data %} - {% if forloop.parentloop.last and forloop.first %} - {% empty_calendar_cells_start m.month.month %} - {% endif %} - {% if m %} - - {% else %} - - {% endif %} - {% if forloop.parentloop.first and forloop.last %} - {% empty_calendar_cells_end m.month.month %} - {% endif %} - {% endfor %} - - {% endfor %} - -
    {% translate "Year" %}
    {% year_url y %}{% up_down_icon m.sum %}
    -
    diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index eaefea3..7a83898 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -9,7 +9,7 @@ register = template.Library() @register.simple_tag def calendar_opacity(v, vmax): - return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.0%}" + return f"{math.sin(min(1, math.fabs(v/vmax))*math.pi/2):.0%}" @register.simple_tag diff --git a/nummi/history/utils.py b/nummi/history/utils.py index 320c10a..3606959 100644 --- a/nummi/history/utils.py +++ b/nummi/history/utils.py @@ -1,6 +1,6 @@ import datetime -from django.db.models import Max, Min, Q, Sum +from django.db.models import Avg, Q, StdDev, Sum from django.db.models.functions import Abs, TruncMonth @@ -38,10 +38,10 @@ def history(transaction_set): "max": { "pm": max( _history.aggregate( - max=Max("sum_p", default=0), - min=-Min("sum_m", default=0), + max=Avg("sum_p", default=0) + StdDev("sum_p", default=0), + min=Avg("sum_m", default=0) - StdDev("sum_m", default=0), ).values(), ), - "sum": _history.aggregate(max=Max(Abs("sum")))["max"], + "sum": _history.aggregate(max=Avg(Abs("sum")) + StdDev(Abs("sum")))["max"], }, } diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index 3ec218b..ecc1fdc 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -82,7 +82,7 @@ table.full-width col.bar { .calendar { overflow-x: auto; - margin-top: var(--gap); + margin-bottom: var(--gap); font-feature-settings: var(--num); table { From 28ac9c8ef7e616dec285689ae917e50bf0a09625 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 30 Dec 2024 17:29:42 +0100 Subject: [PATCH 183/276] Improve calendar table --- nummi/history/templates/history/plot.html | 8 +++++--- nummi/history/templatetags/history_extras.py | 5 +++++ nummi/main/static/main/css/plot.css | 10 +++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/nummi/history/templates/history/plot.html b/nummi/history/templates/history/plot.html index bd7c2e6..ecf24ed 100644 --- a/nummi/history/templates/history/plot.html +++ b/nummi/history/templates/history/plot.html @@ -3,13 +3,14 @@ {% load transaction_extras %} {% load i18n %}
    - +
    {% if not year %} {% endif %} {% calendar_head %} + @@ -24,16 +25,17 @@ {% empty_calendar_cells_start m.month.month %} {% endif %} {% if m %} - {% else %} - + {% endif %} {% if forloop.parentloop.first and forloop.last %} {% empty_calendar_cells_end m.month.month %} {% endif %} {% endfor %} + {% endfor %} diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index 7a83898..8ad71bf 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -60,3 +60,8 @@ def calendar_head(): th = (f"""""" for month in months) return mark_safe("".join(th)) + + +@register.filter +def sum_year(y_data): + return sum(y["sum"] for y in y_data) diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index ecc1fdc..c1f0756 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -95,13 +95,16 @@ table.full-width col.bar { border-top: none; } - td { + td.month { text-align: center; background-color: color-mix( in hsl, var(--td-bg, var(--bg)) var(--opacity), var(--bg) ); + padding: 0; + width: 4rem; + height: 4rem; &.p { --td-bg: var(--green); } @@ -109,6 +112,11 @@ table.full-width col.bar { --td-bg: var(--red); } } + td.total { + text-align: right; + font-weight: 650; + font-feature-settings: var(--num); + } } } } From 5d6bd9ea2b36dda56fa991464dd1d6c351b36c03 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 30 Dec 2024 17:56:55 +0100 Subject: [PATCH 184/276] Improve history view using tenth centile times 125% for min max --- nummi/history/utils.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/nummi/history/utils.py b/nummi/history/utils.py index 3606959..c61fc88 100644 --- a/nummi/history/utils.py +++ b/nummi/history/utils.py @@ -1,6 +1,6 @@ import datetime -from django.db.models import Avg, Q, StdDev, Sum +from django.db.models import Q, Sum from django.db.models.functions import Abs, TruncMonth @@ -36,12 +36,16 @@ def history(transaction_set): return { "data": _data, "max": { - "pm": max( - _history.aggregate( - max=Avg("sum_p", default=0) + StdDev("sum_p", default=0), - min=Avg("sum_m", default=0) - StdDev("sum_m", default=0), - ).values(), - ), - "sum": _history.aggregate(max=Avg(Abs("sum")) + StdDev(Abs("sum")))["max"], + "pm": 125 + * max( + _history.order_by("-sum_p")[len(_history) // 10]["sum_p"], + _history.order_by("-sum_m")[len(_history) // 10]["sum_m"], + ) + / 100, + "sum": 125 + * _history.annotate(abs_sum=Abs("sum")).order_by("-abs_sum")[ + len(_history) // 10 + ]["abs_sum"] + / 100, }, } From 886e6826504f2709d1fd1bc4815b23e95c29ce09 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 30 Dec 2024 18:14:10 +0100 Subject: [PATCH 185/276] Fix history plot on mobile --- nummi/main/static/main/css/plot.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nummi/main/static/main/css/plot.css b/nummi/main/static/main/css/plot.css index c1f0756..43a7880 100644 --- a/nummi/main/static/main/css/plot.css +++ b/nummi/main/static/main/css/plot.css @@ -5,13 +5,14 @@ table.full-width col.bar { .plot { overflow-x: auto; + table { + min-width: 40rem; + } + td.bar { position: relative; padding: 0; overflow: hidden; - @media (width < 720px) { - width: 0; - } div { position: absolute; From 7109142b4ef38d954ca01fecf16ad81e7174cc3e Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Mon, 30 Dec 2024 19:20:53 +0100 Subject: [PATCH 186/276] Fix bug in history --- nummi/history/templatetags/history_extras.py | 27 ++++++++++++-------- nummi/history/utils.py | 25 ++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/nummi/history/templatetags/history_extras.py b/nummi/history/templatetags/history_extras.py index 8ad71bf..53b7b82 100644 --- a/nummi/history/templatetags/history_extras.py +++ b/nummi/history/templatetags/history_extras.py @@ -9,6 +9,8 @@ register = template.Library() @register.simple_tag def calendar_opacity(v, vmax): + if v is None: + return "0%" return f"{math.sin(min(1, math.fabs(v/vmax))*math.pi/2):.0%}" @@ -29,6 +31,8 @@ def empty_calendar_cells_end(n): @register.simple_tag def up_down_icon(val): + if val is None: + return "" if val > 0: return remix("arrow-up-s", "green") elif val < 0: @@ -41,15 +45,18 @@ def up_down_icon(val): def plot_bar(s, sum_pm, s_max): _res = "" - if sum_pm: - _w = abs(sum_pm / s_max) - _res += f"""
    """ - if sum_pm is not None and s * sum_pm > 0: - _w = abs(s / s_max) - _res += ( - f"""
    """ - f"""{pmrvalue(s)}
    """ - ) + if s_max: + if sum_pm: + _w = abs(sum_pm / s_max) + _res += f"""
    """ + if sum_pm is not None and s * sum_pm > 0: + _w = abs(s / s_max) + _res += ( + f"""
    """ + f"""{pmrvalue(s)}
    """ + ) + else: + _res += "
    " return mark_safe(_res) @@ -64,4 +71,4 @@ def calendar_head(): @register.filter def sum_year(y_data): - return sum(y["sum"] for y in y_data) + return sum(y["sum"] or 0 for y in y_data) diff --git a/nummi/history/utils.py b/nummi/history/utils.py index c61fc88..1238469 100644 --- a/nummi/history/utils.py +++ b/nummi/history/utils.py @@ -1,7 +1,7 @@ import datetime from django.db.models import Q, Sum -from django.db.models.functions import Abs, TruncMonth +from django.db.models.functions import Abs, Greatest, TruncMonth def history(transaction_set): @@ -14,15 +14,19 @@ def history(transaction_set): _first_month = _transaction_month.last()["month"] _last_month = _transaction_month.first()["month"] - _history = _transaction_month.annotate( - sum_p=Sum("value", filter=Q(value__gt=0)), - sum_m=Sum("value", filter=Q(value__lt=0)), - sum=Sum("value"), - ).order_by("-month") + _history = ( + _transaction_month.annotate( + sum_p=Sum("value", filter=Q(value__gt=0), default=0), + sum_m=Sum("value", filter=Q(value__lt=0), default=0), + sum=Sum("value"), + ) + .annotate(max_sum=Greatest("sum_p", Abs("sum_m"))) + .order_by("-month") + ) _data = [ _history.filter(month=datetime.date(y, m + 1, 1)).first() - or {"month": datetime.date(y, m + 1, 1), "sum": 0} + or {"month": datetime.date(y, m + 1, 1), "sum": None} for y in range( _first_month.year, _last_month.year + 1, @@ -37,10 +41,9 @@ def history(transaction_set): "data": _data, "max": { "pm": 125 - * max( - _history.order_by("-sum_p")[len(_history) // 10]["sum_p"], - _history.order_by("-sum_m")[len(_history) // 10]["sum_m"], - ) + * _history.order_by("-max_sum")[len(_history.exclude(max_sum=0)) // 10][ + "max_sum" + ] / 100, "sum": 125 * _history.annotate(abs_sum=Abs("sum")).order_by("-abs_sum")[ From 63f1cf4e4d2c655d6d49fbc81893f62769104c43 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 15:03:01 +0100 Subject: [PATCH 187/276] Update statement display (cards instead of table) --- .../templates/account/account_detail.html | 4 - nummi/account/views.py | 10 +- nummi/main/static/main/css/main.css | 48 +++++++++ nummi/main/static/main/css/table.css | 22 ++--- nummi/main/templates/main/list.html | 1 + nummi/main/templatetags/main_extras.py | 9 +- .../templates/statement/statement_table.html | 98 ++++++------------- 7 files changed, 99 insertions(+), 93 deletions(-) diff --git a/nummi/account/templates/account/account_detail.html b/nummi/account/templates/account/account_detail.html index 92c3c7f..ce317ba 100644 --- a/nummi/account/templates/account/account_detail.html +++ b/nummi/account/templates/account/account_detail.html @@ -16,10 +16,6 @@

    {% translate "Statements" %}

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

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} -
    {% if history %}

    {% translate "History" %}

    diff --git a/nummi/account/views.py b/nummi/account/views.py index 902db10..103832b 100644 --- a/nummi/account/views.py +++ b/nummi/account/views.py @@ -35,15 +35,10 @@ class AccountDetailView(NummiDetailView): pk_url_kwarg = "account" def get_context_data(self, **kwargs): - _max = 8 + _max = 6 data = super().get_context_data(**kwargs) account = data.get("object") - _transactions = account.transaction_set.all() - if _transactions.count() > _max: - data["transactions_url"] = reverse_lazy( - "account_transactions", args=(account.pk,) - ) _statements = account.statement_set.all() if _statements.count() > _max: data["statements_url"] = reverse_lazy( @@ -51,11 +46,10 @@ class AccountDetailView(NummiDetailView): ) return data | { - "transactions": _transactions[:8], "new_statement_url": reverse_lazy( "new_statement", kwargs={"account": account.pk} ), - "statements": _statements[:8], + "statements": _statements[:_max], "history": history(account.transaction_set), } diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 602f2de..2eee924 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -103,6 +103,7 @@ footer { padding: 2rem; } main { + position: relative; grid-column: 2; grid-row: 1; overflow-x: hidden; @@ -354,3 +355,50 @@ ul.messages { } } } + +ul.statements { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); + grid-auto-rows: 1fr; + grid-gap: var(--gap); + list-style: none; + padding: 0; + + li { + display: grid; + grid-row-gap: var(--gap); + padding: var(--gap); + border: var(--gray) 1px solid; + text-align: right; + + > * { + display: grid; + align-items: center; + + &.date :first-child { + font-size: 2rem; + } + &.value :first-child { + font-weight: 650; + } + &.icon { + span { + margin: auto; + } + } + &.account a { + overflow: hidden; + text-overflow: "."; + white-space: nowrap; + } + a span::after { + content: "\a0"; + } + } + + &.new, + &.more { + border-style: dashed; + } + } +} diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index e93c777..f663bad 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -48,20 +48,20 @@ table { tfoot tr:not(.new) { background: var(--bg-01); } + .l { + text-align: left; + } + .r, + .value { + text-align: right; + } + .c, + .date { + text-align: center; + } } .date, .value { font-feature-settings: var(--num); } -.l { - text-align: left; -} -.r, -.value { - text-align: right; -} -.c, -.date { - text-align: center; -} diff --git a/nummi/main/templates/main/list.html b/nummi/main/templates/main/list.html index ddde7ec..7f40df7 100644 --- a/nummi/main/templates/main/list.html +++ b/nummi/main/templates/main/list.html @@ -43,4 +43,5 @@ {% include "main/pagination.html" %} {% block table %}{% endblock %} {% include "main/pagination.html" %} + {% block body_more %}{% endblock %} {% endblock %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index 15cc7d0..e4494e8 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -35,8 +35,13 @@ def pmrvalue(val): @register.filter -def remix(icon, cls=""): - return mark_safe(f"""""") +def remix(icon, *args): + return remixnl(f"{icon}-line", *args) + + +@register.filter +def remixnl(icon, cls=""): + return mark_safe(f"""""") @register.filter diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index 32a83f3..9c2de04 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -1,71 +1,33 @@ {% load i18n main_extras statement_extras %} -
    -
    {% translate "Year" %}{% translate "Total" %}
    {% up_down_icon m.sum %}{{ y_data|sum_year|pmrvalue }}
    {month:02d}
    - - - - {% if not account %} - - - {% endif %} - - - - {% if new_statement_url %} - - - - {% endif %} - - - - - {% if not account %} - - {% endif %} - - - - - - - {% for snap in statements %} - - - - - {% if not account %} - - - {% endif %} - - - - - {% empty %} - - - - {% endfor %} - - {% if statements_url %} - - - - - +
    +
    - {% translate "Create statement" %} -
    {{ "check"|remix }}{{ "attachment"|remix }}{% translate "Date" %}{% translate "Account" %}{% translate "Value" %}{% translate "Difference" %}{% translate "Transactions" %}
    {{ snap.sum|check:snap.diff }} - {% if snap.file %}{{ "attachment"|remix }}{% endif %} - - - - - {{ snap.account.icon|remix }} - {{ snap.account }} - {{ snap.value|value }}{{ snap.diff|pmvalue }}{{ snap.sum|pmvalue }}
    {% translate "No statement" %}
    - {% translate "View all statements" %} -
    + {% for sta in statements %} +
  • + {{ sta.date|date:"d" }} + {{ sta.date|date:"F y" }} + {{ sta.value|value }} + {{ sta.diff|pmvalue }} + {% if not account %} + + {% endif %} + {{ sta.sum|check:sta.diff }} +
  • + {% endfor %} + {% if statements_url %} +
  • + + {{ "gallery-view"|remixnl }}{% translate "View all statements" %} + +
  • + {% endif %} +
    From c754e869fcbcebdf9b7cf70d0532f266e637af1c Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 15:14:42 +0100 Subject: [PATCH 188/276] Include icons in links --- .../templates/category/category_plot.html | 16 +++++--------- nummi/main/static/main/css/main.css | 12 +++++----- .../transaction/transaction_table.html | 22 ++++++------------- .../templatetags/transaction_extras.py | 6 ++--- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index 2b76bc5..3381976 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -4,14 +4,13 @@ - - + @@ -23,17 +22,14 @@ - {% empty %} - + {% endfor %} {% endspaceless %} @@ -67,7 +63,7 @@ {% if categories %} - + - {% if not category %} - - - {% endif %} - {% if not account %} - - - {% endif %} + {% if not category %}{% endif %} + {% if not account %}{% endif %} {% if new_transaction_url %} @@ -34,10 +28,10 @@ {% if not category %} - + {% endif %} {% if not account %} - + {% endif %} @@ -67,18 +61,16 @@ {% if not category %} {% if trans.category %} - {% else %} - + {% endif %} {% endif %} {% if not account %} - {% endif %} diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index 313fb15..00f17d4 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -38,9 +38,9 @@ def year_url(context, year, cls=""): @register.simple_tag(takes_context=True) def tr_colspan(context): - ncol = 10 + ncol = 8 if context.get("category"): - ncol -= 2 + ncol -= 1 if context.get("account"): - ncol -= 2 + ncol -= 1 return ncol From 412cf94f936438c9a0368f68649456d70dd6c81f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 15:31:07 +0100 Subject: [PATCH 189/276] Add last statements to home page --- nummi/main/static/main/css/main.css | 1 + nummi/main/templates/main/index.html | 24 ++++++++++++++---------- nummi/main/views.py | 13 ++++++++----- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index f06189f..33a6c0c 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -336,6 +336,7 @@ ul.messages { .accounts { display: grid; grid-row-gap: 0.5rem; + grid-auto-rows: min-content; dl { margin: 0; diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 01006f1..484c51a 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -30,18 +30,22 @@ {% translate "Create account" %} -
    -

    {% translate "Categories" %}

    - {% spaceless %} -

    - {% for cat in categories %} - {{ cat.icon|remix }}{{ cat }} - {% endfor %} - {{ "add"|remix }}{% translate "Create category" %} -

    - {% endspaceless %} +
    +

    {% translate "Statements" %}

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

    {% translate "Categories" %}

    + {% spaceless %} +

    + {% for cat in categories %} + {{ cat.icon|remix }}{{ cat }} + {% endfor %} + {{ "add"|remix }}{% translate "Create category" %} +

    + {% endspaceless %} +
    {% if history %}

    {% translate "History" %}

    diff --git a/nummi/main/views.py b/nummi/main/views.py index 72e6840..2786ed9 100644 --- a/nummi/main/views.py +++ b/nummi/main/views.py @@ -25,20 +25,23 @@ class IndexView(LoginRequiredMixin, TemplateView): def get_context_data(self, **kwargs): _max = 8 _transactions = Transaction.objects.filter(user=self.request.user) - _statements = Statement.objects.filter(user=self.request.user) _accounts = Account.objects.filter(user=self.request.user) + _statements = ( + Statement.objects.filter(user=self.request.user) + .exclude(account__archived=True) + .order_by("account__id", "-date") + .distinct("account__id") + ) res = { "accounts": _accounts, - "transactions": _transactions[:_max], "categories": Category.objects.filter(user=self.request.user), - "statements": _statements[:_max], + "statements": _statements, "history": history(_transactions.exclude(category__budget=False)), } if _transactions.count() > _max: res["transactions_url"] = reverse_lazy("transactions") - if _statements.count() > _max: - res["statements_url"] = reverse_lazy("statements") + res["statements_url"] = reverse_lazy("statements") return super().get_context_data(**kwargs) | res From cb08cb3d460d3754d620dab3eb2ae013af1a1ed4 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 15:41:02 +0100 Subject: [PATCH 190/276] Improve account list --- nummi/main/static/main/css/main.css | 7 +++++++ nummi/main/templates/main/index.html | 20 ++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 33a6c0c..56f6e2a 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -347,6 +347,13 @@ ul.messages { display: grid; grid-template-columns: 1fr min-content; + &.new, + &.more { + border-style: dashed; + } + &.more label span::after { + content: "\2002"; + } } &:not(.show-archive) .account.archived { display: none; diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 484c51a..011e2ef 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -16,19 +16,27 @@ {% for acc in accounts %} {% endfor %} + + -
    - - -
    - {% translate "Create account" %}

    {% translate "Statements" %}

    From 86a354f353c8eec6dcf6ca0ac593315ae3288d8a Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 15:44:33 +0100 Subject: [PATCH 191/276] Fix category plot on statement page --- nummi/category/templates/category/category_plot.html | 3 +-- nummi/main/static/main/css/main.css | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index 3381976..3c95171 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -19,7 +19,7 @@ {% spaceless %} {% for cat in categories %}
    - - {% endif %} - {% if statement %} + {% if statement and statement.diff != statement.sum %} From 7d4dbdc0df2ed634405ec7fdf10226a64a8d1d72 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 16:47:36 +0100 Subject: [PATCH 195/276] Create statement detail view separate from edit view --- nummi/main/static/main/js/base.js | 21 ++++++++++++++ nummi/main/templates/main/base.html | 1 + nummi/statement/models.py | 5 +--- .../templates/statement/statement_detail.html | 29 +++++++++++++++++++ .../templates/statement/statement_form.html | 17 ++--------- nummi/statement/urls.py | 5 +++- nummi/statement/views.py | 26 ++++++++++++++++- 7 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 nummi/main/static/main/js/base.js create mode 100644 nummi/statement/templates/statement/statement_detail.html diff --git a/nummi/main/static/main/js/base.js b/nummi/main/static/main/js/base.js new file mode 100644 index 0000000..6080811 --- /dev/null +++ b/nummi/main/static/main/js/base.js @@ -0,0 +1,21 @@ +const beforeUnloadHandler = (event) => { + event.preventDefault(); +}; + +const forms = document.querySelectorAll("form"); + +for (let form of forms) { + let inputs = form.querySelectorAll("input"); + for (input of inputs) { + input.addEventListener("input", (event) => { + window.addEventListener("beforeunload", beforeUnloadHandler); + }); + } + + form.addEventListener("submit", (event) => { + window.removeEventListener("beforeunload", beforeUnloadHandler); + }); + form.addEventListener("reset", (event) => { + window.removeEventListener("beforeunload", beforeUnloadHandler); + }); +} diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index bba9037..0a68817 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -12,6 +12,7 @@ {% block link %} {% css "main/css/main.css" %} + {% js "main/js/base.js" %} {% endblock %} diff --git a/nummi/statement/models.py b/nummi/statement/models.py index 865abdd..c5e0337 100644 --- a/nummi/statement/models.py +++ b/nummi/statement/models.py @@ -46,10 +46,7 @@ class Statement(AccountModel): ) def __str__(self): - desc = _("%(date)s statement") % {"date": self.date} - if hasattr(self, "account"): - return f"{desc} – {self.account}" - return desc + return _("%(date)s statement") % {"date": self.date} def save(self, *args, **kwargs): if Statement.objects.filter(id=self.id).exists(): diff --git a/nummi/statement/templates/statement/statement_detail.html b/nummi/statement/templates/statement/statement_detail.html new file mode 100644 index 0000000..188b57e --- /dev/null +++ b/nummi/statement/templates/statement/statement_detail.html @@ -0,0 +1,29 @@ +{% extends "main/base.html" %} +{% load i18n main_extras statement_extras category %} +{% block title %} + {{ statement }} + – Nummi +{% endblock %} +{% block link %} + {{ block.super }} + {% css "main/css/form.css" %} + {% css "main/css/table.css" %} + {% css "main/css/plot.css" %} +{% endblock %} +{% block body %} +

    {{ statement }}

    +

    + {{ account.icon|remix }}{{ account }} +

    +

    + {{ "file-edit"|remix }}{% translate "Edit statement" %} +

    +
    +

    {% translate "Transactions" %}

    + {% include "transaction/transaction_table.html" %} +
    +
    +

    {% translate "Categories" %}

    + {% category_plot transactions budget=False statement=object %} +
    +{% endblock %} diff --git a/nummi/statement/templates/statement/statement_form.html b/nummi/statement/templates/statement/statement_form.html index cfe210b..46c5eb0 100644 --- a/nummi/statement/templates/statement/statement_form.html +++ b/nummi/statement/templates/statement/statement_form.html @@ -6,23 +6,10 @@ {% block h2_new %} {% translate "New statement" %} {% endblock %} -{% block h2 %}{{ form.instance }}{% endblock %} {% block pre %} - {% if account %} + {% if not instance|adding %}

    - {{ account.icon|remix }}{{ account }} + {{ "arrow-go-back"|remix }}{% translate "Back" %}

    {% endif %} {% endblock %} -{% block tables %} - {% if not form.instance|adding %} -
    -

    {% translate "Categories" %}

    - {% category_plot transactions budget=False statement=object %} -
    -
    -

    {% translate "Transactions" %}

    - {% include "transaction/transaction_table.html" %} -
    - {% endif %} -{% endblock %} diff --git a/nummi/statement/urls.py b/nummi/statement/urls.py index d9e1e3b..d455c1f 100644 --- a/nummi/statement/urls.py +++ b/nummi/statement/urls.py @@ -6,7 +6,10 @@ from . import views urlpatterns = [ path("list", views.StatementListView.as_view(), name="statements"), path("new", views.StatementCreateView.as_view(), name="new_statement"), - path("", views.StatementUpdateView.as_view(), name="statement"), + path("", views.StatementDetailView.as_view(), name="statement"), + path( + "/edit", views.StatementUpdateView.as_view(), name="edit_statement" + ), path( "/transaction/list", views.StatementTListView.as_view(), diff --git a/nummi/statement/views.py b/nummi/statement/views.py index 4d393d2..426436a 100644 --- a/nummi/statement/views.py +++ b/nummi/statement/views.py @@ -1,7 +1,13 @@ from account.models import Account from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy -from main.views import NummiCreateView, NummiDeleteView, NummiListView, NummiUpdateView +from main.views import ( + NummiCreateView, + NummiDeleteView, + NummiDetailView, + NummiListView, + NummiUpdateView, +) from transaction.views import TransactionListView from .forms import StatementForm @@ -57,6 +63,24 @@ class StatementDeleteView(NummiDeleteView): pk_url_kwarg = "statement" +class StatementDetailView(NummiDetailView): + model = Statement + pk_url_kwarg = "statement" + context_object_name = "statement" + + def get_context_data(self, **kwargs): + data = super().get_context_data(**kwargs) + statement = data.get("statement") + + return data | { + "account": statement.account, + "new_transaction_url": reverse_lazy( + "new_transaction", kwargs={"statement": statement.pk} + ), + "transactions": statement.transaction_set.all(), + } + + class StatementListView(NummiListView): model = Statement context_object_name = "statements" From 94d1907f9aa7248153969b16d3d966f9a6e3e9ac Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 17:04:34 +0100 Subject: [PATCH 196/276] Add statement summary to details page --- nummi/main/static/main/css/main.css | 31 +++++++++++++++++++ .../templates/statement/statement_detail.html | 16 ++++++++++ 2 files changed, 47 insertions(+) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 2819819..e5e3de7 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -426,3 +426,34 @@ a, content: "\2002"; } } + +.statement-details { + display: grid; + grid-template-columns: repeat(4, min-content); + grid-gap: var(--gap); + align-items: center; + + > span:nth-child(2) { + display: grid; + grid-auto-rows: min-content; + > span[class^="ri-"] { + font-size: 2rem; + } + > span.value { + text-align: right; + } + } + > span:first-child, + > span:nth-child(3) { + display: grid; + border: var(--gray) 1px solid; + padding: 0.75rem; + + > .date { + text-align: right; + } + > .value { + font-size: 2rem; + } + } +} diff --git a/nummi/statement/templates/statement/statement_detail.html b/nummi/statement/templates/statement/statement_detail.html index 188b57e..bc27ade 100644 --- a/nummi/statement/templates/statement/statement_detail.html +++ b/nummi/statement/templates/statement/statement_detail.html @@ -18,6 +18,22 @@

    {{ "file-edit"|remix }}{% translate "Edit statement" %}

    +
    + + {{ statement.start_date|date:"Y-m-d" }} + {{ statement.start_value|value }} + + + {{ statement.sum|pmvalue }} + {{ "arrow-right"|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" %}

    {% include "transaction/transaction_table.html" %} From fe59869a1da11f8ec5f3edbf233f170d975f416f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 17:13:33 +0100 Subject: [PATCH 197/276] Fix show archived button --- nummi/main/static/main/css/main.css | 9 +++++++-- nummi/main/templates/main/index.html | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index e5e3de7..4fb461d 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -355,6 +355,10 @@ ul.messages { dl { margin: 0; + dt, + dd { + margin: 0; + } .account { padding: 0.5rem; margin-bottom: 0.5rem; @@ -366,8 +370,8 @@ ul.messages { &.more { border-style: dashed; } - &.more label span::after { - content: "\2002"; + &.more label { + display: block; } } &:not(.show-archive) .account.archived { @@ -428,6 +432,7 @@ a, } .statement-details { + overflow-x: auto; display: grid; grid-template-columns: repeat(4, min-content); grid-gap: var(--gap); diff --git a/nummi/main/templates/main/index.html b/nummi/main/templates/main/index.html index 011e2ef..550f843 100644 --- a/nummi/main/templates/main/index.html +++ b/nummi/main/templates/main/index.html @@ -25,7 +25,7 @@ {% endfor %}
    - - - + + {% for invoice in transaction.invoices %} - {% empty %} - + {% endfor %} - + diff --git a/nummi/transaction/templates/transaction/transaction_detail.html b/nummi/transaction/templates/transaction/transaction_detail.html new file mode 100644 index 0000000..bc5954e --- /dev/null +++ b/nummi/transaction/templates/transaction/transaction_detail.html @@ -0,0 +1,42 @@ +{% extends "main/form/base.html" %} +{% load i18n %} +{% load main_extras %} +{% block title %} + {{ transaction }} + – Nummi +{% endblock %} +{% block link %} + {{ block.super }} + {% css "main/css/form.css" %} + {% css "main/css/table.css" %} + {% css "main/css/plot.css" %} +{% endblock %} +{% block body %} +

    {{ transaction }}

    +
    + +

    + {{ "edit"|remix }}{% translate "Edit transaction" %} +

    + {% if transaction.description %}

    {{ transaction.description|linebreaksbr }}

    {% endif %} +
    +
    +

    {% translate "Invoices" %}

    + {% include "transaction/invoice_table.html" %} +
    +{% endblock %} diff --git a/nummi/transaction/templates/transaction/transaction_form.html b/nummi/transaction/templates/transaction/transaction_form.html index 5b40a0e..0b215bb 100644 --- a/nummi/transaction/templates/transaction/transaction_form.html +++ b/nummi/transaction/templates/transaction/transaction_form.html @@ -13,12 +13,9 @@ {{ statement }}

    {% endif %} -{% endblock %} -{% block tables %} - {% if not form.instance|adding %} -
    -

    {% translate "Invoices" %}

    - {% include "transaction/invoice_table.html" %} -
    + {% if not instance|adding %} +

    + {{ "arrow-go-back"|remix }}{% translate "Back" %} +

    {% endif %} {% endblock %} diff --git a/nummi/transaction/urls.py b/nummi/transaction/urls.py index 7c62eb8..c39aef6 100644 --- a/nummi/transaction/urls.py +++ b/nummi/transaction/urls.py @@ -15,7 +15,12 @@ urlpatterns = [ name="transaction_month", ), path("new", views.TransactionCreateView.as_view(), name="new_transaction"), - path("", views.TransactionUpdateView.as_view(), name="transaction"), + path("", views.TransactionDetailView.as_view(), name="transaction"), + path( + "/edit", + views.TransactionUpdateView.as_view(), + name="edit_transaction", + ), path( "/delete", views.TransactionDeleteView.as_view(), diff --git a/nummi/transaction/views.py b/nummi/transaction/views.py index 6b1f56e..189b9ba 100644 --- a/nummi/transaction/views.py +++ b/nummi/transaction/views.py @@ -7,6 +7,7 @@ from history.utils import history from main.views import ( NummiCreateView, NummiDeleteView, + NummiDetailView, NummiListView, NummiUpdateView, UserMixin, @@ -61,6 +62,22 @@ class TransactionUpdateView(NummiUpdateView): pk_url_kwarg = "transaction" +class TransactionDetailView(NummiDetailView): + model = Transaction + pk_url_kwarg = "transaction" + context_object_name = "transaction" + + def get_context_data(self, **kwargs): + data = super().get_context_data(**kwargs) + transaction = data.get("transaction") + + return data | { + "account": transaction.statement.account, + "statement": transaction.statement, + "category": transaction.category, + } + + class InvoiceUpdateView(NummiUpdateView): model = Invoice form_class = InvoiceForm From 275a1f6bc7efdc4f3f9eee7d45bcaf6117d06eaa Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 1 Jan 2025 08:20:16 +0100 Subject: [PATCH 201/276] Fix transaction details on mobile --- 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 6903443..f01600e 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -467,7 +467,7 @@ ul.statements { .transaction-details { display: grid; - grid-auto-columns: minmax(32rem, max-content); + grid-auto-columns: minmax(1fr, max-content); grid-auto-rows: min-content; max-width: 32rem; From c8b58a18ac5258d297e303bc69e1ed7bbe8e89c1 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 1 Jan 2025 08:38:04 +0100 Subject: [PATCH 202/276] Improve invoice list --- nummi/main/static/main/css/main.css | 24 +++++---- .../templates/statement/statement_table.html | 2 +- .../templates/transaction/invoice_table.html | 49 ++++++------------- .../transaction/transaction_table.html | 2 +- 4 files changed, 32 insertions(+), 45 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index f01600e..da8502b 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -261,7 +261,7 @@ footer { line-height: 1.5em; } - a &, + a:not(.i) &, .wi &, h2 & { &:first-child::after { @@ -384,7 +384,8 @@ ul.messages { } } -ul.statements { +ul.statements, +ul.invoices { display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); grid-auto-rows: 1fr; @@ -395,10 +396,20 @@ ul.statements { li { display: grid; grid-row-gap: var(--gap); - padding: 0.75rem; + padding: var(--gap); border: var(--gray) 1px solid; text-align: right; + align-items: center; + &.new, + &.more { + border-style: dashed; + } + } +} + +ul.statements { + li { > * { display: grid; align-items: center; @@ -420,11 +431,6 @@ ul.statements { white-space: nowrap; } } - - &.new, - &.more { - border-style: dashed; - } } } @@ -449,7 +455,7 @@ ul.statements { > span:nth-child(3) { display: grid; border: var(--gray) 1px solid; - padding: 0.75rem; + padding: var(--gap); > .date { text-align: right; diff --git a/nummi/statement/templates/statement/statement_table.html b/nummi/statement/templates/statement/statement_table.html index 9c2de04..35340a8 100644 --- a/nummi/statement/templates/statement/statement_table.html +++ b/nummi/statement/templates/statement/statement_table.html @@ -11,7 +11,7 @@ {% for sta in statements %}
  • {{ sta.date|date:"d" }} - {{ sta.date|date:"F y" }} + {{ sta.date|date:"M y" }} {{ sta.value|value }} {{ sta.diff|pmvalue }} {% if not account %} diff --git a/nummi/transaction/templates/transaction/invoice_table.html b/nummi/transaction/templates/transaction/invoice_table.html index 9b32285..9ac6bf4 100644 --- a/nummi/transaction/templates/transaction/invoice_table.html +++ b/nummi/transaction/templates/transaction/invoice_table.html @@ -1,38 +1,19 @@ {% load main_extras %} {% load i18n %}
    -
  • {% translate "Category" %}{% translate "Category" %} {% translate "Expenses" %} {% translate "Income" %}
    {% if cat.category %} {% if month %} - {{ cat.category__name }} + {{ cat.category__icon|remix }}{{ cat.category__name }} {% elif year %} - {{ cat.category__name }} + {{ cat.category__icon|remix }}{{ cat.category__name }} {% else %} - {{ cat.category__name }} + {{ cat.category__icon|remix }}{{ cat.category__name }} {% endif %} {% endif %} - {% if cat.category %}{{ cat.category__icon|remix }}{% endif %} - {{ cat.sum_m|pmvalue }} {% if cat.sum_m %} @@ -59,7 +55,7 @@
    {% translate "No transaction" %}{% translate "No transaction" %}
    {% translate "Total" %}{% translate "Total" %} {{ total_m|pmvalue }}
    diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 2eee924..f06189f 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -78,9 +78,6 @@ a { height: 1.5rem; line-height: 1.5rem; - [class^="ri-"] { - margin-right: 0.5em; - } &.add { color: var(--text-link); background: var(--bg-01); @@ -391,9 +388,6 @@ ul.statements { text-overflow: "."; white-space: nowrap; } - a span::after { - content: "\a0"; - } } &.new, @@ -402,3 +396,9 @@ ul.statements { } } } + +a { + & > span[class^="ri-"]:first-child::after { + content: "\2002"; + } +} diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index 1654e6f..87315bc 100644 --- a/nummi/transaction/templates/transaction/transaction_table.html +++ b/nummi/transaction/templates/transaction/transaction_table.html @@ -9,14 +9,8 @@
    {% translate "Income" %} {% translate "Trader" %}{% translate "Category" %}{% translate "Category" %}{% translate "Account" %}{% translate "Account" %}
    {{ trans.trader|default_if_none:"" }}{{ trans.category.icon|remix }} - {{ trans.category }} + {{ trans.category.icon|remix }}{{ trans.category }} {{ trans.account.icon|remix }} - {{ trans.account }} + {{ trans.account.icon|remix }}{{ trans.account }}
    + {% if cat.category %} {% if month %} {{ cat.category__icon|remix }}{{ cat.category__name }} @@ -88,7 +88,6 @@
    {% translate "Expected total" %} {{ total|check:statement.diff }} {% if statement.diff < 0 %}
    diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 56f6e2a..6202e13 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -405,7 +405,8 @@ ul.statements { } } -a { +a, +.wi { & > span[class^="ri-"]:first-child::after { content: "\2002"; } From b20ef58e1867c846dfe68640e15c54e1609f3f4f Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 15:58:06 +0100 Subject: [PATCH 192/276] Reduce statement size to allow for better mobile experience --- nummi/main/static/main/css/main.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 6202e13..2cdf690 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -363,7 +363,7 @@ ul.messages { ul.statements { display: grid; - grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); grid-auto-rows: 1fr; grid-gap: var(--gap); list-style: none; @@ -372,7 +372,7 @@ ul.statements { li { display: grid; grid-row-gap: var(--gap); - padding: var(--gap); + padding: 0.75rem; border: var(--gray) 1px solid; text-align: right; From f9e489218d6e2a96ef0cf160dfcfe441f4d59786 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 16:14:24 +0100 Subject: [PATCH 193/276] Add icons to messages --- nummi/main/static/main/css/main.css | 23 +++++++++++++++++++---- nummi/main/templates/main/base.html | 4 +++- nummi/main/templatetags/main_extras.py | 13 +++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 2cdf690..2819819 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -291,21 +291,36 @@ ul.messages { font-weight: 550; list-style-type: none; margin: 0; + margin-bottom: var(--gap); background: var(--bg-01); padding: 0; li { + --message-color: var(--text); padding: calc(var(--gap) / 2) var(--gap); - border-left: var(--border) solid var(--gray); + border-left: var(--message-color) solid var(--border); + [class^="ri-"] { + height: 1.5em; + width: 1.5em; + line-height: 1.5em; + border-radius: var(--radius); + background: var(--message-color); + color: var(--bg); + margin-right: 0.5rem; + } + + &.msg-level-20 { + --message-color: var(--green); + } &.msg-level-25 { - border-left-color: var(--green-1); + --message-color: var(--green-1); } &.msg-level-30 { - border-left-color: var(--red-1); + --message-color: var(--red-1); } &.msg-level-40 { - border-left-color: var(--red); + --message-color: var(--red); } } } diff --git a/nummi/main/templates/main/base.html b/nummi/main/templates/main/base.html index 2a497f9..bba9037 100644 --- a/nummi/main/templates/main/base.html +++ b/nummi/main/templates/main/base.html @@ -82,7 +82,9 @@
    {% if messages %}
      - {% for message in messages %}
    • {{ message }}
    • {% endfor %} + {% for message in messages %} +
    • {{ message.level|messageicon }}{{ message }}
    • + {% endfor %}
    {% endif %} {% block body %}{% endblock %} diff --git a/nummi/main/templatetags/main_extras.py b/nummi/main/templatetags/main_extras.py index e4494e8..0a9abcc 100644 --- a/nummi/main/templatetags/main_extras.py +++ b/nummi/main/templatetags/main_extras.py @@ -44,6 +44,19 @@ def remixnl(icon, cls=""): return mark_safe(f"""""") +@register.filter +def messageicon(level): + ico = { + 10: "bug", + 20: "information", + 25: "check", + 30: "alert", + 40: "error-warning", + } + + return remix(ico.get(level, "question")) + + @register.filter def extension(file): return file.name.split(".")[-1].upper() From fd140a931415c93e45700f1895f63561bb8a9a34 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Tue, 31 Dec 2024 16:19:37 +0100 Subject: [PATCH 194/276] Hide expected total when it is correct --- nummi/category/templates/category/category_plot.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nummi/category/templates/category/category_plot.html b/nummi/category/templates/category/category_plot.html index 3c95171..d60a0f3 100644 --- a/nummi/category/templates/category/category_plot.html +++ b/nummi/category/templates/category/category_plot.html @@ -84,7 +84,7 @@
    {{ total_p|pmvalue }}
    {% translate "Expected total" %} {{ total|check:statement.diff }}
    {% translate "Name" %}{% translate "File" %}{% translate "Delete" %}{% translate "Invoice" %}{{ "file-edit"|remix }}
    - {{ invoice.name }} + {{ invoice.name }} [{{ invoice.file|extension }}] - {% translate "File" %} [{{ invoice.file|extension }}] - - {% translate "Delete" %} + {{ "file-edit"|remix }}
    {% translate "No invoice" %}{% translate "No invoice" %}
    {% translate "Create invoice" %}
    - - - - - - - - - - - {% for invoice in transaction.invoices %} - - - - - {% empty %} - - - - {% endfor %} - - - - - - -
    {% translate "Invoice" %}{{ "file-edit"|remix }}
    - {{ invoice.name }} [{{ invoice.file|extension }}] - - {{ "file-edit"|remix }} -
    {% translate "No invoice" %}
    - {% translate "Create invoice" %} -
    +
    diff --git a/nummi/transaction/templates/transaction/transaction_table.html b/nummi/transaction/templates/transaction/transaction_table.html index 87315bc..ab25a82 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 %}{{ "attachment"|remix }}{% endfor %} + {% for invoice in trans.invoices %}{{ "attachment"|remix }}{% endfor %}
    {% endif %} -{% endblock %} +{% endblock body %} From 2991786444f1b57ba1a2d3a2c043d56a23692785 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 1 Jan 2025 19:01:35 +0100 Subject: [PATCH 216/276] Improve statement form --- .prettierrc.toml | 2 +- nummi/main/static/main/css/form.css | 7 +++++-- nummi/main/static/main/css/main.css | 5 +++++ nummi/main/static/main/css/table.css | 5 ----- nummi/statement/forms.py | 10 ++++++++++ 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.prettierrc.toml b/.prettierrc.toml index 49b487b..eeb77a3 100644 --- a/.prettierrc.toml +++ b/.prettierrc.toml @@ -1 +1 @@ -end_of_line = "auto" +endOfLine = "auto" diff --git a/nummi/main/static/main/css/form.css b/nummi/main/static/main/css/form.css index 3b83905..440f13d 100644 --- a/nummi/main/static/main/css/form.css +++ b/nummi/main/static/main/css/form.css @@ -54,9 +54,12 @@ form { background: none; z-index: 1; - &.value { - font-size: 1.5rem; + &[name*="value"] { text-align: right; + font-feature-settings: var(--num); + } + &[name*="date"] { + font-feature-settings: var(--num); } &:focus { diff --git a/nummi/main/static/main/css/main.css b/nummi/main/static/main/css/main.css index 4ac95d7..dd87b3d 100644 --- a/nummi/main/static/main/css/main.css +++ b/nummi/main/static/main/css/main.css @@ -495,3 +495,8 @@ ul.statements { border-style: dashed; } } + +.date, +.value { + font-feature-settings: var(--num); +} diff --git a/nummi/main/static/main/css/table.css b/nummi/main/static/main/css/table.css index f663bad..c6284ec 100644 --- a/nummi/main/static/main/css/table.css +++ b/nummi/main/static/main/css/table.css @@ -60,8 +60,3 @@ table { text-align: center; } } - -.date, -.value { - font-feature-settings: var(--num); -} diff --git a/nummi/statement/forms.py b/nummi/statement/forms.py index 8467680..e7afbb3 100644 --- a/nummi/statement/forms.py +++ b/nummi/statement/forms.py @@ -15,6 +15,15 @@ class StatementForm(NummiForm): "file": NummiFileInput, } + meta_fieldsets = [ + [ + ["account"], + ["start_date", "start_value"], + ["date", "value"], + ["file"], + ], + ] + def __init__(self, *args, **kwargs): _user = kwargs.get("user") _disable_account = kwargs.pop("disable_account", False) @@ -30,6 +39,7 @@ class StatementForm(NummiForm): label=_("Add transactions"), required=False, ) + if _disable_account: self.fields["account"].disabled = True From 79f87779dd416859e8e50e40a19595ddf7077f77 Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" Date: Wed, 1 Jan 2025 19:05:34 +0100 Subject: [PATCH 217/276] 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 218/276] 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 219/276] 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 220/276] 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 221/276] 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 222/276] 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 223/276] 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 224/276] 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 225/276] 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 228/276] 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 229/276] 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 230/276] 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 231/276] 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 232/276] 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 233/276] 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 234/276] 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 235/276] 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 236/276] 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 237/276] 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 238/276] 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 239/276] 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 240/276] 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 241/276] 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 242/276] 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 243/276] 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 244/276] 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 245/276] 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 246/276] 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 247/276] 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 248/276] 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 249/276] 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 250/276] 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 251/276] 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 252/276] 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 253/276] 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 254/276] 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 255/276] 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 256/276] 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 257/276] 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 258/276] 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 259/276] 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 %} -