Compare commits

..

No commits in common. "ee3ec2152767113b81049ff2dbea88b8739a2a83" and "4bbb5de3c5547264dbfbde1f18f3eea28691c8f8" have entirely different histories.

55 changed files with 670 additions and 1263 deletions

View File

@ -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: 2023-04-22 15:16+0200\n"
"PO-Revision-Date: 2023-04-22 15:17+0200\n"
"Last-Translator: Edgar P. Burkhart <traduction@edgarpierre.fr>\n"
"Language-Team: \n"
@ -37,22 +37,6 @@ msgstr "Défaut"
msgid "Accounts"
msgstr "Comptes"
#: .\account\templates\account\account_detail.html:13
msgid "Edit account"
msgstr "Modifier le compte"
#: .\account\templates\account\account_detail.html:16
msgid "Statements"
msgstr "Relevés"
#: .\account\templates\account\account_detail.html:20
msgid "Transactions"
msgstr "Transactions"
#: .\account\templates\account\account_detail.html:25
msgid "History"
msgstr "Historique"
#: .\account\templates\account\account_form.html:5
msgid "Create account"
msgstr "Créer un compte"
@ -60,3 +44,15 @@ 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"

View File

@ -1,29 +0,0 @@
{% extends "main/base.html" %}
{% load main_extras %}
{% load i18n %}
{% block title %}{{ object }} {{ block.super }}{% endblock %}
{% block link %}
{{ block.super }}
{% css "main/css/table.css" %}
{% css "main/css/plot.css" %}
{% endblock %}
{% block body %}
<h2>{{ object.icon|remix }}{{ object }}</h2>
<p>
<a href="{% url "edit_account" object.pk %}">{% translate "Edit account" %}</a>
</p>
<section>
<h3>{% translate "Statements" %}</h3>
{% include "statement/statement_table.html" %}
</section>
<section>
<h3>{% translate "Transactions" %}</h3>
{% include "transaction/transaction_table.html" %}
</section>
{% if history %}
<section>
<h3>{% translate "History" %}</h3>
{% include "history/plot.html" %}
</section>
{% endif %}
{% endblock %}

View File

@ -8,3 +8,15 @@
{% translate "New account" %}
{% endblock %}
{% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %}
{% block tables %}
{% if not form.instance|adding %}
<h3>{% translate "Statements" %}</h3>
{% include "statement/statement_table.html" %}
<h3>{% translate "Transactions" %}</h3>
{% include "transaction/transaction_table.html" %}
{% if history.data %}
<h3>{% translate "History" %}</h3>
{% include "history/plot.html" %}
{% endif %}
{% endif %}
{% endblock %}

View File

@ -1,13 +1,12 @@
from django.urls import path
from statement.views import StatementCreateView
from transaction.views import TransactionMonthView, TransactionYearView
from transaction.views import TransactionMonthView
from . import views
urlpatterns = [
path("new", views.AccountCreateView.as_view(), name="new_account"),
path("<account>", views.AccountDetailView.as_view(), name="account"),
path("<account>/edit", views.AccountUpdateView.as_view(), name="edit_account"),
path("<account>", views.AccountUpdateView.as_view(), name="account"),
path(
"<account>/transactions",
views.AccountTListView.as_view(),
@ -28,14 +27,9 @@ urlpatterns = [
views.AccountDeleteView.as_view(),
name="del_account",
),
path(
"<account>/history/<int:year>",
TransactionYearView.as_view(),
name="account_transaction_year",
),
path(
"<account>/history/<int:year>/<int:month>",
TransactionMonthView.as_view(),
name="account_transaction_month",
name="transaction_month",
),
]

View File

@ -1,12 +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,
NummiDetailView,
NummiUpdateView,
)
from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView
from statement.views import StatementListView
from transaction.views import TransactionListView
@ -24,20 +19,10 @@ class AccountUpdateView(NummiUpdateView):
form_class = AccountForm
pk_url_kwarg = "account"
class AccountDeleteView(NummiDeleteView):
model = Account
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")
account = data["form"].instance
_transactions = account.transaction_set.all()
if _transactions.count() > _max:
@ -60,6 +45,11 @@ class AccountDetailView(NummiDetailView):
}
class AccountDeleteView(NummiDeleteView):
model = Account
pk_url_kwarg = "account"
class AccountMixin:
def get_queryset(self):
self.account = get_object_or_404(

View File

@ -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: 2023-11-25 12:05+0100\n"
"PO-Revision-Date: 2023-04-22 15:18+0200\n"
"Last-Translator: Edgar P. Burkhart <traduction@edgarpierre.fr>\n"
"Language-Team: \n"
@ -38,18 +38,6 @@ 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"
@ -58,6 +46,14 @@ msgstr "Créer une catégorie"
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"
@ -66,10 +62,10 @@ msgstr "Dépenses"
msgid "Income"
msgstr "Revenus"
#: .\category\templates\category\category_plot.html:62
#: .\category\templates\category\category_plot.html:56
msgid "No transaction"
msgstr "Aucune transaction"
#: .\category\templates\category\category_plot.html:70
#: .\category\templates\category\category_plot.html:64
msgid "Total"
msgstr "Total"

View File

@ -1,24 +0,0 @@
{% 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 %}
<h2>{{ object.icon|remix }}{{ object }}</h2>
<p>
<a href="{% url "edit_category" object.pk %}">{% translate "Edit category" %}</a>
</p>
<section>
<h3>{% translate "Transactions" %}</h3>
{% include "transaction/transaction_table.html" %}
</section>
{% if history %}
<section>
<h3>{% translate "History" %}</h3>
{% include "history/plot.html" %}
</section>
{% endif %}
{% endblock %}

View File

@ -8,3 +8,11 @@
{% translate "New category" %}
{% endblock %}
{% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %}
{% block tables %}
<h3>{% translate "Transactions" %}</h3>
{% include "transaction/transaction_table.html" %}
{% if history.data %}
<h3>{% translate "History" %}</h3>
{% include "history/plot.html" %}
{% endif %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% load main_extras statement_extras %}
{% load main_extras %}
{% load i18n %}
<div class="plot">
<table class="full-width">
@ -23,9 +23,7 @@
<th scope="row" class="l">
{% if cat.category %}
{% if month %}
<a href="{% url "category_transaction_month" cat.category month.year month.month %}">{{ cat.category__name }}</a>
{% elif year %}
<a href="{% url "category_transaction_year" cat.category year.year %}">{{ cat.category__name }}</a>
<a href="{% url "transaction_month" cat.category month.year month.month %}">{{ cat.category__name }}</a>
{% else %}
{{ cat.category__name }}
{% endif %}
@ -34,28 +32,24 @@
<td class="c">
{% if cat.category %}{{ cat.category__icon|remix }}{% endif %}
</td>
<td class="value">{{ cat.sum_m|pmvalue }}</td>
<td class="value">{{ cat.sum_m|pmrvalue }}</td>
<td class="bar m">
{% if cat.sum_m %}
<div style="width: {% widthratio cat.sum_m max -100 %}%"></div>
{% endif %}
{% if cat.sum < 0 %}
<div class="tot" style="width:{% widthratio cat.sum max -100 %}%">
<span>{{ cat.sum|pmvalue }}</span>
<span>{{ cat.sum|pmrvalue }}</span>
</div>
{% endif %}
</td>
<td class="bar p">
{% if cat.sum_p %}
<div style="width: {% widthratio cat.sum_p max 100 %}%"></div>
{% endif %}
{% if cat.sum > 0 %}
<div class="tot" style="width:{% widthratio cat.sum max 100 %}%">
<span>{{ cat.sum|pmvalue }}</span>
<span>{{ cat.sum|pmrvalue }}</span>
</div>
{% endif %}
</td>
<td class="value">{{ cat.sum_p|pmvalue }}</td>
<td class="value">{{ cat.sum_p|pmrvalue }}</td>
</tr>
{% empty %}
<tr>
@ -64,16 +58,16 @@
{% endfor %}
{% endspaceless %}
</tbody>
<tfoot>
{% if categories %}
<tfoot>
<tr>
<th scope="row" colspan="2" class="l">{% translate "Total" %}</th>
<td class="value">{{ total_m|pmvalue }}</td>
<td class="value">{{ total_m|pmrvalue }}</td>
<td class="bar m">
<div style="width: {% widthratio total_m max -100 %}%"></div>
{% if total < 0 %}
<div class="tot" style="width:{% widthratio total max -100 %}%">
<span>{{ total|pmvalue }}</span>
<span>{{ total|pmrvalue }}</span>
</div>
{% endif %}
</td>
@ -81,35 +75,13 @@
<div style="width: {% widthratio total_p max 100 %}%"></div>
{% if total > 0 %}
<div class="tot" style="width:{% widthratio total max 100 %}%">
<span>{{ total|pmvalue }}</span>
<span>{{ total|pmrvalue }}</span>
</div>
{% endif %}
</td>
<td class="value">{{ total_p|pmvalue }}</td>
<td class="value">{{ total_p|pmrvalue }}</td>
</tr>
{% endif %}
{% if statement %}
<tr>
<th scope="row" class="l">{% translate "Expected total" %}</th>
<td class="c">{{ total|check:statement.diff }}</td>
<td></td>
<td class="bar m">
{% if statement.diff < 0 %}
<div class="tot" style="width:{% widthratio statement.diff max -100 %}%">
<span>{{ statement.diff|pmvalue }}</span>
</div>
{% endif %}
</td>
<td class="bar p">
{% if statement.diff >= 0 %}
<div class="tot" style="width:{% widthratio statement.diff max 100 %}%">
<span>{{ statement.diff|pmvalue }}</span>
</div>
{% endif %}
</td>
<td></td>
</tr>
{% endif %}
</tfoot>
{% endif %}
</table>
</div>

View File

@ -6,11 +6,10 @@ register = template.Library()
@register.inclusion_tag("category/category_plot.html")
def category_plot(transactions, budget=True, **kwargs):
if budget:
transactions = transactions.exclude(category__budget=False)
def category_plot(transactions, **kwargs):
categories = (
transactions.values("category", "category__name", "category__icon")
transactions.exclude(category__budget=False)
.values("category", "category__name", "category__icon")
.annotate(
sum=models.Sum("value"),
sum_m=models.Sum("value", filter=models.Q(value__lt=0)),

View File

@ -1,26 +1,20 @@
from django.urls import path
from transaction.views import TransactionMonthView, TransactionYearView
from transaction.views import TransactionMonthView
from . import views
urlpatterns = [
path("new", views.CategoryCreateView.as_view(), name="new_category"),
path("<category>", views.CategoryDetailView.as_view(), name="category"),
path("<category>/edit", views.CategoryUpdateView.as_view(), name="edit_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/<int:year>",
TransactionYearView.as_view(),
name="category_transaction_year",
),
path(
"<category>/history/<int:year>/<int:month>",
TransactionMonthView.as_view(),
name="category_transaction_month",
name="transaction_month",
),
]

View File

@ -1,12 +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,
NummiDetailView,
NummiUpdateView,
)
from main.views import NummiCreateView, NummiDeleteView, NummiUpdateView
from transaction.views import TransactionListView
from .forms import CategoryForm
@ -23,15 +18,10 @@ 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["object"]
category = data["form"].instance
data["transactions"] = category.transaction_set.all()[:_max]
if len(data["transactions"]) == _max:

View File

@ -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: 2023-04-22 15:16+0200\n"
"PO-Revision-Date: 2023-04-22 15:18+0200\n"
"Last-Translator: Edgar P. Burkhart <traduction@edgarpierre.fr>\n"
"Language-Team: \n"
@ -17,18 +17,14 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.2.2\n"
#: .\history\templates\history\plot.html:17
#: .\history\templates\history\plot.html:15
msgid "Month"
msgstr "Mois"
#: .\history\templates\history\plot.html:18
#: .\history\templates\history\plot.html:16
msgid "Expenses"
msgstr "Dépenses"
#: .\history\templates\history\plot.html:19
#: .\history\templates\history\plot.html:17
msgid "Income"
msgstr "Revenus"
#: .\history\templates\history\plot.html:55
msgid "Year"
msgstr "Année"

View File

@ -1,8 +1,6 @@
{% load main_extras %}
{% load history_extras %}
{% load transaction_extras %}
{% load i18n %}
<div class="history plot">
<div class="plot">
<table class="full-width">
<colgroup>
<col class="icon">
@ -20,67 +18,46 @@
</tr>
</thead>
<tbody>
{% for date in history.data reversed %}
{% ifchanged %}
{% if date.sum_m or date.sum_p %}
<tr {% if not date.month.month|divisibleby:"2" %}class="even"{% endif %}>
<td class="icon">{% up_down_icon date.sum %}</td>
{% spaceless %}
{% for date in history.data %}
<tr>
<td class="icon">
<span class="ri-{% if date.sum > 0 %}arrow-up-s-line green{% elif date.sum < 0 %}arrow-down-s-line red{% endif %}"></span>
</td>
<th class="date" scope="row">
{% if year %}
{% month_url date.month fmt="F" %}
{% if date.has_transactions %}
{% if account %}
<a href="{% url "transaction_month" account=account.pk year=date.month.year month=date.month.month %}">{{ date.month|date:"Y-m" }}</a>
{% elif category %}
<a href="{% url "transaction_month" category=category.pk year=date.month.year month=date.month.month %}">{{ date.month|date:"Y-m" }}</a>
{% else %}
{% month_url date.month %}
<a href="{% url "transaction_month" year=date.month.year month=date.month.month %}">{{ date.month|date:"Y-m" }}</a>
{% endif %}
{% else %}
{{ date.month|date:"Y-m" }}
{% endif %}
</th>
<td class="value">{{ date.sum_m|pmrvalue }}</td>
<td class="bar m">{% plot_bar date.sum date.sum_m history.max.pm %}</td>
<td class="bar p">{% plot_bar date.sum date.sum_p history.max.pm %}</td>
<td class="bar m">
<div style="width: {% widthratio date.sum_m history.max -100 %}%"></div>
{% if date.sum < 0 %}
<div class="tot" style="width:{% widthratio date.sum history.max -100 %}%">
<span>{{ date.sum|pmrvalue }}</span>
</div>
{% endif %}
</td>
<td class="bar p">
<div style="width: {% widthratio date.sum_p history.max 100 %}%"></div>
{% if date.sum > 0 %}
<div class="tot" style="width:{% widthratio date.sum history.max 100 %}%">
<span>{{ date.sum|pmrvalue }}</span>
</div>
{% endif %}
</td>
<td class="value">{{ date.sum_p|pmrvalue }}</td>
</tr>
{% else %}
<tr class="empty">
<td colspan="6" class="empty"></td>
</tr>
{% endif %}
{% endifchanged %}
{% endfor %}
</tbody>
</table>
</div>
<div class="calendar">
<table class="full-width">
<thead>
<tr>
{% if not year %}
<th scope="col">{% translate "Year" %}</th>
{% endif %}
{% calendar_head %}
</tr>
</thead>
<tbody>
{% regroup history.data by month.year as years_list %}
{% for y, y_data in years_list reversed %}
<tr>
{% if not year %}
<th class="date" scope="row">{% year_url y %}</th>
{% endif %}
{% for m in y_data %}
{% if forloop.parentloop.last and forloop.first %}
{% empty_calendar_cells_start m.month.month %}
{% endif %}
{% if m %}
<td class="{% if m.sum > 0 %}p{% else %}m{% endif %}"
style="--opacity: {% calendar_opacity m.sum history.max.sum %}"
title="{{ m.sum|pmrvalue }}">{% up_down_icon m.sum %}</td>
{% else %}
<td></td>
{% endif %}
{% if forloop.parentloop.first and forloop.last %}
{% empty_calendar_cells_end m.month.month %}
{% endif %}
{% endfor %}
</tr>
{% endfor %}
{% endspaceless %}
</tbody>
</table>
</div>

View File

@ -1,62 +0,0 @@
import math
from django import template
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):.0%}"
@register.simple_tag
def empty_calendar_cells(n):
return mark_safe(n * "<td></td>")
@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")
return remix("equal", "white")
@register.simple_tag
def plot_bar(s, sum_pm, s_max):
_res = ""
if sum_pm:
_w = abs(sum_pm / s_max)
_res += f"""<div style="width: {_w:.1%}"></div>"""
if sum_pm is not None and s * sum_pm > 0:
_w = abs(s / s_max)
_res += (
f"""<div class="tot" style="width: {_w:.1%}">"""
f"""<span>{pmrvalue(s)}</span></div>"""
)
return mark_safe(_res)
@register.simple_tag
def calendar_head():
months = range(1, 13)
th = (f"""<th>{month:02d}</th>""" for month in months)
return mark_safe("".join(th))

View File

@ -1,47 +1,48 @@
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 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
_transaction_month = transaction_set.values(month=TruncMonth("date")).order_by(
"-date"
)
_first_month = _transaction_month.last()["month"]
_last_month = _transaction_month.first()["month"]
_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)
)
)
)
_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"),
has_transactions=Value(1),
).order_by("-month")
_data = [
_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 {
"data": _data,
"max": {
"pm": max(
"data": _history.union(_months).order_by("-month"),
"max": max(
_history.aggregate(
max=Max("sum_p", default=0),
min=-Min("sum_m", default=0),
).values(),
),
"sum": _history.aggregate(max=Max(Abs("sum")))["max"],
},
}

View File

@ -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: 2023-11-25 12:05+0100\n"
"PO-Revision-Date: 2023-04-23 08:03+0200\n"
"Last-Translator: Edgar P. Burkhart <traduction@edgarpierre.fr>\n"
"Language-Team: \n"
@ -21,51 +21,67 @@ msgstr ""
msgid "User"
msgstr "Utilisateur"
#: .\main\templates\main\base.html:28
#: .\main\templates\main\base.html:27
msgid "Skip to main content"
msgstr "Aller au contenu principal"
#: .\main\templates\main\base.html:34
#: .\main\templates\main\base.html:33
msgid "Home"
msgstr "Accueil"
#: .\main\templates\main\base.html:39
#: .\main\templates\main\base.html:38 .\main\templates\main\index.html:40
msgid "Statements"
msgstr "Relevés"
#: .\main\templates\main\base.html:45
#: .\main\templates\main\base.html:44 .\main\templates\main\index.html:24
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: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:54
#: .\main\templates\main\base.html:73
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
#: .\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:65
#: .\main\templates\main\base.html:84
#, python-format
msgid "Logged in as <strong>%(user)s</strong>"
msgstr "Connecté en tant que <strong>%(user)s</strong>"
#: .\main\templates\main\confirm_delete.html:15
#: .\main\templates\main\confirm_delete.html:19
#, python-format
msgid "Are you sure you want do delete <strong>%(object)s</strong> ?"
msgstr "Êtes-vous sûr de vouloir supprimer <strong>%(object)s</strong> ?"
#: .\main\templates\main\confirm_delete.html:19
#: .\main\templates\main\confirm_delete.html:23
msgid "Cancel"
msgstr "Annuler"
#: .\main\templates\main\confirm_delete.html:20
#: .\main\templates\main\confirm_delete.html:24
msgid "Confirm"
msgstr "Confirmer"
@ -89,51 +105,26 @@ msgstr "Créer"
msgid "Save"
msgstr "Enregistrer"
#: .\main\templates\main\index.html:13
#: .\main\templates\main\index.html:15
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
#: .\main\templates\main\index.html:20
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:28
msgid "Categories"
msgstr "Catégories"
#: .\main\templates\main\index.html:56
msgid "Create category"
msgstr "Créer une catégorie"
#: .\main\templates\main\index.html:34
msgid "No category"
msgstr "Aucune catégorie"
#: .\main\templates\main\index.html:63
#: .\main\templates\main\index.html:44
msgid "History"
msgstr "Historique"
#: .\main\views.py:69
#: .\main\views.py:68
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"

View File

@ -5,65 +5,58 @@ form ul.errorlist {
margin: 0;
}
form {
> table > tbody > tr > th {
form > table > tbody > tr > th {
background: var(--bg-01);
background-clip: padding-box;
border-right: 1px solid var(--gray);
}
tbody :is(input, select, textarea) {
form tbody input,
form tbody select,
form tbody textarea {
font: inherit;
border: none;
background: transparent;
background: var(--bg);
width: 100%;
height: 100%;
line-height: 1.5;
}
input[type="checkbox"] {
form input[type="checkbox"] {
width: initial;
}
tfoot {
text-align: right;
}
}
table.file-input {
tr {
table.file-input tr {
border: none;
:first-child {
padding-left: 0;
}
:last-child {
padding-right: 0;
}
}
th {
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;
}
.buttons {
input {
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;
&:hover {
}
.buttons input:hover {
text-decoration: underline;
}
&[type="submit"] {
.buttons input[type="submit"] {
border: 0.1rem solid var(--green);
background: var(--green-1);
}
&[type="reset"] {
.buttons input[type="reset"] {
border: 0.1rem solid var(--red);
background: var(--red-1);
}
}
a.del {
.buttons a.del {
color: var(--red);
}
}

View File

@ -1,5 +1,4 @@
@import "https://rsms.me/inter/inter.css";
@import "https://cdn.jsdelivr.net/npm/remixicon@4.0.0/fonts/remixicon.css";
*,
*::before,
@ -40,8 +39,7 @@
--border: 0.5em;
--radius: 0.25em;
--default-ffs: "dlig", "ss01", "ss04";
--num: var(--default-ffs), "tnum", "case";
--num: "tnum", "ss01", "ss02", "case";
}
body {
@ -53,7 +51,6 @@ body {
display: grid;
grid-template-columns: max-content 1fr;
font-feature-settings: var(--default-ffs);
}
p {
@ -63,31 +60,11 @@ a {
color: var(--text-link);
text-decoration: none;
display: inline-block;
&:is(:hover, :focus) {
}
a:hover {
text-decoration: underline;
}
&.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;
}
&.add {
color: var(--text-link);
background: var(--bg-01);
}
}
}
.red {
color: var(--red);
fill: var(--red);
@ -106,23 +83,6 @@ main {
grid-column: 2;
grid-row: 1;
overflow-x: hidden;
h2.new {
opacity: 0.8;
}
.split {
display: grid;
column-gap: var(--gap);
row-gap: var(--gap);
grid-template-columns: 100%;
@media (width > 720px) {
grid-template-columns: max-content 1fr;
}
& > section > :first-child {
margin-top: 0;
}
}
}
nav {
grid-column: 1;
@ -135,46 +95,38 @@ nav {
background: var(--bg-01);
line-height: 2rem;
h1 img {
}
nav h1 img {
height: 1cap;
}
ul {
nav ul {
list-style: none;
padding: 0;
margin: 0;
position: relative;
}
a {
&.skip-link {
nav .skip-link {
opacity: 0.8;
font-weight: 300;
&:is(:active, :focus) {
}
nav .skip-link:active,
nav .skip-link:focus {
opacity: initial;
font-weight: 500;
}
nav a {
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 {
nav a.cur {
font-weight: 550;
[class^="ri-"] {
background: var(--text-link);
color: var(--bg);
}
nav a.cur::after {
content: "◎";
position: absolute;
right: 0;
}
}
}
:is(nav, main) > :first-child,
main > section:first-child > :first-child {
nav > :first-child,
main > :first-child {
margin-top: 0;
}
footer {
@ -183,46 +135,16 @@ footer {
font-weight: 250;
}
.pagination {
#pagination {
text-align: center;
font-feature-settings: var(--num);
a {
min-width: 1rem;
padding: 0 0.5rem;
&.cur {
}
#pagination a {
width: 2rem;
}
#pagination a.cur {
font-weight: 650;
text-decoration: underline dotted;
&:is(:hover, :focus) {
text-decoration: underline;
}
}
}
@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;
}
}
}
& + section :first-child {
margin-top: 0;
}
}
@media (width < 1024px) {
@ -238,34 +160,15 @@ footer {
height: initial;
}
}
[class^="ri-"] {
display: inline-block;
text-align: center;
font-weight: normal;
&.green,
&.red,
&.white {
&.green {
background: var(--green);
color: var(--bg);
a.big-link {
margin-right: 1em;
}
&.red {
background: var(--red);
color: var(--bg);
}
&.white {
background: var(--bg-01);
}
border-radius: var(--radius);
height: 1.5em;
width: 1.5em;
line-height: 1.5em;
}
h2 & {
a.big-link [class^="ri-"] {
margin-right: 0.5em;
}
[class^="ri-"] {
font-weight: normal;
}
h1,
@ -285,6 +188,9 @@ h2 {
h3 {
font-size: 1.5rem;
}
main h2.new {
opacity: 0.8;
}
p {
margin: 0.5em 0;
}
@ -295,42 +201,18 @@ ul.messages {
margin: 0;
background: var(--bg-01);
padding: 0;
li {
}
ul.messages li {
padding: calc(var(--gap) / 2) var(--gap);
border-left: var(--border) solid var(--gray);
}
&.msg-level-25 {
ul.messages li.msg-level-25 {
border-left-color: var(--green-1);
}
&.msg-level-30 {
ul.messages li.msg-level-30 {
border-left-color: var(--red-1);
}
&.msg-level-40 {
ul.messages li.msg-level-40 {
border-left-color: var(--red);
}
}
}
.backlinks {
display: grid;
grid-template-columns: repeat(2, 1fr);
p {
grid-column: 1;
&.back {
grid-column: 2;
text-align: right;
a {
margin-right: 0;
margin-left: 1em;
[class^="ri-"] {
margin-right: 0em;
margin-left: 0.5em;
}
}
}
}
}

View File

@ -1,33 +1,52 @@
table.full-width col.bar {
width: auto;
}
.plot {
overflow-x: auto;
td.bar {
position: relative;
padding: 0;
overflow: hidden;
@media (width < 720px) {
width: 0;
}
div {
.plot td.bar {
position: relative;
padding: 0;
}
.plot td.bar div {
position: absolute;
height: 0.5rem;
top: 0;
&:not(.tot) {
}
.plot td.bar div:not(.tot) {
width: 0;
box-sizing: border-box;
z-index: 1;
display: inline-block;
}
&.tot {
.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);
}
.plot td.bar div.tot {
z-index: 10;
height: 0.5rem;
span {
}
.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;
@ -38,81 +57,20 @@ table.full-width col.bar {
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);
span {
.plot td.bar.p div.tot span {
left: 0;
}
}
}
&.m div {
right: 0;
border-radius: var(--radius) 0 0 var(--radius);
background: var(--red-1);
&.tot {
background: var(--red);
span {
.plot td.bar.m div.tot span {
right: 0;
}
}
@media (width < 720px) {
.plot .bar {
width: 0;
overflow: hidden;
}
}
&.history tbody tr {
background: initial;
}
tbody tr {
&.empty {
height: 0.5rem;
}
&.even {
background: #eeeeff;
}
}
tfoot {
.plot tfoot {
background: var(--bg-01);
}
}
.calendar {
overflow-x: auto;
margin-top: var(--gap);
font-feature-settings: var(--num);
table {
tbody tr {
background: initial;
&:not(:last-child) {
border-bottom: none;
}
&:not(:first-child) {
border-top: none;
}
td {
text-align: center;
background-color: color-mix(
in hsl,
var(--td-bg, var(--bg)) var(--opacity),
var(--bg)
);
&.p {
--td-bg: var(--green);
}
&.m {
--td-bg: var(--red);
}
}
}
}
}

View File

@ -1,37 +1,29 @@
.table,
form {
overflow-x: auto;
width: 100%;
}
table {
border-collapse: collapse;
&.full-width {
}
table.more tbody:last-child tr:last-child {
border-bottom: 0.1rem dashed var(--gray);
}
table.full-width {
width: 100%;
col {
width: 8rem;
}
}
col.icon {
width: 1ch;
}
thead tr:not(.new) {
thead {
background: var(--bg-01);
}
table.full-width col {
width: 8rem;
}
table col.icon {
width: 1ch;
}
tr {
border: 1px solid var(--gray);
border: 0.1rem solid var(--gray);
height: 2rem;
line-height: 2rem;
tbody &:where(:nth-of-type(even)) {
background: #eeeeff;
}
&.more,
&.new {
text-align: center;
border-style: dashed;
}
}
td,
th {
@ -39,14 +31,7 @@ table {
position: relative;
white-space: nowrap;
text-overflow: ellipsis;
&.empty {
text-align: center;
opacity: 0.8;
font-weight: 300;
}
}
}
.date,
.value {
font-feature-settings: var(--num);
@ -62,3 +47,8 @@ table {
.date {
text-align: center;
}
td.empty {
text-align: center;
opacity: 0.8;
font-weight: 300;
}

View File

@ -1,17 +1,17 @@
{% load static %}
{% load i18n %}
{% load main_extras %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>
{% block title %}Nummi{% endblock %}
</title>
{% block link %}
<link rel="icon" href="{% static "main/svg/logo.svg" %}" type="image/svg+xml">
{% css "main/css/main.css" %}
<link rel="icon" href="{% static "main/svg/logo.svg" %}" type="image/svg+xml" />
<link rel="stylesheet" href="{% static "main/css/main.css" %}" type="text/css" />
<link rel="stylesheet" href="{% static "main/remixicon/remixicon.css" %}" type="text/css" />
{% endblock %}
</head>
<body>
@ -19,7 +19,7 @@
{% spaceless %}
<nav>
<h1>
<img src="{% static "main/svg/logo.svg" %}" alt="">
<img src="{% static "main/svg/logo.svg" %}" alt="" />
Nummi
</h1>
<ul>
@ -30,52 +30,60 @@
<li>
<a href="{% url "index" %}"
class="home{% if request.resolver_match.url_name == "index" %} cur{% endif %}"
accesskey="h">
<span>{% translate "Home" %}</span>
{{ "home"|remix }}
</a>
accesskey="h">{% translate "Home" %}</a>
</li>
<li>
<a href="{% url "statements" %}"
class="{% if request.resolver_match.url_name == "statements" %}cur{% endif %}">
<span>{% translate "Statements" %}</span>
{{ "file"|remix }}
{% translate "Statements" %}
</a>
</li>
<li>
<a href="{% url "transactions" %}"
class="{% if request.resolver_match.url_name == "transactions" %}cur{% endif %}">
<span>{% translate "Transactions" %}</span>
{{ "receipt"|remix }}
{% translate "Transactions" %}
</a>
</li>
<li>
<a href="{% url "new_account" %}"
class="{% if request.resolver_match.url_name == "new_account" %}cur{% endif %}"
accesskey="a">{% translate "Create account" %}</a>
</li>
<li>
<a href="{% url "new_statement" %}"
class="{% if request.resolver_match.url_name == "new_statement" %}cur{% endif %}"
accesskey="s">{% translate "Create statement" %}</a>
</li>
<li>
<a href="{% url "new_category" %}"
class="{% if request.resolver_match.url_name == "new_category" %}cur{% endif %}"
accesskey="c">{% translate "Create category" %}</a>
</li>
<li>
<a href="{% url "new_transaction" %}"
class="{% if request.resolver_match.url_name == "new_transaction" %}cur{% endif %}"
accesskey="t">{% translate "Create transaction" %}</a>
</li>
<li>
<a href="{% url "search" %}"
class="{% if request.resolver_match.url_name == "search" %}cur{% endif %}"
accesskey="r">
<span>{% translate "Search" %}</span>
{{ "search"|remix }}
</a>
accesskey="r">{% translate "Search" %}</a>
</li>
<li>
{% blocktranslate %}Logged in as <strong>{{ user }}</strong>{% endblocktranslate %}
</li>
<li>
<a href="{% url "logout" %}" accesskey="l">
<span>{% translate "Log out" %}</span>
{{ "close-circle"|remix }}
</a>
<a href="{% url "logout" %}" accesskey="l">{% translate "Log out" %}</a>
</li>
{% else %}
<li>
<a {% if request.resolver_match.url_name == "login" %}class="cur"{% endif %}
href="{% url "login" %}">
<span>{% translate "Log in" %}</span>
{{ "user"|remix }}
</a>
href="{% url "login" %}">{% translate "Log in" %}</a>
</li>
{% endif %}
</ul>
{% if user.is_authenticated %}
<p>
{% blocktranslate %}Logged in as <strong>{{ user }}</strong>{% endblocktranslate %}
</p>
{% endif %}
</nav>
{% endspaceless %}
{% endblock %}

View File

@ -4,8 +4,12 @@
{% load i18n %}
{% block link %}
{{ block.super }}
{% css "main/css/form.css" %}
{% css "main/css/table.css" %}
<link rel="stylesheet"
href="{% static 'main/css/form.css' %}"
type="text/css" />
<link rel="stylesheet"
href="{% static 'main/css/table.css' %}"
type="text/css" />
{% endblock %}
{% block body %}
{% spaceless %}

View File

@ -12,9 +12,15 @@
{% endblock %}
{% block link %}
{{ block.super }}
{% css "main/css/form.css" %}
{% css "main/css/table.css" %}
{% css "main/css/plot.css" %}
<link rel="stylesheet"
href="{% static 'main/css/form.css' %}"
type="text/css" />
<link rel="stylesheet"
href="{% static 'main/css/table.css' %}"
type="text/css" />
<link rel="stylesheet"
href="{% static 'main/css/plot.css' %}"
type="text/css" />
{% endblock %}
{% block body %}
{% with instance=form.instance %}
@ -30,7 +36,7 @@
{% block pre %}{% endblock %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% if instance|adding %}<input hidden name="next" value="{{ request.path }}">{% endif %}
{% if instance|adding %}<input hidden name="next" value="{{ request.path }}" />{% endif %}
{{ form }}
</form>
{% block tables %}{% endblock %}

View File

@ -29,11 +29,11 @@
{% if not form.instance|adding %}
<a class="del" href="{{ form.instance.get_delete_url }}">{% translate "Delete" %}</a>
{% endif %}
<input type="reset" value="{% translate "Reset" %}">
<input type="reset" value="{% translate "Reset" %}" />
{% if form.instance|adding %}
<input type="submit" value="{% translate "Create" %}">
<input type="submit" value="{% translate "Create" %}" />
{% else %}
<input type="submit" value="{% translate "Save" %}">
<input type="submit" value="{% translate "Save" %}" />
{% endif %}
{% endblock %}
</td>

View File

@ -1,7 +1,7 @@
{% extends "main/form/form_base.html" %}
{% load i18n %}
{% block buttons %}
<input hidden value="{{ next }}" name="next">
<input type="reset">
<input type="submit" value="{% translate "Log in" %}">
<input hidden value="{{ next }}" name="next" />
<input type="reset" />
<input type="submit" value="{% translate "Log in" %}" />
{% endblock %}

View File

@ -4,66 +4,44 @@
{% load i18n %}
{% block link %}
{{ block.super }}
{% css "main/css/table.css" %}
{% css "main/css/plot.css" %}
<link rel="stylesheet"
href="{% static 'main/css/table.css' %}"
type="text/css" />
<link rel="stylesheet"
href="{% static 'main/css/plot.css' %}"
type="text/css" />
{% endblock %}
{% block body %}
<div class="split">
<section class="accounts">
<h2>{% translate "Accounts" %}</h2>
<div class="table">
<table>
<thead>
<tr>
<th colspan="2">{% translate "Account" %}</th>
<th>{% translate "Balance" %}</th>
<th>{% translate "Edit" %}</th>
</tr>
</thead>
<tbody>
<p>
{% for acc in accounts %}
<tr>
<td>{{ acc.icon|remix }}</td>
<th class="l">
<a href="{{ acc.get_absolute_url }}">{{ acc }}</a>
</th>
<td class="value">{{ acc.statement_set.first.value|value }}</td>
<td>
<a href="{% url "edit_account" acc.pk %}">{% translate "Edit" %}</a>
</td>
</tr>
<a class="big-link" href="{{ acc.get_absolute_url }}">{{ acc.icon|remix }}{{ acc }}</a>
{% empty %}
<tr>
<td class="empty" colspan="4">{% translate "No account" %}</td>
</tr>
{% translate "No account" %}
{% endfor %}
</tbody>
<tfoot>
<tr class="new">
<td colspan="4">
<a href="{% url "new_account" %}">{% translate "Create account" %}</a>
</td>
</tr>
</tfoot>
</table>
</div>
</section>
<section class="categories">
</p>
{% if transactions %}
<h2>{% translate "Transactions" %}</h2>
{% include "transaction/transaction_table.html" %}
{% endif %}
{% if categories %}
<h2>{% translate "Categories" %}</h2>
{% spaceless %}
<p>
{% for cat in categories %}
<a class="big-link" href="{{ cat.get_absolute_url }}">{{ cat.icon|remix }}{{ cat }}</a>
{% empty %}
{% translate "No category" %}
{% endfor %}
<a class="big-link add" href="{% url "new_category" %}">{{ "add"|remix }}{% translate "Create category" %}</a>
</p>
{% endspaceless %}
</section>
</div>
{% if history %}
<section>
{% endif %}
{% if statements %}
<h2>{% translate "Statements" %}</h2>
{% include "statement/statement_table.html" %}
{% endif %}
{% if history.data %}
<h2>{% translate "History" %}</h2>
{% include "history/plot.html" %}
</section>
{% endif %}
{% endblock %}

View File

@ -13,15 +13,14 @@
{% endblock %}
{% block link %}
{{ block.super }}
{% css "main/css/table.css" %}
<link rel="stylesheet"
href="{% static 'main/css/table.css' %}"
type="text/css" />
{% endblock %}
{% block body %}
<h2>
{% block h2 %}{% endblock %}
</h2>
{% if account or category or search %}
<div class="backlinks">
{% block backlinks %}
{% if account %}
<p>
<a class="big-link" href="{{ account.get_absolute_url }}">{{ account.icon|remix }}{{ account }}</a>
@ -37,9 +36,6 @@
<a href="{% url "search" %}">{% translate "Search" %}</a>
</p>
{% endif %}
{% endblock %}
</div>
{% endif %}
{% include "main/pagination.html" %}
{% block table %}{% endblock %}
{% include "main/pagination.html" %}

View File

@ -1,11 +1,14 @@
{% extends "main/base.html" %}
{% load main_extras %}
{% load static %}
{% load i18n %}
{% block link %}
{{ block.super }}
{% css "main/css/table.css" %}
{% css "main/css/form.css" %}
<link rel="stylesheet"
href="{% static 'main/css/table.css' %}"
type="text/css" />
<link rel="stylesheet"
href="{% static 'main/css/form.css' %}"
type="text/css" />
{% endblock %}
{% block body %}
<h2>{% translate "Log in" %}</h2>

View File

@ -1,32 +1,9 @@
{% load i18n transaction_extras %}
{% load i18n %}
{% if page_obj %}
<p class="pagination">
<p id="pagination">
{% for page in paginator.page_range %}
<a href="?page={{ page }}"
{% if page == page_obj.number %}class="cur"{% endif %}>{{ page }}</a>
{% endfor %}
</p>
{% endif %}
{% if month %}
<p class="pagination">{% year_url month %}</p>
<p class="pagination n3">
{% if previous_month %}
{% month_url previous_month fmt="F Y" %}
{% endif %}
{% month_url month cls="cur" fmt="F Y" %}
{% if next_month %}
{% month_url next_month fmt="F Y" %}
{% endif %}
</p>
{% endif %}
{% if year %}
<p class="pagination n3">
{% 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 %}
</p>
{% endif %}

View File

@ -1,5 +1,4 @@
from django import template
from django.templatetags.static import static
from django.utils import formats
from django.utils.safestring import mark_safe
@ -8,16 +7,16 @@ register = template.Library()
@register.filter
def value(val, pm=False, r=2):
if val is None:
if not val:
return ""
_prefix = ""
_suffix = "&nbsp;€"
_val = formats.number_format(val, decimal_pos=r, use_l10n=True, force_grouping=True)
_val = formats.number_format(round(val, r), r, use_l10n=True, force_grouping=True)
if val > 0:
if pm:
_prefix += "&plus;&nbsp;"
elif val < 0:
else:
_val = _val[1:]
_prefix += "&minus;&nbsp;"
@ -39,6 +38,14 @@ def remix(icon, cls=""):
return mark_safe(f"""<span class="ri-{icon}-line {cls}"></span>""")
@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()
@ -52,10 +59,3 @@ def verbose_name(obj):
@register.filter
def adding(obj):
return obj._state.adding
@register.simple_tag
def css(href):
return mark_safe(
f"""<link rel="stylesheet" href="{static(href)}" type="text/css">"""
)

View File

@ -9,7 +9,6 @@ from django.utils.translation import gettext as _
from django.views.generic import (
CreateView,
DeleteView,
DetailView,
ListView,
TemplateView,
UpdateView,
@ -77,10 +76,6 @@ 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")

View File

@ -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: 2023-04-22 15:16+0200\n"
"PO-Revision-Date: 2023-04-22 15:20+0200\n"
"Last-Translator: Edgar P. Burkhart <traduction@edgarpierre.fr>\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:14
#: .\search\templates\search\search.html:18
#: .\search\templates\search\search_form.html:5
msgid "Search"
msgstr "Rechercher"

View File

@ -7,8 +7,12 @@
{% endblock %}
{% block link %}
{{ block.super }}
{% css "main/css/form.css" %}
{% css "main/css/table.css" %}
<link rel="stylesheet"
href="{% static 'main/css/form.css' %}"
type="text/css" />
<link rel="stylesheet"
href="{% static 'main/css/table.css' %}"
type="text/css" />
{% endblock %}
{% block body %}
<h2>{% translate "Search" %}</h2>

View File

@ -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: 2023-11-25 12:05+0100\n"
"PO-Revision-Date: 2023-04-22 15:22+0200\n"
"Last-Translator: Edgar P. Burkhart <traduction@edgarpierre.fr>\n"
"Language-Team: \n"
@ -38,7 +38,7 @@ msgid "Start value"
msgstr "Valeur initiale"
#: .\statement\models.py:29
#: .\statement\templates\statement\statement_table.html:30
#: .\statement\templates\statement\statement_table.html:27
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:18
#: .\statement\templates\statement\statement_table.html:5
msgid "Create statement"
msgstr "Créer un relevé"
@ -74,32 +74,32 @@ msgstr "Créer un relevé"
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_table.html:31
#: .\statement\templates\statement\statement_form.html:24
#: .\statement\templates\statement\statement_table.html:28
msgid "Transactions"
msgstr "Transactions"
#: .\statement\templates\statement\statement_table.html:25
#: .\statement\templates\statement\statement_table.html:22
msgid "Date"
msgstr "Date"
#: .\statement\templates\statement\statement_table.html:27
#: .\statement\templates\statement\statement_table.html:24
msgid "Account"
msgstr "Compte"
#: .\statement\templates\statement\statement_table.html:29
#: .\statement\templates\statement\statement_table.html:26
msgid "Value"
msgstr "Valeur"
#: .\statement\templates\statement\statement_table.html:62
#: .\statement\templates\statement\statement_table.html:56
msgid "No statement"
msgstr "Aucun relevé"
#: .\statement\templates\statement\statement_table.html:70
#: .\statement\templates\statement\statement_table.html:64
msgid "View all statements"
msgstr "Voir tous les relevés"

View File

@ -1,12 +1,15 @@
{% extends "main/form/base.html" %}
{% load i18n main_extras statement_extras category %}
{% load i18n main_extras category %}
{% block title_new %}
{% translate "Create statement" %}
{% endblock %}
{% block h2_new %}
{% translate "New statement" %}
{% endblock %}
{% block h2 %}{{ form.instance }}{% endblock %}
{% block h2 %}
{{ form.instance.sum|check:form.instance.diff }}
{{ form.instance }}
{% endblock %}
{% block pre %}
{% if account %}
<p>
@ -16,13 +19,9 @@
{% endblock %}
{% block tables %}
{% if not form.instance|adding %}
<section>
<h3>{% translate "Categories" %}</h3>
{% category_plot transactions budget=False statement=object %}
</section>
<section>
<h3>{% translate "Transactions" %}</h3>
{% category_plot transactions %}
<h3>{% translate "Transactions" %} ({{ form.instance.sum|pmvalue }} / {{ form.instance.diff|pmvalue }})</h3>
{% include "transaction/transaction_table.html" %}
</section>
{% endif %}
{% endblock %}

View File

@ -7,6 +7,5 @@
{% translate "Statements" %}
{% endblock %}
{% block table %}
{% url "new_statement" as new_statement_url %}
{% include "statement/statement_table.html" %}
{% endblock %}

View File

@ -1,4 +1,10 @@
{% load i18n main_extras statement_extras %}
{% load main_extras %}
{% load i18n %}
{% if new_statement_url %}
<p>
<a href="{{ new_statement_url }}">{% translate "Create statement" %}</a>
</p>
{% endif %}
<div id="statements" class="table">
<table class="full-width {% if statements_url %}more{% endif %}">
<colgroup>
@ -11,14 +17,6 @@
<col class="value" span="3">
</colgroup>
<thead>
{% if new_statement_url %}
<tr class="new">
<td colspan="{% if account %}6{% else %}8{% endif %}">
<a href="{{ new_statement_url }}">{% translate "Create statement" %}</a>
</td>
</tr>
{% endif %}
<tr>
<th>{{ "check"|remix }}</th>
<th>{{ "attachment"|remix }}</th>
<th>{% translate "Date" %}</th>
@ -28,19 +26,20 @@
<th>{% translate "Value" %}</th>
<th>{% translate "Difference" %}</th>
<th>{% translate "Transactions" %}</th>
</tr>
</thead>
<tbody>
{% for snap in statements %}
<tr>
<td class="c">{{ snap.sum|check:snap.diff }}</td>
{% if snap.sum == snap.diff %}
<td class="c green">{{ "check"|remix }}</td>
{% else %}
<td class="c red">{{ "close"|remix }}</td>
{% endif %}
<td class="c">
{% if snap.file %}<a href="{{ snap.file.url }}">{{ "attachment"|remix }}</a>{% endif %}
</td>
<th class="date" scope="row">
<a href="{{ snap.get_absolute_url }}">
<time datetime="{{ snap.date|date:"Y-m-d" }}">{{ snap.date|date:"Y-m-d" }}</time>
</a>
<a href="{{ snap.get_absolute_url }}">{{ snap.date|date:"Y-m-d" }}</a>
</th>
{% if not account %}
<td class="r">{{ snap.account.icon|remix }}</td>
@ -58,14 +57,10 @@
</tr>
{% endfor %}
</tbody>
{% if statements_url %}
<tfoot>
<tr class="more">
<td colspan="{% if account %}6{% else %}8{% endif %}">
<a href="{{ statements_url }}">{% translate "View all statements" %}</a>
</td>
</tr>
</tfoot>
{% endif %}
</table>
</div>
{% if statements_url %}
<p>
<a href="{{ statements_url }}">{% translate "View all statements" %}</a>
</p>
{% endif %}

View File

@ -1,14 +0,0 @@
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")

View File

@ -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: 2023-11-25 12:05+0100\n"
"PO-Revision-Date: 2023-04-23 08:03+0200\n"
"Last-Translator: Edgar P. Burkhart <traduction@edgarpierre.fr>\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:10
#: .\transaction\templates\transaction\transaction_table.html:32
#: .\transaction\templates\transaction\invoice_table.html:9
#: .\transaction\templates\transaction\transaction_table.html:28
msgid "Name"
msgstr "Nom"
@ -32,11 +32,12 @@ 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:31
#: .\transaction\templates\transaction\transaction_table.html:27
msgid "Date"
msgstr "Date"
@ -45,7 +46,7 @@ msgid "Real date"
msgstr "Date réelle"
#: .\transaction\models.py:28
#: .\transaction\templates\transaction\transaction_table.html:35
#: .\transaction\templates\transaction\transaction_table.html:30
msgid "Trader"
msgstr "Commerçant"
@ -54,7 +55,7 @@ msgid "Payment"
msgstr "Paiement"
#: .\transaction\models.py:38
#: .\transaction\templates\transaction\transaction_table.html:37
#: .\transaction\templates\transaction\transaction_table.html:32
msgid "Category"
msgstr "Catégorie"
@ -63,13 +64,12 @@ msgid "Statement"
msgstr "Relevé"
#: .\transaction\models.py:48
#: .\transaction\templates\transaction\transaction_table.html:40
#: .\transaction\templates\transaction\transaction_table.html:35
msgid "Account"
msgstr "Compte"
#: .\transaction\models.py:83
#: .\transaction\templates\transaction\transaction_archive_month.html:27
#: .\transaction\templates\transaction\transaction_archive_year.html:31
#: .\transaction\templates\transaction\transaction_archive_month.html:11
#: .\transaction\templates\transaction\transaction_list.html:4
#: .\transaction\templates\transaction\transaction_list.html:7
msgid "Transactions"
@ -80,18 +80,18 @@ msgid "Invoice"
msgstr "Facture"
#: .\transaction\models.py:94
#: .\transaction\templates\transaction\invoice_table.html:11
#: .\transaction\templates\transaction\invoice_table.html:22
#: .\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:20
#: .\transaction\templates\transaction\transaction_form.html:19
msgid "Invoices"
msgstr "Factures"
#: .\transaction\templates\transaction\invoice_form.html:4
#: .\transaction\templates\transaction\invoice_table.html:37
#: .\transaction\templates\transaction\invoice_table.html:35
msgid "Create invoice"
msgstr "Créer une facture"
@ -99,31 +99,21 @@ msgstr "Créer une facture"
msgid "New invoice"
msgstr "Nouvelle facture"
#: .\transaction\templates\transaction\invoice_table.html:12
#: .\transaction\templates\transaction\invoice_table.html:25
#: .\transaction\templates\transaction\invoice_table.html:11
#: .\transaction\templates\transaction\invoice_table.html:23
msgid "Delete"
msgstr "Supprimer"
#: .\transaction\templates\transaction\invoice_table.html:30
#: .\transaction\templates\transaction\invoice_table.html:28
msgid "No invoice"
msgstr "Aucune facture"
#: .\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:22
#: .\transaction\templates\transaction\transaction_archive_year.html:26
msgid "Categories"
msgstr "Catégories"
#: .\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:25
#: .\transaction\templates\transaction\transaction_table.html:5
msgid "Create transaction"
msgstr "Créer une transaction"
@ -131,18 +121,10 @@ msgstr "Créer une transaction"
msgid "New transaction"
msgstr "Nouvelle transaction"
#: .\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
#: .\transaction\templates\transaction\transaction_table.html:71
msgid "No transaction"
msgstr "Aucune transaction"
#: .\transaction\templates\transaction\transaction_table.html:95
#: .\transaction\templates\transaction\transaction_table.html:80
msgid "View all transactions"
msgstr "Voir toutes les transactions"

View File

@ -6,11 +6,9 @@
<col class="desc" span="3">
</colgroup>
<thead>
<tr>
<th>{% translate "Name" %}</th>
<th>{% translate "File" %}</th>
<th>{% translate "Delete" %}</th>
</tr>
</thead>
<tbody>
{% for invoice in transaction.invoices %}
@ -22,7 +20,7 @@
<a href="{{ invoice.file.url }}">{% translate "File" %} [{{ invoice.file|extension }}]</a>
</td>
<td>
<a href="{{ invoice.get_delete_url }}">{% translate "Delete" %}</a>
<a href="{{ invoice.get_delete_url }}">{% translate "Delete" %}
</td>
</tr>
{% empty %}
@ -32,7 +30,7 @@
{% endfor %}
</tbody>
<tfoot>
<tr class="more">
<tr>
<td colspan="3">
<a href="{% url "new_invoice" transaction.pk %}">{% translate "Create invoice" %}</a>
</td>

View File

@ -1,28 +1,17 @@
{% extends "transaction/transaction_list.html" %}
{% load i18n main_extras transaction_extras static category %}
{% load i18n static category %}
{% block link %}
{{ block.super }}
{% css "main/css/plot.css" %}
<link rel="stylesheet"
href="{% static 'main/css/plot.css' %}"
type="text/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 %}
<p class="back">
<a href="{% url "transaction_month" month.year month.month %}">{% translate "Back" %}{{ "arrow-go-back"|remix }}</a>
</p>
{% endif %}
{% endblock %}
{% block table %}
{% if not category %}
<section>
<h3>{% translate "Categories" %}</h3>
{% category_plot transactions month=month %}
</section>
{% endif %}
<section>
<h3>{% translate "Transactions" %}</h3>
{{ block.super }}
</section>
{% if not category %}
<h3>{% translate "Categories" %}</h3>
{% category_plot transactions month=month %}
{% endif %}
{% endblock %}

View File

@ -1,34 +0,0 @@
{% extends "transaction/transaction_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 backlinks %}
{{ block.super }}
{% if account or category %}
<p class="back">
<a href="{% url "transaction_year" year.year %}">{% translate "Back" %}{{ "arrow-go-back"|remix }}</a>
</p>
{% endif %}
{% endblock %}
{% block table %}
{% if history %}
<section>
<h3>{% translate "History" %}</h3>
{% include "history/plot.html" %}
</section>
{% endif %}
{% if not category %}
<section>
<h3>{% translate "Categories" %}</h3>
{% category_plot transactions year=year %}
</section>
{% endif %}
<section>
<h3>{% translate "Transactions" %}</h3>
{{ block.super }}
</section>
{% endblock %}

View File

@ -8,17 +8,15 @@
{% translate "New transaction" %}
{% endblock %}
{% block pre %}
{% if statement %}
{% if snapshot %}
<p>
<a href="{{ statement.get_absolute_url }}">{{ statement }}</a>
<a href="{{ snapshot.get_absolute_url }}">{{ snapshot }}</a>
</p>
{% endif %}
{% endblock %}
{% block tables %}
{% if not form.instance|adding %}
<section>
<h3>{% translate "Invoices" %}</h3>
{% include "transaction/invoice_table.html" %}
</section>
{% endif %}
{% endblock %}

View File

@ -7,6 +7,5 @@
{% translate "Transactions" %}
{% endblock %}
{% block table %}
{% url "new_transaction" as new_transaction_url %}
{% include "transaction/transaction_table.html" %}
{% endblock %}

View File

@ -1,13 +1,17 @@
{% load main_extras transaction_extras %}
{% load main_extras %}
{% load i18n %}
{% if new_transaction_url %}
<p>
<a href="{{ new_transaction_url }}">{% translate "Create transaction" %}</a>
</p>
{% endif %}
<div id="transactions" class="table">
<table class="full-width">
<table class="full-width {% if transactions_url %}more{% endif %}">
<colgroup>
<col class="icon">
<col class="date">
<col class="desc">
<col class="value">
<col class="value">
<col class="desc">
{% if not category %}
<col class="icon">
@ -19,19 +23,10 @@
{% endif %}
</colgroup>
<thead>
{% if new_transaction_url %}
<tr class="new">
<td colspan="{% tr_colspan %}">
<a href="{{ new_transaction_url }}">{% translate "Create transaction" %}</a>
</td>
</tr>
{% endif %}
<tr>
<th>{{ "attachment"|remix }}</th>
<th>{% translate "Date" %}</th>
<th>{% translate "Name" %}</th>
<th>{% translate "Expenses" %}</th>
<th>{% translate "Income" %}</th>
<th>{% translate "Value" %}</th>
<th>{% translate "Trader" %}</th>
{% if not category %}
<th colspan="2">{% translate "Category" %}</th>
@ -39,7 +34,6 @@
{% if not account %}
<th colspan="2">{% translate "Account" %}</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for trans in transactions %}
@ -47,23 +41,11 @@
<td class="c">
{% for invoice in trans.invoices %}<a href="{{ invoice.file.url }}">{{ "attachment"|remix }}</a>{% endfor %}
</td>
<td class="date">
<time datetime="{{ trans.date|date:"Y-m-d" }}">
{% if month %}
{{ trans.date|date:"l d"|capfirst }}
{% elif year %}
{{ trans.date|date:"d F" }}
{% else %}
{{ trans.date|date:"Y-m-d" }}
{% endif %}
</time>
</td>
<td class="date">{{ trans.date|date:"Y-m-d" }}</td>
<th scope="row" class="l">
<a href="{{ trans.get_absolute_url }}">{{ trans.name }}</a>
</th>
{% if trans.value >= 0 %}<td></td>{% endif %}
<td class="value">{{ trans.value|pmvalue }}</td>
{% if trans.value < 0 %}<td></td>{% endif %}
<td>{{ trans.trader|default_if_none:"" }}</td>
{% if not category %}
{% if trans.category %}
@ -84,18 +66,17 @@
</tr>
{% empty %}
<tr>
<td class="empty" colspan="{% tr_colspan %}">{% translate "No transaction" %}</td>
<td class="empty"
colspan="{% if category and account %}5{% elif category or account %}7{% else %}9{% endif %}">
{% translate "No transaction" %}
</td>
</tr>
{% endfor %}
</tbody>
{% if transactions_url %}
<tfoot>
<tr class="more">
<td colspan="{% tr_colspan %}">
<a href="{{ transactions_url }}">{% translate "View all transactions" %}</a>
</td>
</tr>
</tfoot>
{% endif %}
</table>
</div>
{% if transactions_url %}
<p>
<a href="{{ transactions_url }}">{% translate "View all transactions" %}</a>
</p>
{% endif %}

View File

@ -1,46 +0,0 @@
import datetime
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(takes_context=True)
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"""<a class="{cls}" href="{url}"><time datetime="{date(month, "Y-m")}">"""
f"""{date(month, fmt).capitalize()}</time></a>"""
)
@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"""<a class="{cls}" href="{url}">"""
f"""<time datetime="{year}">{year}</time></a>"""
)
@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

View File

@ -4,11 +4,6 @@ from . import views
urlpatterns = [
path("list", views.TransactionListView.as_view(), name="transactions"),
path(
"history/<int:year>",
views.TransactionYearView.as_view(),
name="transaction_year",
),
path(
"history/<int:year>/<int:month>",
views.TransactionMonthView.as_view(),

View File

@ -1,9 +0,0 @@
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 := context.get("category"):
url_name = "category_" + url_name
url_params |= {"category": category.pk}
return url_name, url_params

View File

@ -2,8 +2,7 @@ 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, YearArchiveView
from history.utils import history
from django.views.generic.dates import MonthArchiveView
from main.views import (
NummiCreateView,
NummiDeleteView,
@ -110,8 +109,11 @@ class TransactionListView(NummiListView):
context_object_name = "transactions"
class TransactionACMixin:
class TransactionMonthView(UserMixin, MonthArchiveView):
model = Transaction
date_field = "date"
context_object_name = "transactions"
month_format = "%m"
def get_queryset(self):
if "account" in self.kwargs:
@ -136,25 +138,3 @@ class TransactionACMixin:
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"
class TransactionYearView(UserMixin, TransactionACMixin, YearArchiveView):
model = Transaction
date_field = "date"
context_object_name = "transactions"
make_object_list = True
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**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)}