Compare commits

..

3 commits
main ... api

248 changed files with 22465 additions and 6414 deletions

View file

@ -1,29 +0,0 @@
name: Build and push Docker image
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: code.edgarpierre.fr
username: ${{ vars.DOCKER_PUSH_USERNAME }}
password: ${{ secrets.DOCKER_PUSH_PASSWORD }}
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: |
code.edgarpierre.fr/${{ github.repository }}:${{ github.ref_name }}
code.edgarpierre.fr/${{ github.repository }}:latest

8
.gitignore vendored
View file

@ -1,3 +1,7 @@
/env*
env*
__pycache__
/media
*.pkg.tar.zst
/nummi-git/
/pkg/
/src/
/nummi/media

View file

@ -1,31 +0,0 @@
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:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
rev: "6.0.0"
hooks:
- id: flake8
args: ["--max-line-length=88", "--extend-ignore=E203"]
- repo: https://github.com/Riverside-Healthcare/djLint
rev: v1.23.3
hooks:
- id: djlint-django
args: ["--reformat", "--lint", "--quiet"]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v3.0.0-alpha.6"
hooks:
- id: prettier
types_or: ["css", "javascript", "svg"]

View file

@ -1 +0,0 @@
endOfLine = "auto"

View file

@ -1 +0,0 @@
3.12

22
.vscode/launch.json vendored
View file

@ -1,22 +0,0 @@
{
// 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
}
]
}

View file

@ -1,17 +0,0 @@
FROM ghcr.io/astral-sh/uv:debian-slim
ADD . /app
WORKDIR /app
RUN useradd -m -r nummi && \
chown -R nummi /app
USER nummi
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV NUMMI_CONFIG=/nummi/config.toml
RUN uv sync --locked
CMD ["/app/entrypoint.sh"]

47
PKGBUILD Normal file
View file

@ -0,0 +1,47 @@
pkgname=nummi-git
pkgver=r151.ee26d0d
pkgrel=1
pkgdesc="Web-based accounting interface"
arch=("any")
url="https://git.edgarpierre.fr/edpibu/nummi"
license=('AGPL3')
depends=(
"gunicorn"
"python-django"
"python-matplotlib"
"python-toml"
"python-psycopg2"
)
makedepends=("git")
optdepends=("postgresql: database")
backup=("etc/${pkgname%-git}/config.toml")
source=(
"${pkgname}::git+https://git.edgarpierre.fr/edpibu/nummi"
"${pkgname%-git}.service"
"${pkgname%-git}.tmpfiles"
"${pkgname%-git}.sysusers"
"${pkgname%-git}.nginx"
"config.toml"
)
b2sums=('SKIP'
'9e9b62a141bb4594dc203978d5c56dd397ed6c095e6f83fdea07ded2fa46dd2e052aa8f80729a3b612e3773aa487660fcca791079ad4885b825c9c831df61b45'
'cd42e864d82ca5ea191d2c15cec55afea272a875f19ebc1252e03fb86ef3d62820e2fedd706a8da0b0e3a7aee355e0bb5dcdb7f3bd803497c1320fab540f7d70'
'78bebc6cca3f8b520783565de9122cc57e62eb629d3693dfaa322a77f4be3c55c5cd5a460bd1f6f3028f4dd1b2018ec6166b7f9aaf55ac3be6178bd1a3a00405'
'c878c463ecb58d94b3f9b96d798b098a7718aec6df4b424a60674c0a9daf9abcb4556e527e55f67dbb093e109145b0ca17556f7dce66116ba7b170ead61acc16'
'5bdc097ab3fa7e8e128d194bfb6c0fefb5528903e2b8765557a82033f8925c1174dbe73007a595e32c5e415cf6f19957c35bbe463c50d2813894c20c8a28a88d')
pkgver() {
cd "$pkgname"
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
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}/
cd ${pkgname}/${pkgname%-git}
find * -type f -exec install -Dm0644 "{}" "${pkgdir}/usr/share/webapps/${pkgname%-git}/{}" \;
}

View file

@ -1,23 +0,0 @@
services:
nummi:
image: code.edgarpierre.fr/edpibu/nummi
container_name: nummi
restart: unless-stopped
ports:
- 33001:8000
volumes:
- /docker/nummi/config:/nummi
- /docker/nummi/static:/app/static
- /docker/nummi/media:/app/media
depends_on:
- postgres
postgres:
image: postgres:17-alpine
container_name: nummi_postgres
restart: unless-stopped
environment:
POSTGRES_USER: nummi
POSTGRES_PASSWORD:
volumes:
- /docker/nummi/postgres:/var/lib/postgresql/data

View file

@ -1,5 +0,0 @@
#!/usr/bin/env sh
cd /app/nummi
uv run manage.py collectstatic --noinput
uv run manage.py migrate --noinput
uv run gunicorn --bind :8000 --workers 2 nummi.wsgi:application

View file

@ -1,6 +0,0 @@
from django.apps import AppConfig
class AccountConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "account"

View file

@ -1,22 +0,0 @@
from django.forms.widgets import Select
from main.forms import IconInput, NummiForm
from .models import Account
class AccountForm(NummiForm):
class Meta:
model = Account
fields = [
"name",
"icon",
"default",
"archived",
]
widgets = {
"icon": IconInput(),
}
class AccountSelect(Select):
template_name = "account/forms/widgets/account.html"

View file

@ -1,75 +0,0 @@
# 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 <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \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 <traduction@edgarpierre.fr>\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:12 .\account\models.py:45 .\account\models.py:53
#: .\account\templates\account\account_list.html:9
msgid "Account"
msgstr "Compte"
#: .\account\models.py:12
msgid "Name"
msgstr "Nom"
#: .\account\models.py:16
msgid "Icon"
msgstr "Icône"
#: .\account\models.py:18
msgid "Default"
msgstr "Défaut"
#: .\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:15
msgid "Edit account"
msgstr "Modifier le compte"
#: .\account\templates\account\account_detail.html:18
msgid "Statements"
msgstr "Relevés"
#: .\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"

View file

@ -1,16 +0,0 @@
# 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,
),
]

View file

@ -1,20 +0,0 @@
# 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",
},
),
]

View file

@ -1,57 +0,0 @@
from uuid import uuid4
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 NummiModel
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(
max_length=24,
default="bank",
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:
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", args=(self.pk,))
def get_delete_url(self):
return reverse("del_account", args=(self.pk,))
@property
def transactions(self):
return apps.get_model("transaction", "Transaction").objects.filter(
statement__account=self
)
class Meta:
ordering = ["-default", "archived", "name"]
verbose_name = _("Account")
verbose_name_plural = _("Accounts")
class AccountModel(NummiModel):
account = models.ForeignKey(
Account,
on_delete=models.CASCADE,
verbose_name=_("Account"),
)
class Meta:
abstract = True

View file

@ -1,27 +0,0 @@
{% extends "main/base.html" %}
{% load main_extras history_extras statement_extras %}
{% load i18n %}
{% block title %}
{{ account }} {{ block.super }}
{% endblock title %}
{% block link %}
{{ block.super }}
{% css "main/css/table.css" %}
{% css "main/css/plot.css" %}
{% endblock link %}
{% block body %}
<h2>{{ account.icon|remix }}{{ account }}</h2>
<p>
<a href="{% url "edit_account" account.pk %}">{{ "edit"|remix }}{% translate "Edit account" %}</a>
</p>
<section>
<h3>{% translate "Statements" %}</h3>
{% 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 %}
</section>
<section>
<h3>{% translate "History" %}</h3>
{% history_plot account.transactions account=account %}
</section>
{% endblock body %}

View file

@ -1,10 +0,0 @@
{% extends "main/form/base.html" %}
{% load main_extras %}
{% load i18n %}
{% block title_new %}
{% translate "Create account" %}
{% endblock %}
{% block h2_new %}
{% translate "New account" %}
{% endblock %}
{% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %}

View file

@ -1,16 +0,0 @@
{% extends "main/list.html" %}
{% load i18n main_extras account_extras %}
{% block link %}
{{ block.super }}
{% css "main/css/table.css" %}
{% css "main/css/plot.css" %}
{% endblock link %}
{% block name %}
{% translate "Account" %}
{% endblock name %}
{% block h2 %}
{% translate "Accounts" %}
{% endblock h2 %}
{% block table %}
<div class="split">{% account_table accounts %}</div>
{% endblock table %}

View file

@ -1,39 +0,0 @@
{% load i18n main_extras %}
<dl class="accounts">
{% for acc in accounts %}
<div class="account {% if not search and acc.archived %}archived{% endif %}">
<dt>
<a href="{{ acc.get_absolute_url }}">{{ acc.icon|remix }}{{ acc }}</a>
</dt>
<dd class="value">
{% if acc.statement_set.first %}{{ acc.statement_set.first.value|value }}{% endif %}
</dd>
</div>
{% endfor %}
{% if index %}
<div class="more account">
<dt>
<a href="{% url "accounts" %}">{{ "gallery-view"|remixnl }}{% translate "All accounts" %}</a>
</dt>
<dd class="value">
{{ accounts|balance|value }}
</dd>
</div>
{% elif not search %}
<div class="more account">
<dt>
<label class="wi" for="show-archived-accounts">
{{ "archive"|remix }}{% translate "Show archived" %}
</label>
</dt>
<dd>
<input type="checkbox" class="show-archived" id="show-archived-accounts" />
</dd>
</div>
<div class="new account">
<dt>
<a href="{% url "new_account" %}">{{ "add-box"|remix }}{% translate "Create account" %}</a>
</dt>
</div>
{% endif %}
</dl>

View file

@ -1,5 +0,0 @@
{% load main_extras %}
<span class="ico-input account-select">
{{ "bank"|remix }}
{% include "django/forms/widgets/select.html" %}
</span>

View file

@ -1,10 +0,0 @@
from django import template
register = template.Library()
@register.inclusion_tag("account/account_table.html")
def account_table(accounts, **kwargs):
return kwargs | {
"accounts": accounts,
}

View file

@ -1,31 +0,0 @@
from django.urls import path
from statement.views import StatementCreateView
from . import views
urlpatterns = [
path("list", views.AccountListView.as_view(), name="accounts"),
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>/transactions",
views.AccountTListView.as_view(),
name="account_transactions",
),
path(
"<account>/statements",
views.AccountSListView.as_view(),
name="account_statements",
),
path(
"<account>/statement",
StatementCreateView.as_view(),
name="new_statement",
),
path(
"<account>/delete",
views.AccountDeleteView.as_view(),
name="del_account",
),
]

View file

@ -1,60 +0,0 @@
from django.shortcuts import get_object_or_404
from main.views import (
NummiCreateView,
NummiDeleteView,
NummiDetailView,
NummiListView,
NummiUpdateView,
)
from statement.views import StatementListView
from transaction.views import TransactionListView
from .forms import AccountForm
from .models import Account
class AccountCreateView(NummiCreateView):
model = Account
form_class = AccountForm
class AccountUpdateView(NummiUpdateView):
model = Account
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"
context_object_name = "account"
class AccountMixin:
def get_queryset(self):
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) | {"account": self.account}
class AccountListView(NummiListView):
model = Account
context_object_name = "accounts"
class AccountTListView(AccountMixin, TransactionListView):
pass
class AccountSListView(AccountMixin, StatementListView):
pass

3
nummi/api/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View file

@ -1,6 +1,6 @@
from django.apps import AppConfig
class SearchConfig(AppConfig):
class ApiConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "search"
name = "api"

3
nummi/api/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
nummi/api/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

11
nummi/api/urls.py Normal file
View file

@ -0,0 +1,11 @@
from django.urls import path
from . import views
urlpatterns = [
path("transactions", views.TransactionListView.as_view(), name="transactions"),
path("categories", views.CategoryListView.as_view(), name="categories"),
path("accounts", views.AccountListView.as_view(), name="accounts"),
path("snapshots", views.SnapshotListView.as_view(), name="snapshots"),
path("history", views.HistoryView.as_view(), name="history"),
]

51
nummi/api/views.py Normal file
View file

@ -0,0 +1,51 @@
from django.db.models import Sum
from django.db.models.functions import ExtractQuarter, ExtractYear
from django.http import JsonResponse
from django.views import View
from django.views.generic.list import MultipleObjectMixin
from main.models import Account, Category, Snapshot, Transaction
from main.views import UserMixin
class TransactionListView(UserMixin, MultipleObjectMixin, View):
model = Transaction
def get(self, request, *args, **kwargs):
return JsonResponse({"transactions": list(self.get_queryset().values())})
class CategoryListView(UserMixin, MultipleObjectMixin, View):
model = Category
def get(self, request, *args, **kwargs):
return JsonResponse({"categories": list(self.get_queryset().values())})
class AccountListView(UserMixin, MultipleObjectMixin, View):
model = Account
def get(self, request, *args, **kwargs):
return JsonResponse({"accounts": list(self.get_queryset().values())})
class SnapshotListView(UserMixin, MultipleObjectMixin, View):
model = Snapshot
def get(self, request, *args, **kwargs):
return JsonResponse({"snapshots": list(self.get_queryset().values())})
class HistoryView(UserMixin, MultipleObjectMixin, View):
model = Transaction
def get(self, request, *args, **kwargs):
return JsonResponse(
{
"data": list(
self.get_queryset()
.values("category__name", quarter=ExtractQuarter("date"), year=ExtractYear("date"))
.annotate(Sum("value"))
)
}
)

View file

@ -1,6 +0,0 @@
from django.apps import AppConfig
class CategoryConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "category"

View file

@ -1,21 +0,0 @@
from django.forms.widgets import Select
from main.forms import IconInput, NummiForm
from .models import Category
class CategoryForm(NummiForm):
class Meta:
model = Category
fields = [
"name",
"icon",
"budget",
]
widgets = {
"icon": IconInput,
}
class CategorySelect(Select):
template_name = "category/forms/widgets/category.html"

View file

@ -1,83 +0,0 @@
# 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 <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \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 <traduction@edgarpierre.fr>\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:13
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_detail.html:14
msgid "Edit category"
msgstr "Modifier la catégorie"
#: .\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"
#: .\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_plot.html:14
msgid "Expenses"
msgstr "Dépenses"
#: .\category\templates\category\category_plot.html:15
msgid "Income"
msgstr "Revenus"
#: .\category\templates\category\category_plot.html:58
msgid "No transaction"
msgstr "Aucune transaction"
#: .\category\templates\category\category_plot.html:66
msgid "Total"
msgstr "Total"
#: .\category\templates\category\category_plot.html:89
msgid "Expected total"
msgstr "Total attendu"

View file

@ -1,64 +0,0 @@
# 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)
]

View file

@ -1,16 +0,0 @@
# 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,
),
]

View file

@ -1,33 +0,0 @@
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 NummiModel
class Category(NummiModel):
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", args=(self.pk,))
def get_delete_url(self):
return reverse("del_category", args=(self.pk,))
class Meta:
ordering = ["name"]
verbose_name = _("Category")
verbose_name_plural = _("Categories")

View file

@ -1,28 +0,0 @@
{% extends "main/base.html" %}
{% load i18n main_extras history_extras transaction_extras %}
{% block title %}
{{ category }} {{ block.super }}
{% endblock title %}
{% block link %}
{{ block.super }}
{% css "main/css/table.css" %}
{% css "main/css/plot.css" %}
{% endblock link %}
{% block body %}
<h2>{{ category.icon|remix }}{{ category }}</h2>
<p>
<a href="{% url "edit_category" category.pk %}">{{ "edit"|remix }}{% translate "Edit category" %}</a>
</p>
<section>
<h3>{% translate "Transactions" %}</h3>
{% url_get "transactions" category=category.id as t_url %}
<p>
<a class="big-link" href="{{ t_url }}">{{ "list-check"|remixnl }}{% translate "View all transactions" %}</a>
</p>
{% transaction_table category.transaction_set.all n_max=8 transactions_url=t_url %}
</section>
<section>
<h3>{% translate "History" %}</h3>
{% history_plot category.transaction_set category=category %}
</section>
{% endblock body %}

View file

@ -1,10 +0,0 @@
{% extends "main/form/base.html" %}
{% load main_extras %}
{% load i18n %}
{% block title_new %}
{% translate "Create category" %}
{% endblock %}
{% block h2_new %}
{% translate "New category" %}
{% endblock %}
{% block h2 %}{{ form.instance.icon|remix }}{{ form.instance }}{% endblock %}

View file

@ -1,114 +0,0 @@
{% load main_extras statement_extras history_extras %}
{% load i18n %}
<div class="plot">
<table class="full-width">
<colgroup>
<col class="desc">
<col class="value">
<col span="2" class="bar">
<col class="value">
</colgroup>
<thead>
<tr>
<th scope="col">{% translate "Category" %}</th>
<th scope="col" colspan="2">{% translate "Expenses" %}</th>
<th scope="col" colspan="2">{% translate "Income" %}</th>
</tr>
</thead>
<tbody>
{% spaceless %}
{% for cat in categories %}
<tr>
<th scope="row" class="l wi">
{% if cat.category %}
{% if year %}
<a href="{% history_url year=year category=cat.category account=account.id %}">{{ cat.category__icon|remix }}{{ cat.category__name }}</a>
{% elif month %}
<a href="{% url_get "transactions" start_date=month end_date=month|end_of_month account=account.id category=cat.category %}">{{ cat.category__icon|remix }}{{ cat.category__name }}</a>
{% elif statement %}
<a href="{% url_get "transactions" account=statement.account.id statement=statement.id category=cat.category %}">{{ cat.category_.icon|remix }}{{ cat.category__name }}</a>
{% elif account %}
<a href="{% url_get "transactions" account=account.id category=cat.category %}">{{ cat.category__icon|remix }}{{ cat.category__name }}</a>
{% else %}
{{ cat.category__icon|remix }}{{ cat.category__name }}
{% endif %}
{% endif %}
</th>
<td class="value">{{ cat.sum_m|pmvalue }}</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>
</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>
</div>
{% endif %}
</td>
<td class="value">{{ cat.sum_p|pmvalue }}</td>
</tr>
{% empty %}
<tr>
<td class="empty" colspan="5">{% translate "No transaction" %}</td>
</tr>
{% endfor %}
{% endspaceless %}
</tbody>
<tfoot>
{% if categories %}
<tr>
<th scope="row" class="l">{% translate "Total" %}</th>
<td class="value">{{ total_m|pmvalue }}</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>
</div>
{% endif %}
</td>
<td class="bar p">
<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>
</div>
{% endif %}
</td>
<td class="value">{{ total_p|pmvalue }}</td>
</tr>
{% endif %}
{% if statement and statement.diff != statement.sum %}
<tr>
<th scope="row" class="l">{% translate "Expected total" %}</th>
<td class="c">{{ total|check:statement.diff }}</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>
</table>
</div>

View file

@ -1,5 +0,0 @@
{% load main_extras %}
<span class="ico-input category-select">
{{ "folder"|remix }}
{% include "django/forms/widgets/select.html" %}
</span>

View file

@ -1,34 +0,0 @@
from django import template
from django.db import models
from django.db.models.functions import Greatest
register = template.Library()
@register.inclusion_tag("category/category_plot.html", takes_context=True)
def category_plot(context, transactions, **kwargs):
kwargs.setdefault("account", context.get("account"))
if not kwargs.get("account"):
transactions = transactions.exclude(category__budget=False)
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 (
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("sum"),
)
)

View file

@ -1,15 +0,0 @@
from django.urls import path
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>/transactions",
views.CategoryTListView.as_view(),
name="category_transactions",
),
path("<category>/delete", views.CategoryDeleteView.as_view(), name="del_category"),
]

View file

@ -1,49 +0,0 @@
from django.shortcuts import get_object_or_404
from main.views import (
NummiCreateView,
NummiDeleteView,
NummiDetailView,
NummiUpdateView,
)
from transaction.views import TransactionListView
from .forms import CategoryForm
from .models import Category
class CategoryCreateView(NummiCreateView):
model = Category
form_class = CategoryForm
class CategoryUpdateView(NummiUpdateView):
model = Category
form_class = CategoryForm
pk_url_kwarg = "category"
class CategoryDetailView(NummiDetailView):
model = Category
pk_url_kwarg = "category"
context_object_name = "category"
class CategoryDeleteView(NummiDeleteView):
model = Category
pk_url_kwarg = "category"
class CategoryMixin:
def get_queryset(self):
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) | {"category": self.category}
class CategoryTListView(CategoryMixin, TransactionListView):
pass

View file

@ -1,38 +0,0 @@
# 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 <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \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 <traduction@edgarpierre.fr>\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: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:57
msgid "Expenses"
msgstr "Dépenses"
#: .\history\templates\history\plot.html:58
msgid "Income"
msgstr "Revenus"

View file

@ -1,25 +0,0 @@
{% load history_extras %}
{% if month %}
<p class="pagination">
<a href="{% history_url year=month %}">{{ month.year }}</a>
</p>
{% endif %}
<p class="pagination n3">
{% if month %}
{% if previous_month %}
<a href="{% history_url month=previous_month %}">{{ previous_month|date:"F Y"|capfirst }}</a>
{% endif %}
<a class="cur" href="{% history_url month=month %}">{{ month|date:"F Y"|capfirst }}</a>
{% if next_month %}
<a href="{% history_url month=next_month %}">{{ next_month|date:"F Y"|capfirst }}</a>
{% endif %}
{% elif year %}
{% if previous_year %}
<a href="{% history_url year=previous_year %}">{{ previous_year|date:"Y" }}</a>
{% endif %}
<a class="cur" href="{% history_url year=year %}">{{ year|date:"Y" }}</a>
{% if next_year %}
<a href="{% history_url year=next_year %}">{{ next_year|date:"Y" }}</a>
{% endif %}
{% endif %}
</p>

View file

@ -1,92 +0,0 @@
{% load main_extras %}
{% load history_extras %}
{% load transaction_extras %}
{% load i18n %}
<div class="calendar">
<table>
<thead>
<tr>
{% if not year %}
<th scope="col">{% translate "Year" %}</th>
{% endif %}
{% calendar_head %}
<th scope="col">{% translate "Total" %}</th>
</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">
<a href="{% history_url year=y account=account.id category=category.id %}">{{ y }}</a>
</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="month {% 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 class="month"></td>
{% endif %}
{% if forloop.parentloop.first and forloop.last %}
{% empty_calendar_cells_end m.month.month %}
{% endif %}
{% endfor %}
<td class="total">{{ y_data|sum_year|pmrvalue }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="history plot">
<table class="full-width">
<colgroup>
<col class="icon">
<col class="desc">
<col class="value">
<col span="2" class="bar">
<col class="value">
</colgroup>
<thead>
<tr>
<th scope="col">{{ "expand-up-down"|remix }}</th>
<th scope="col">{% translate "Month" %}</th>
<th scope="col" colspan="2">{% translate "Expenses" %}</th>
<th scope="col" colspan="2">{% translate "Income" %}</th>
</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>
<th class="date" scope="row">
<a href="{% history_url year=date.month.year month=date.month.month account=account.id category=category.id %}">
{% if year %}
{{ date.month|date:"F"|capfirst }}
{% else %}
{{ date.month|date:"Y-m" }}
{% endif %}
</a>
</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="value">{{ date.sum_p|pmrvalue }}</td>
</tr>
{% else %}
<tr class="empty">
<td colspan="6" class="empty"></td>
</tr>
{% endif %}
{% endifchanged %}
{% endfor %}
</tbody>
</table>
</div>

View file

@ -1,51 +0,0 @@
{% extends "main/base.html" %}
{% load i18n static main_extras transaction_extras category history_extras %}
{% block link %}
{{ block.super }}
{% css "main/css/plot.css" %}
{% css "main/css/table.css" %}
{% endblock link %}
{% block body %}
<h2>
{% block h2 %}
{% endblock h2 %}
</h2>
{% history_pagination %}
{% if account or category %}
<p class="back">
<a class="big-link"
href="{% history_url year=year month=month clear=True %}">{{ "arrow-go-back"|remix }}{% translate "Back" %}</a>
{% if account %}
<a class="big-link" href="{% url "account" account.id %}">{{ account.icon|remix }}{{ account }}</a>
{% endif %}
{% if category %}
<a class="big-link" href="{% url "category" category.id %}">{{ category.icon|remix }}{{ category }}</a>
{% endif %}
</p>
{% endif %}
{% if history %}
<section>
<h3>{% translate "History" %}</h3>
{% include "history/plot.html" %}
</section>
{% endif %}
{% if not category %}
<section>
<h3>{% translate "Categories" %}</h3>
{% category_plot transactions month=month year=year %}
</section>
{% endif %}
<section>
<h3>{% translate "Transactions" %}</h3>
{% if month %}
{% url_get "transactions" start_date=month end_date=month|end_of_month category=category.id account=account.id as t_url %}
{% elif year %}
{% url_get "transactions" start_date=year end_date=year|end_of_year category=category.id account=account.id as t_url %}
{% endif %}
<p>
<a class="big-link" href="{{ t_url }}">{{ "list-check"|remixnl }}{% translate "View all transactions" %}</a>
</p>
{% transaction_table transactions n_max=8 transactions_url=t_url %}
</section>
{% history_pagination %}
{% endblock body %}

View file

@ -1,8 +0,0 @@
{% extends "history/transaction_archive.html" %}
{% load i18n static main_extras transaction_extras category %}
{% block title %}
{{ month|date:"F Y"|capfirst }} {{ block.super }}
{% endblock title %}
{% block h2 %}
{{ month|date:"F Y"|capfirst }}
{% endblock h2 %}

View file

@ -1,8 +0,0 @@
{% extends "history/transaction_archive.html" %}
{% load i18n static main_extras transaction_extras category %}
{% block title %}
{{ year|date:"Y" }} {{ block.super }}
{% endblock title %}
{% block h2 %}
{{ year|date:"Y" }}
{% endblock h2 %}

View file

@ -1,119 +0,0 @@
import datetime
import math
from urllib import parse
from django import template
from django.urls import reverse
from django.utils.safestring import mark_safe
from history.utils import history
from main.templatetags.main_extras import pmrvalue, remix
register = template.Library()
@register.inclusion_tag("history/plot.html", takes_context=True)
def history_plot(context, transactions, **kwargs):
kwargs.setdefault("account", context.get("account"))
kwargs.setdefault("category", context.get("category"))
if kwargs.get("category") or kwargs.get("account"):
kwargs["history"] = history(transactions.all())
else:
kwargs["history"] = history(transactions.exclude(category__budget=False))
return kwargs
@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%}"
@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 is None:
return ""
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 s_max:
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>"""
)
else:
_res += "<div></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))
@register.filter
def sum_year(y_data):
return sum(y["sum"] or 0 for y in y_data)
@register.inclusion_tag("history/pagination.html", takes_context=True)
def history_pagination(context):
return context
@register.simple_tag(takes_context=True)
def history_url(context, month=None, year=None, clear=False, **kwargs):
if not clear:
kwargs.setdefault("account", getattr(context.get("account"), "id", None))
kwargs.setdefault("category", getattr(context.get("category"), "id", None))
if month:
if isinstance(month, datetime.date):
year = month.year
month = month.month
url = reverse("history:month", kwargs={"year": year, "month": month})
elif year:
if isinstance(year, datetime.date):
year = year.year
url = reverse("history:year", kwargs={"year": year})
kwargs = {k: v for k, v in kwargs.items() if v}
if kwargs:
return f"{url}?{parse.urlencode(kwargs)}"
return url

View file

@ -1,13 +0,0 @@
from django.urls import path
from . import views
app_name = "history"
urlpatterns = [
path(
"month/<int:year>/<int:month>",
views.TransactionMonthView.as_view(),
name="month",
),
path("year/<int:year>", views.TransactionYearView.as_view(), name="year"),
]

View file

@ -1,54 +0,0 @@
import datetime
from django.db.models import Q, Sum
from django.db.models.functions import Abs, Greatest, TruncMonth
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"]
_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": None}
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": 125
* _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")[
len(_history) // 10
]["abs_sum"]
/ 100,
},
}

View file

@ -1,54 +0,0 @@
from django.shortcuts import get_object_or_404
from django.views.generic.dates import MonthArchiveView, YearArchiveView
from history.utils import history
from main.views import UserMixin
from transaction.models import Transaction
class ACFilterMixin:
def get_queryset(self):
queryset = super().get_queryset()
if account := self.request.GET.get("account"):
queryset = queryset.filter(statement__account=account)
if category := self.request.GET.get("category"):
queryset = queryset.filter(category=category)
return queryset
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
if account := self.request.GET.get("account"):
context_data["account"] = get_object_or_404(
self.request.user.account_set, pk=account
)
if category := self.request.GET.get("category"):
context_data["category"] = get_object_or_404(
self.request.user.category_set, pk=category
)
return context_data
class TransactionMonthView(UserMixin, ACFilterMixin, MonthArchiveView):
model = Transaction
date_field = "date"
context_object_name = "transactions"
month_format = "%m"
template_name = "history/transaction_month.html"
class TransactionYearView(UserMixin, ACFilterMixin, YearArchiveView):
model = Transaction
date_field = "date"
context_object_name = "transactions"
make_object_list = True
template_name = "history/transaction_year.html"
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)
context_data["history"] = history(h_data)
return context_data

Binary file not shown.

View file

@ -0,0 +1,21 @@
# NUMMI.
# Copyright (C) 2022
# This file is distributed under the same license as the nummi package.
# edpibu <git@edgarpierre.fr>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-21 17:26+0100\n"
"PO-Revision-Date: 2022-12-21 17:30+0100\n"
"Last-Translator: edpibu <git@edgarpierre.fr>\n"
"Language-Team: edpibu <git@edgarpierre.fr>\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"
#: .\plot\views.py:28
msgid "Snapshots"
msgstr "Relevés"

8
nummi/main/admin.py Normal file
View file

@ -0,0 +1,8 @@
from django.contrib import admin
from .models import Category, Invoice, Snapshot, Transaction
admin.site.register(Transaction)
admin.site.register(Invoice)
admin.site.register(Category)
admin.site.register(Snapshot)

View file

@ -2,4 +2,5 @@ from django.apps import AppConfig
class MainConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "main"

View file

@ -1,51 +1,97 @@
from django import forms
from django.forms.widgets import TextInput
from django.db import models, transaction
from django.utils.translation import gettext_lazy as _
from .utils import get_icons
class NummiFileInput(forms.ClearableFileInput):
template_name = "main/form/fileinput.html"
from .models import Account, Category, Invoice, Snapshot, Transaction
class NummiForm(forms.ModelForm):
template_name = "main/form/form_base.html"
meta_fieldsets = []
template_name = "main/form/base.html"
def __init__(self, *args, **kwargs):
kwargs.pop("user", None)
def __init__(self, *args, user, **kwargs):
super().__init__(*args, **kwargs)
@property
def fieldsets(self):
if self.meta_fieldsets:
for group in self.meta_fieldsets:
yield ((self[f] for f in fieldset) for fieldset in group)
else:
yield ([f] for f in self)
class AccountForm(NummiForm):
class Meta:
model = Account
fields = [
"name",
"icon",
"default",
]
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 CategoryForm(NummiForm):
class Meta:
model = Category
fields = [
"name",
"icon",
]
class IconInput(DatalistInput):
template_name = "main/forms/widgets/icon.html"
icon_list = get_icons()
class TransactionForm(NummiForm):
class Meta:
model = Transaction
fields = [
"snapshot",
"name",
"value",
"date",
"real_date",
"category",
"trader",
"payment",
"description",
]
def get_context(self, *args):
context = super().get_context(*args)
context["widget"]["options"] = self.icon_list
return context
def __init__(self, *args, **kwargs):
_user = kwargs.get("user")
super().__init__(*args, **kwargs)
self.fields["category"].queryset = Category.objects.filter(user=_user)
self.fields["snapshot"].queryset = Snapshot.objects.filter(user=_user)
class InvoiceForm(NummiForm):
prefix = "invoice"
class Meta:
model = Invoice
fields = [
"name",
"file",
]
class SnapshotForm(NummiForm):
class Meta:
model = Snapshot
fields = ["account", "start_date", "date", "start_value", "value", "file"]
def __init__(self, *args, **kwargs):
_user = kwargs.get("user")
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,
)
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)

Binary file not shown.

View file

@ -0,0 +1,231 @@
# NUMMI.
# Copyright (C) 2022
# This file is distributed under the same license as the nummi package.
# edpibu <git@edgarpierre.fr>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-01 10:02+0100\n"
"PO-Revision-Date: 2022-12-21 17:30+0100\n"
"Last-Translator: edpibu <git@edgarpierre.fr>\n"
"Language-Team: edpibu <git@edgarpierre.fr>\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"
#: .\main\forms.py:81
msgid "Add transactions"
msgstr "Ajouter des transactions"
#: .\main\models.py:17
msgid "User"
msgstr "Utilisateur"
#: .\main\models.py:45 .\main\models.py:79 .\main\models.py:87
#: .\main\models.py:237 .\main\templates\main\base.html:39
#: .\main\templates\main\table\snapshot.html:7
#: .\main\templates\main\table\transaction.html:11
msgid "Account"
msgstr "Compte"
#: .\main\models.py:45 .\main\models.py:97 .\main\models.py:208
#: .\main\models.py:278 .\main\templates\main\table\transaction.html:7
#: .\main\templates\main\transaction_form.html:27
msgid "Name"
msgstr "Nom"
#: .\main\models.py:49 .\main\models.py:102
msgid "Icon"
msgstr "Icône"
#: .\main\models.py:51
msgid "Default"
msgstr "Défaut"
#: .\main\models.py:80 .\main\templates\main\index.html:16
msgid "Accounts"
msgstr "Comptes"
#: .\main\models.py:97 .\main\models.py:121 .\main\models.py:227
#: .\main\templates\main\base.html:49
#: .\main\templates\main\table\transaction.html:10
msgid "Category"
msgstr "Catégorie"
#: .\main\models.py:104
msgid "Budget"
msgstr "Budget"
#: .\main\models.py:122 .\main\templates\main\index.html:30
#: .\main\templates\main\snapshot_form.html:34
msgid "Categories"
msgstr "Catégories"
#: .\main\models.py:127
msgid "End date"
msgstr "Date de fin"
#: .\main\models.py:129
msgid "Start date"
msgstr "Date de début"
#: .\main\models.py:132
msgid "End value"
msgstr "Valeur de fin"
#: .\main\models.py:135
msgid "Start value"
msgstr "Valeur de début"
#: .\main\models.py:141 .\main\templates\main\table\snapshot.html:9
msgid "Difference"
msgstr "Différence"
#: .\main\models.py:148
msgid "Transaction difference"
msgstr "Différence des transactions"
#: .\main\models.py:154 .\main\models.py:283
msgid "File"
msgstr "Fichier"
#: .\main\models.py:161
#, python-format
msgid "%(date)s statement"
msgstr "Relevé du %(date)s"
#: .\main\models.py:201 .\main\models.py:232
msgid "Statement"
msgstr "Relevé"
#: .\main\models.py:202 .\main\templates\main\account_form.html:23
#: .\main\templates\main\list\snapshot.html:15
msgid "Statements"
msgstr "Relevés"
#: .\main\models.py:208 .\main\models.py:271 .\main\templates\main\base.html:44
msgid "Transaction"
msgstr "Transaction"
#: .\main\models.py:210 .\main\templates\main\table\transaction.html:12
msgid "Description"
msgstr "Description"
#: .\main\models.py:212 .\main\templates\main\table\snapshot.html:8
#: .\main\templates\main\table\transaction.html:8
msgid "Value"
msgstr "Valeur"
#: .\main\models.py:214 .\main\templates\main\table\snapshot.html:6
#: .\main\templates\main\table\transaction.html:6
msgid "Date"
msgstr "Date"
#: .\main\models.py:215
msgid "Real date"
msgstr "Date réelle"
#: .\main\models.py:217 .\main\templates\main\table\transaction.html:9
msgid "Trader"
msgstr "Commerçant"
#: .\main\models.py:220
msgid "Payment"
msgstr "Paiement"
#: .\main\models.py:272 .\main\templates\main\account_form.html:27
#: .\main\templates\main\category_form.html:25
#: .\main\templates\main\index.html:26
#: .\main\templates\main\list\transaction.html:15
#: .\main\templates\main\snapshot_form.html:75
#: .\main\templates\main\table\snapshot.html:10
msgid "Transactions"
msgstr "Transactions"
#: .\main\models.py:278 .\main\models.py:317
msgid "Invoice"
msgstr "Facture"
#: .\main\models.py:318 .\main\templates\main\transaction_form.html:23
msgid "Invoices"
msgstr "Factures"
#: .\main\templates\main\base.html:54
msgid "Snapshot"
msgstr "Relevé"
#: .\main\templates\main\base.html:60 .\main\templates\main\base.html:62
msgid "Search"
msgstr "Rechercher"
#: .\main\templates\main\base.html:64
msgid "Log out"
msgstr "Se déconnecter"
#: .\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:23
msgid "Cancel"
msgstr "Annuler"
#: .\main\templates\main\confirm_delete.html:24
msgid "Confirm"
msgstr "Confirmer"
#: .\main\templates\main\form\base.html:17
#: .\main\templates\main\tag\form_buttons.html:4
#: .\main\templates\main\transaction_form.html:34
msgid "Delete"
msgstr "Supprimer"
#: .\main\templates\main\form\base.html:20
#: .\main\templates\main\tag\form_buttons.html:8
msgid "Save"
msgstr "Enregistrer"
#: .\main\templates\main\index.html:40
msgid "Snapshots"
msgstr "Relevés"
#: .\main\templates\main\list\snapshot.html:35
msgid "No snapshots to show"
msgstr "Aucun relevé à afficher"
#: .\main\templates\main\list\transaction.html:35
msgid "No transactions to show"
msgstr "Aucune transaction à afficher"
#: .\main\templates\main\login.html:11 .\main\templates\main\login.html:27
msgid "Log In"
msgstr "Se connecter"
#: .\main\templates\main\table\snapshot.html:11
msgid "Valid"
msgstr "Valide"
#: .\main\templates\main\table\snapshot.html:15
msgid "New statement"
msgstr "Nouveau relevé"
#: .\main\templates\main\table\snapshot.html:50
msgid "View all statements"
msgstr "Voir tous les relevés"
#: .\main\templates\main\table\transaction.html:16
msgid "New transaction"
msgstr "Ajouter une transaction"
#: .\main\templates\main\table\transaction.html:55
msgid "View all transactions"
msgstr "Voir toutes les transactions"
#: .\main\templates\main\transaction_form.html:39
msgid "New invoice"
msgstr "Nouvelle facture"

View file

@ -1,134 +0,0 @@
# 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 <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \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 <traduction@edgarpierre.fr>\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:28
msgid "Skip to main content"
msgstr "Aller au contenu principal"
#: .\main\templates\main\base.html:35
msgid "Home"
msgstr "Accueil"
#: .\main\templates\main\base.html:42 .\main\templates\main\index.html:17
msgid "Statements"
msgstr "Relevés"
#: .\main\templates\main\base.html:49
msgid "Transactions"
msgstr "Transactions"
#: .\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:62
#, python-format
msgid "Logged in as <strong>%(user)s</strong>"
msgstr "Connecté en tant que <strong>%(user)s</strong>"
#: .\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 <strong>%(object)s</strong> ?"
msgstr "Êtes-vous sûr de vouloir supprimer <strong>%(object)s</strong> ?"
#: .\main\templates\main\confirm_delete.html:20
msgid "Cancel"
msgstr "Annuler"
#: .\main\templates\main\confirm_delete.html:21
msgid "Confirm"
msgstr "Confirmer"
#: .\main\templates\main\form\fileinput.html:6
msgid "File"
msgstr "Fichier"
#: .\main\templates\main\form\form_base.html:46
msgid "Create"
msgstr "Créer"
#: .\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:23
msgid "Categories"
msgstr "Catégories"
#: .\main\templates\main\index.html:29
msgid "Create category"
msgstr "Créer une catégorie"
#: .\main\templates\main\index.html:34
msgid "History"
msgstr "Historique"
#: .\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é"
#~ msgid "Create transaction"
#~ msgstr "Créer une transaction"
#~ msgid "No category"
#~ msgstr "Aucune catégorie"

View file

@ -0,0 +1,154 @@
# Generated by Django 4.0.4 on 2022-05-22 07:45
import datetime
import re
import uuid
import django.core.validators
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Category",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"name",
models.CharField(
default="New Category",
max_length=64,
validators=[
django.core.validators.RegexValidator(
re.compile("^[-\\w]+\\Z"),
"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens.",
"invalid",
)
],
),
),
("icon", models.CharField(default="folder", max_length=64)),
],
options={
"ordering": ["name"],
},
),
migrations.CreateModel(
name="Transaction",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.CharField(default="New Transaction", max_length=256)),
("description", models.TextField(blank=True, null=True)),
(
"value",
models.DecimalField(decimal_places=2, default=0, max_digits=12),
),
("date", models.DateField(default=datetime.date.today)),
("trader", models.CharField(blank=True, max_length=128, null=True)),
(
"category",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="main.category",
),
),
],
options={
"ordering": ["-date"],
},
),
migrations.CreateModel(
name="Snapshot",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("date", models.DateField(default=datetime.date.today, unique=True)),
(
"value",
models.DecimalField(decimal_places=2, default=0, max_digits=12),
),
(
"diff",
models.DecimalField(
decimal_places=2, default=0, editable=False, max_digits=12
),
),
(
"previous",
models.OneToOneField(
blank=True,
editable=False,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="main.snapshot",
),
),
],
options={
"ordering": ["-date"],
},
),
migrations.CreateModel(
name="Invoice",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.CharField(default="New Invoice", max_length=256)),
(
"file",
models.FileField(
upload_to="invoices/",
validators=[
django.core.validators.FileExtensionValidator(["pdf"])
],
),
),
(
"transaction",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="main.transaction",
),
),
],
),
]

View file

@ -1,349 +0,0 @@
# Generated by Django 4.1.4 on 2023-04-20 15:32
import datetime
import uuid
import django.contrib.postgres.operations
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),
]
operations = [
django.contrib.postgres.operations.TrigramExtension(),
django.contrib.postgres.operations.UnaccentExtension(),
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"
),
),
("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",
),
),
(
"icon",
models.SlugField(
default="folder", max_length=24, verbose_name="Icon"
),
),
],
options={
"ordering": ["name"],
"verbose_name": "Category",
"verbose_name_plural": "Categories",
},
),
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"
),
),
(
"user",
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
("default", models.BooleanField(default=False, verbose_name="Default")),
(
"icon",
models.SlugField(
default="bank", max_length=24, verbose_name="Icon"
),
),
],
options={
"verbose_name": "Account",
"verbose_name_plural": "Accounts",
"ordering": ["-default", "name"],
},
),
migrations.CreateModel(
name="Snapshot",
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"
),
),
(
"value",
models.DecimalField(
decimal_places=2,
default=0,
max_digits=12,
verbose_name="End value",
),
),
(
"file",
models.FileField(
blank=True,
default="",
max_length=256,
upload_to=media.utils.get_path,
validators=[
django.core.validators.FileExtensionValidator(["pdf"])
],
verbose_name="File",
),
),
(
"user",
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
(
"account",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="main.account",
verbose_name="Account",
),
),
(
"start_date",
models.DateField(
default=datetime.date.today, verbose_name="Start date"
),
),
(
"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",
),
),
],
options={
"ordering": ["-date", "account"],
"verbose_name": "Statement",
"verbose_name_plural": "Statements",
},
),
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"),
),
(
"trader",
models.CharField(
blank=True, max_length=128, null=True, verbose_name="Trader"
),
),
(
"category",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="main.category",
verbose_name="Category",
),
),
(
"real_date",
models.DateField(blank=True, null=True, verbose_name="Real date"),
),
(
"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="main.account",
verbose_name="Account",
),
),
(
"snapshot",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="main.snapshot",
verbose_name="Statement",
),
),
(
"user",
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
],
options={
"ordering": ["-date", "snapshot"],
"verbose_name": "Transaction",
"verbose_name_plural": "Transactions",
},
),
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="main.transaction",
),
),
(
"user",
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
],
options={
"ordering": ["transaction", "name"],
"verbose_name": "Invoice",
"verbose_name_plural": "Invoices",
},
),
]

View file

@ -0,0 +1,25 @@
# Generated by Django 4.0.4 on 2022-05-22 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="snapshot",
name="date",
field=models.DateField(unique=True),
),
migrations.AlterField(
model_name="snapshot",
name="diff",
field=models.DecimalField(
blank=True, decimal_places=2, editable=False, max_digits=12, null=True
),
),
]

View file

@ -1,33 +0,0 @@
# 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.RenameField("Transaction", "snapshot", "statement"),
migrations.SeparateDatabaseAndState(
database_operations=database_operations,
state_operations=state_operations,
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-05-22 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0002_alter_snapshot_date_alter_snapshot_diff"),
]
operations = [
migrations.AddField(
model_name="transaction",
name="real_date",
field=models.DateField(blank=True, null=True),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-05-22 11:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0003_transaction_real_date"),
]
operations = [
migrations.AddField(
model_name="transaction",
name="payment",
field=models.CharField(blank=True, max_length=128, null=True),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-05-22 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0004_transaction_payment"),
]
operations = [
migrations.AlterField(
model_name="category",
name="name",
field=models.CharField(default="New Category", max_length=64),
),
]

View file

@ -0,0 +1,146 @@
# Generated by Django 4.1.4 on 2022-12-20 14:26
import datetime
import django.core.validators
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0005_alter_category_name"),
]
operations = [
migrations.AlterModelOptions(
name="category",
options={
"ordering": ["name"],
"verbose_name": "Catégorie",
"verbose_name_plural": "Catégories",
},
),
migrations.AlterModelOptions(
name="invoice",
options={"verbose_name": "Facture", "verbose_name_plural": "Factures"},
),
migrations.AlterModelOptions(
name="snapshot",
options={
"ordering": ["-date"],
"verbose_name": "Relevé",
"verbose_name_plural": "Relevés",
},
),
migrations.AlterModelOptions(
name="transaction",
options={
"ordering": ["-date"],
"verbose_name": "Transaction",
"verbose_name_plural": "Transactions",
},
),
migrations.AddField(
model_name="category",
name="budget",
field=models.BooleanField(default=True, verbose_name="Budget"),
),
migrations.AlterField(
model_name="category",
name="icon",
field=models.CharField(
default="folder", max_length=64, verbose_name="Icône"
),
),
migrations.AlterField(
model_name="category",
name="name",
field=models.CharField(
default="Catégorie", max_length=64, verbose_name="Nom"
),
),
migrations.AlterField(
model_name="invoice",
name="file",
field=models.FileField(
upload_to="invoices/",
validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="Fichier",
),
),
migrations.AlterField(
model_name="invoice",
name="name",
field=models.CharField(
default="Facture", max_length=256, verbose_name="Nom"
),
),
migrations.AlterField(
model_name="snapshot",
name="date",
field=models.DateField(unique=True, verbose_name="Date"),
),
migrations.AlterField(
model_name="snapshot",
name="value",
field=models.DecimalField(
decimal_places=2, default=0, max_digits=12, verbose_name="Valeur"
),
),
migrations.AlterField(
model_name="transaction",
name="category",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="main.category",
verbose_name="Catégorie",
),
),
migrations.AlterField(
model_name="transaction",
name="date",
field=models.DateField(default=datetime.date.today, verbose_name="Date"),
),
migrations.AlterField(
model_name="transaction",
name="description",
field=models.TextField(blank=True, null=True, verbose_name="Description"),
),
migrations.AlterField(
model_name="transaction",
name="name",
field=models.CharField(
default="Transaction", max_length=256, verbose_name="Nom"
),
),
migrations.AlterField(
model_name="transaction",
name="payment",
field=models.CharField(
blank=True, max_length=128, null=True, verbose_name="Paiement"
),
),
migrations.AlterField(
model_name="transaction",
name="real_date",
field=models.DateField(blank=True, null=True, verbose_name="Date réelle"),
),
migrations.AlterField(
model_name="transaction",
name="trader",
field=models.CharField(
blank=True, max_length=128, null=True, verbose_name="Commerçant"
),
),
migrations.AlterField(
model_name="transaction",
name="value",
field=models.DecimalField(
decimal_places=2, default=0, max_digits=12, verbose_name="Valeur"
),
),
]

View file

@ -0,0 +1,45 @@
# Generated by Django 4.1.4 on 2022-12-21 08:53
import datetime
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0006_alter_category_options_alter_invoice_options_and_more"),
]
operations = [
migrations.AddField(
model_name="snapshot",
name="file",
field=models.FileField(
blank=True,
max_length=128,
null=True,
upload_to="snapshots/",
validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="Fichier",
),
),
migrations.AlterField(
model_name="invoice",
name="file",
field=models.FileField(
max_length=128,
upload_to="invoices/",
validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="Fichier",
),
),
migrations.AlterField(
model_name="snapshot",
name="date",
field=models.DateField(
default=datetime.date.today, unique=True, verbose_name="Date"
),
),
]

View file

@ -0,0 +1,16 @@
# Generated by Django 4.1.4 on 2022-12-21 15:57
from django.contrib.postgres.operations import TrigramExtension, UnaccentExtension
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("main", "0007_snapshot_file_alter_invoice_file_alter_snapshot_date"),
]
operations = [
TrigramExtension(),
UnaccentExtension(),
]

View file

@ -0,0 +1,48 @@
# Generated by Django 4.1.4 on 2022-12-28 11:20
import django.core.validators
import django.db.models.deletion
from django.db import migrations, models
import main.models
class Migration(migrations.Migration):
dependencies = [
("main", "0008_auto_20221221_1657"),
]
operations = [
migrations.AlterField(
model_name="invoice",
name="file",
field=models.FileField(
max_length=128,
upload_to=main.models.get_path,
validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="Fichier",
),
),
migrations.AlterField(
model_name="invoice",
name="transaction",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to="main.transaction",
),
),
migrations.AlterField(
model_name="snapshot",
name="file",
field=models.FileField(
blank=True,
default="",
max_length=256,
upload_to=main.models.get_path,
validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="Fichier",
),
),
]

View file

@ -0,0 +1,60 @@
# Generated by Django 4.1.4 on 2022-12-29 18:03
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("main", "0009_alter_invoice_file_alter_invoice_transaction_and_more"),
]
operations = [
migrations.AddField(
model_name="category",
name="user",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Utilisateur",
),
preserve_default=False,
),
migrations.AddField(
model_name="invoice",
name="user",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Utilisateur",
),
preserve_default=False,
),
migrations.AddField(
model_name="snapshot",
name="user",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Utilisateur",
),
preserve_default=False,
),
migrations.AddField(
model_name="transaction",
name="user",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Utilisateur",
),
preserve_default=False,
),
]

View file

@ -0,0 +1,56 @@
# Generated by Django 4.1.4 on 2022-12-29 18:14
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("main", "0010_category_user_invoice_user_snapshot_user_and_more"),
]
operations = [
migrations.AlterField(
model_name="category",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Utilisateur",
),
),
migrations.AlterField(
model_name="invoice",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Utilisateur",
),
),
migrations.AlterField(
model_name="snapshot",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Utilisateur",
),
),
migrations.AlterField(
model_name="transaction",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Utilisateur",
),
),
]

View file

@ -0,0 +1,20 @@
# Generated by Django 4.1.4 on 2022-12-29 18:22
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0011_alter_category_user_alter_invoice_user_and_more"),
]
operations = [
migrations.AlterField(
model_name="snapshot",
name="date",
field=models.DateField(default=datetime.date.today, verbose_name="Date"),
),
]

View file

@ -1,4 +1,4 @@
# Generated by Django 4.1.4 on 2023-04-22 09:01
# Generated by Django 4.1.4 on 2022-12-29 18:32
import uuid
@ -8,14 +8,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("main", "0002_segmentation"),
("main", "0012_alter_snapshot_date"),
]
state_operations = [
operations = [
migrations.CreateModel(
name="Account",
fields=[
@ -31,35 +30,51 @@ class Migration(migrations.Migration):
(
"name",
models.CharField(
default="Account", max_length=64, verbose_name="Name"
default="Account", max_length=64, verbose_name="Nom"
),
),
(
"icon",
models.SlugField(
default="bank", max_length=24, verbose_name="Icon"
models.CharField(
default="folder", max_length=64, verbose_name="Icône"
),
),
("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",
verbose_name="Utilisateur",
),
),
],
options={
"verbose_name": "Account",
"verbose_name_plural": "Accounts",
"ordering": ["-default", "name"],
"db_table": "account_account",
"ordering": ["name"],
},
),
]
operations = [
migrations.SeparateDatabaseAndState(state_operations=state_operations)
migrations.AddField(
model_name="snapshot",
name="account",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="main.account",
verbose_name="Account",
),
),
migrations.AddField(
model_name="transaction",
name="account",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="main.account",
verbose_name="Account",
),
),
]

View file

@ -0,0 +1,20 @@
# Generated by Django 4.1.4 on 2022-12-29 20:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0013_account_snapshot_account_transaction_account"),
]
operations = [
migrations.AlterField(
model_name="account",
name="icon",
field=models.CharField(
default="building-columns", max_length=64, verbose_name="Icône"
),
),
]

View file

@ -0,0 +1,71 @@
# Generated by Django 4.1.4 on 2022-12-29 20:26
import datetime
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0014_alter_account_icon"),
]
operations = [
migrations.AlterModelOptions(
name="snapshot",
options={
"ordering": ["-date"],
"verbose_name": "Statement",
"verbose_name_plural": "Statements",
},
),
migrations.RemoveField(
model_name="snapshot",
name="diff",
),
migrations.RemoveField(
model_name="snapshot",
name="previous",
),
migrations.AddField(
model_name="snapshot",
name="start_date",
field=models.DateField(
default=datetime.date.today, verbose_name="Start date"
),
),
migrations.AddField(
model_name="snapshot",
name="start_value",
field=models.DecimalField(
decimal_places=2, default=0, max_digits=12, verbose_name="Start value"
),
),
migrations.AddField(
model_name="transaction",
name="snapshot",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="main.snapshot",
verbose_name="Statement",
),
),
migrations.AlterField(
model_name="snapshot",
name="date",
field=models.DateField(
default=datetime.date.today, verbose_name="End date"
),
),
migrations.AlterField(
model_name="snapshot",
name="value",
field=models.DecimalField(
decimal_places=2, default=0, max_digits=12, verbose_name="End value"
),
),
]

View file

@ -0,0 +1,46 @@
# Generated by Django 4.1.4 on 2022-12-29 20:32
import django.db.models.deletion
from django.db import migrations, models
def set_snapshot(apps, schema_editor):
Transaction = apps.get_model("main", "Transaction")
Snapshot = apps.get_model("main", "Snapshot")
for transaction in Transaction.objects.filter(snapshot__isnull=True):
transaction.snapshot = (
Snapshot.objects.filter(user=transaction.user).order_by("-date").first()
)
transaction.save()
class Migration(migrations.Migration):
dependencies = [
("main", "0015_alter_snapshot_options_remove_snapshot_diff_and_more"),
]
operations = [
migrations.AlterField(
model_name="transaction",
name="account",
field=models.ForeignKey(
blank=True,
editable=False,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="main.account",
verbose_name="Account",
),
),
migrations.RunPython(set_snapshot),
migrations.AlterField(
model_name="transaction",
name="snapshot",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="main.snapshot",
verbose_name="Statement",
),
),
]

View file

@ -0,0 +1,44 @@
# Generated by Django 4.1.4 on 2022-12-29 20:34
import django.db.models.deletion
from django.db import migrations, models
def set_account(apps, schema_editor):
Transaction = apps.get_model("main", "Transaction")
Snapshot = apps.get_model("main", "Snapshot")
Account = apps.get_model("main", "Account")
for snapshot in Snapshot.objects.filter(account__isnull=True):
account = Account.objects.filter(user=snapshot.user).first()
if account is None:
account = Account(user=snapshot.user)
account.save()
snapshot.account = account
snapshot.save()
for transaction in Transaction.objects.filter(
account__isnull=True, snapshot__isnull=False
):
transaction.account = transaction.snapshot.account
transaction.save()
class Migration(migrations.Migration):
atomic = False
dependencies = [
("main", "0016_alter_transaction_account_alter_transaction_snapshot"),
]
operations = [
migrations.RunPython(set_account, atomic=True),
migrations.AlterField(
model_name="transaction",
name="account",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to="main.account",
verbose_name="Account",
),
),
]

View file

@ -0,0 +1,34 @@
# Generated by Django 4.1.4 on 2022-12-29 20:56
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0017_alter_transaction_account"),
]
operations = [
migrations.AddField(
model_name="snapshot",
name="diff",
field=models.DecimalField(
decimal_places=2,
default=0,
editable=False,
max_digits=12,
verbose_name="Différence",
),
),
migrations.AlterField(
model_name="snapshot",
name="account",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="main.account",
verbose_name="Account",
),
),
]

View file

@ -0,0 +1,24 @@
# Generated by Django 4.1.4 on 2022-12-29 20:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0018_snapshot_diff_alter_snapshot_account"),
]
operations = [
migrations.AddField(
model_name="snapshot",
name="sum",
field=models.DecimalField(
decimal_places=2,
default=0,
editable=False,
max_digits=12,
verbose_name="Transaction difference",
),
),
]

View file

@ -0,0 +1,141 @@
# Generated by Django 4.1.4 on 2022-12-29 21:15
import datetime
import re
import django.core.validators
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0019_snapshot_sum"),
]
operations = [
migrations.AlterModelOptions(
name="account",
options={
"ordering": ["name"],
"verbose_name": "Compte",
"verbose_name_plural": "Comptes",
},
),
migrations.AlterModelOptions(
name="snapshot",
options={
"ordering": ["-date"],
"verbose_name": "Relevé",
"verbose_name_plural": "Relevés",
},
),
migrations.AlterField(
model_name="account",
name="icon",
field=models.CharField(
default="building-columns",
max_length=64,
validators=[
django.core.validators.RegexValidator(
re.compile("^[-a-zA-Z0-9_]+\\Z"),
"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.",
"invalid",
)
],
verbose_name="Icône",
),
),
migrations.AlterField(
model_name="account",
name="name",
field=models.CharField(default="Compte", max_length=64, verbose_name="Nom"),
),
migrations.AlterField(
model_name="category",
name="icon",
field=models.CharField(
default="folder",
max_length=64,
validators=[
django.core.validators.RegexValidator(
re.compile("^[-a-zA-Z0-9_]+\\Z"),
"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.",
"invalid",
)
],
verbose_name="Icône",
),
),
migrations.AlterField(
model_name="snapshot",
name="account",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="main.account",
verbose_name="Compte",
),
),
migrations.AlterField(
model_name="snapshot",
name="date",
field=models.DateField(
default=datetime.date.today, verbose_name="Date de fin"
),
),
migrations.AlterField(
model_name="snapshot",
name="start_date",
field=models.DateField(
default=datetime.date.today, verbose_name="Date de début"
),
),
migrations.AlterField(
model_name="snapshot",
name="start_value",
field=models.DecimalField(
decimal_places=2,
default=0,
max_digits=12,
verbose_name="Valeur de début",
),
),
migrations.AlterField(
model_name="snapshot",
name="sum",
field=models.DecimalField(
decimal_places=2,
default=0,
editable=False,
max_digits=12,
verbose_name="Différence des transactions",
),
),
migrations.AlterField(
model_name="snapshot",
name="value",
field=models.DecimalField(
decimal_places=2, default=0, max_digits=12, verbose_name="Valeur de fin"
),
),
migrations.AlterField(
model_name="transaction",
name="account",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to="main.account",
verbose_name="Compte",
),
),
migrations.AlterField(
model_name="transaction",
name="snapshot",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="main.snapshot",
verbose_name="Relevé",
),
),
]

View file

@ -0,0 +1,297 @@
# Generated by Django 4.1.4 on 2022-12-31 08:51
import datetime
import re
import django.core.validators
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import main.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("main", "0020_alter_account_options_alter_snapshot_options_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="account",
options={
"ordering": ["name"],
"verbose_name": "Account",
"verbose_name_plural": "Accounts",
},
),
migrations.AlterModelOptions(
name="category",
options={
"ordering": ["name"],
"verbose_name": "Category",
"verbose_name_plural": "Categories",
},
),
migrations.AlterModelOptions(
name="invoice",
options={"verbose_name": "Invoice", "verbose_name_plural": "Invoices"},
),
migrations.AlterModelOptions(
name="snapshot",
options={
"ordering": ["-date"],
"verbose_name": "Statement",
"verbose_name_plural": "Statements",
},
),
migrations.AlterField(
model_name="account",
name="icon",
field=models.CharField(
default="building-columns",
max_length=64,
validators=[
django.core.validators.RegexValidator(
re.compile("^[-a-zA-Z0-9_]+\\Z"),
"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.",
"invalid",
)
],
verbose_name="Icon",
),
),
migrations.AlterField(
model_name="account",
name="name",
field=models.CharField(
default="Account", max_length=64, verbose_name="Name"
),
),
migrations.AlterField(
model_name="account",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
migrations.AlterField(
model_name="category",
name="icon",
field=models.CharField(
default="folder",
max_length=64,
validators=[
django.core.validators.RegexValidator(
re.compile("^[-a-zA-Z0-9_]+\\Z"),
"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.",
"invalid",
)
],
verbose_name="Icon",
),
),
migrations.AlterField(
model_name="category",
name="name",
field=models.CharField(
default="Category", max_length=64, verbose_name="Name"
),
),
migrations.AlterField(
model_name="category",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
migrations.AlterField(
model_name="invoice",
name="file",
field=models.FileField(
max_length=128,
upload_to=main.models.get_path,
validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="File",
),
),
migrations.AlterField(
model_name="invoice",
name="name",
field=models.CharField(
default="Invoice", max_length=256, verbose_name="Name"
),
),
migrations.AlterField(
model_name="invoice",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
migrations.AlterField(
model_name="snapshot",
name="account",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="main.account",
verbose_name="Account",
),
),
migrations.AlterField(
model_name="snapshot",
name="date",
field=models.DateField(
default=datetime.date.today, verbose_name="End date"
),
),
migrations.AlterField(
model_name="snapshot",
name="diff",
field=models.DecimalField(
decimal_places=2,
default=0,
editable=False,
max_digits=12,
verbose_name="Difference",
),
),
migrations.AlterField(
model_name="snapshot",
name="file",
field=models.FileField(
blank=True,
default="",
max_length=256,
upload_to=main.models.get_path,
validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="File",
),
),
migrations.AlterField(
model_name="snapshot",
name="start_date",
field=models.DateField(
default=datetime.date.today, verbose_name="Start date"
),
),
migrations.AlterField(
model_name="snapshot",
name="start_value",
field=models.DecimalField(
decimal_places=2, default=0, max_digits=12, verbose_name="Start value"
),
),
migrations.AlterField(
model_name="snapshot",
name="sum",
field=models.DecimalField(
decimal_places=2,
default=0,
editable=False,
max_digits=12,
verbose_name="Transaction difference",
),
),
migrations.AlterField(
model_name="snapshot",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
migrations.AlterField(
model_name="snapshot",
name="value",
field=models.DecimalField(
decimal_places=2, default=0, max_digits=12, verbose_name="End value"
),
),
migrations.AlterField(
model_name="transaction",
name="account",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to="main.account",
verbose_name="Account",
),
),
migrations.AlterField(
model_name="transaction",
name="category",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="main.category",
verbose_name="Category",
),
),
migrations.AlterField(
model_name="transaction",
name="name",
field=models.CharField(
default="Transaction", max_length=256, verbose_name="Name"
),
),
migrations.AlterField(
model_name="transaction",
name="payment",
field=models.CharField(
blank=True, max_length=128, null=True, verbose_name="Payment"
),
),
migrations.AlterField(
model_name="transaction",
name="real_date",
field=models.DateField(blank=True, null=True, verbose_name="Real date"),
),
migrations.AlterField(
model_name="transaction",
name="snapshot",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="main.snapshot",
verbose_name="Statement",
),
),
migrations.AlterField(
model_name="transaction",
name="trader",
field=models.CharField(
blank=True, max_length=128, null=True, verbose_name="Trader"
),
),
migrations.AlterField(
model_name="transaction",
name="user",
field=models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
migrations.AlterField(
model_name="transaction",
name="value",
field=models.DecimalField(
decimal_places=2, default=0, max_digits=12, verbose_name="Value"
),
),
]

View file

@ -0,0 +1,27 @@
# Generated by Django 4.1.4 on 2022-12-31 08:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("main", "0021_alter_account_options_alter_category_options_and_more"),
]
operations = [
migrations.AlterField(
model_name="account",
name="icon",
field=models.SlugField(
default="building-columns", max_length=64, verbose_name="Icon"
),
),
migrations.AlterField(
model_name="category",
name="icon",
field=models.SlugField(
default="folder", max_length=64, verbose_name="Icon"
),
),
]

Some files were not shown because too many files have changed in this diff Show more