Compare commits
2 commits
667c6d3da1
...
7356d02ada
Author | SHA1 | Date | |
---|---|---|---|
7356d02ada | |||
f2df88d091 |
10 changed files with 260 additions and 72 deletions
|
@ -0,0 +1,80 @@
|
|||
# Generated by Django 4.1.4 on 2022-12-29 18:32
|
||||
|
||||
import uuid
|
||||
|
||||
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", "0012_alter_snapshot_date"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Account",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(
|
||||
default="Account", max_length=64, verbose_name="Nom"
|
||||
),
|
||||
),
|
||||
(
|
||||
"icon",
|
||||
models.CharField(
|
||||
default="folder", max_length=64, verbose_name="Icône"
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
editable=False,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Utilisateur",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Account",
|
||||
"verbose_name_plural": "Accounts",
|
||||
"ordering": ["name"],
|
||||
},
|
||||
),
|
||||
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",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -2,12 +2,12 @@ import pathlib
|
|||
import uuid
|
||||
from datetime import date
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.validators import FileExtensionValidator
|
||||
from django.db import models
|
||||
from django.forms import ModelForm
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class UserModel(models.Model):
|
||||
|
@ -19,7 +19,57 @@ class UserModel(models.Model):
|
|||
abstract = True
|
||||
|
||||
|
||||
class Category(UserModel):
|
||||
class CustomModel(UserModel):
|
||||
@property
|
||||
def adding(self):
|
||||
return self._state.adding
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Account(CustomModel):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name"))
|
||||
icon = models.CharField(max_length=64, default="folder", verbose_name=_("Icon"))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("account", kwargs={"pk": self.pk})
|
||||
|
||||
def get_delete_url(self):
|
||||
return reverse("del_account", kwargs={"pk": self.pk})
|
||||
|
||||
@property
|
||||
def transactions(self):
|
||||
return Transaction.objects.filter(account=self)
|
||||
|
||||
@property
|
||||
def snapshots(self):
|
||||
return Snapshot.objects.filter(account=self)
|
||||
|
||||
class Meta:
|
||||
ordering = ["name"]
|
||||
verbose_name = _("Account")
|
||||
verbose_name_plural = _("Accounts")
|
||||
|
||||
|
||||
class AccountModel(CustomModel):
|
||||
account = models.ForeignKey(
|
||||
Account,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Account"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Category(CustomModel):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
name = models.CharField(
|
||||
max_length=64, default=_("Category"), verbose_name=_("Name")
|
||||
|
@ -36,10 +86,6 @@ class Category(UserModel):
|
|||
def get_delete_url(self):
|
||||
return reverse("del_category", kwargs={"pk": self.pk})
|
||||
|
||||
@property
|
||||
def adding(self):
|
||||
return self._state.adding
|
||||
|
||||
@property
|
||||
def transactions(self):
|
||||
return Transaction.objects.filter(category=self)
|
||||
|
@ -50,15 +96,7 @@ class Category(UserModel):
|
|||
verbose_name_plural = _("Categories")
|
||||
|
||||
|
||||
class CategoryForm(ModelForm):
|
||||
template_name = "main/form/base.html"
|
||||
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = ["name", "icon", "budget"]
|
||||
|
||||
|
||||
class Transaction(UserModel):
|
||||
class Transaction(AccountModel):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
name = models.CharField(
|
||||
max_length=256, default=_("Transaction"), verbose_name=_("Name")
|
||||
|
@ -92,10 +130,6 @@ class Transaction(UserModel):
|
|||
def get_delete_url(self):
|
||||
return reverse("del_transaction", kwargs={"pk": self.pk})
|
||||
|
||||
@property
|
||||
def adding(self):
|
||||
return self._state.adding
|
||||
|
||||
@property
|
||||
def invoices(self):
|
||||
return Invoice.objects.filter(transaction=self)
|
||||
|
@ -110,28 +144,11 @@ class Transaction(UserModel):
|
|||
verbose_name_plural = _("Transactions")
|
||||
|
||||
|
||||
class TransactionForm(ModelForm):
|
||||
template_name = "main/form/base.html"
|
||||
|
||||
class Meta:
|
||||
model = Transaction
|
||||
fields = [
|
||||
"date",
|
||||
"name",
|
||||
"value",
|
||||
"trader",
|
||||
"category",
|
||||
"real_date",
|
||||
"payment",
|
||||
"description",
|
||||
]
|
||||
|
||||
|
||||
def invoice_path(instance, filename):
|
||||
return pathlib.Path("invoices", str(instance.id)).with_suffix(".pdf")
|
||||
|
||||
|
||||
class Invoice(UserModel):
|
||||
class Invoice(CustomModel):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
name = models.CharField(
|
||||
max_length=256, default=_("Invoice"), verbose_name=_("Name")
|
||||
|
@ -165,29 +182,16 @@ class Invoice(UserModel):
|
|||
"del_invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk}
|
||||
)
|
||||
|
||||
@property
|
||||
def adding(self):
|
||||
return self._state.adding
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Invoice")
|
||||
verbose_name_plural = _("Invoices")
|
||||
|
||||
|
||||
class InvoiceForm(ModelForm):
|
||||
template_name = "main/form/base.html"
|
||||
prefix = "invoice"
|
||||
|
||||
class Meta:
|
||||
model = Invoice
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
def snapshot_path(instance, filename):
|
||||
return pathlib.Path("snapshots", str(instance.id)).with_suffix(".pdf")
|
||||
|
||||
|
||||
class Snapshot(UserModel):
|
||||
class Snapshot(AccountModel):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
date = models.DateField(default=date.today, verbose_name=_("Date"))
|
||||
value = models.DecimalField(
|
||||
|
@ -284,10 +288,6 @@ class Snapshot(UserModel):
|
|||
def get_delete_url(self):
|
||||
return reverse("del_snapshot", kwargs={"pk": self.pk})
|
||||
|
||||
@property
|
||||
def adding(self):
|
||||
return self._state.adding
|
||||
|
||||
@property
|
||||
def sum(self):
|
||||
if self.previous is None:
|
||||
|
@ -327,9 +327,37 @@ class Snapshot(UserModel):
|
|||
verbose_name_plural = _("Snapshots")
|
||||
|
||||
|
||||
class SnapshotForm(ModelForm):
|
||||
class NummiForm(ModelForm):
|
||||
template_name = "main/form/base.html"
|
||||
|
||||
|
||||
class AccountForm(NummiForm):
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class CategoryForm(NummiForm):
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class TransactionForm(NummiForm):
|
||||
class Meta:
|
||||
model = Transaction
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class InvoiceForm(NummiForm):
|
||||
prefix = "invoice"
|
||||
|
||||
class Meta:
|
||||
model = Invoice
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class SnapshotForm(NummiForm):
|
||||
class Meta:
|
||||
model = Snapshot
|
||||
fields = ["date", "value", "file"]
|
||||
fields = "__all__"
|
||||
|
|
|
@ -12,10 +12,12 @@ h1 img {
|
|||
margin-right: var(--gap);
|
||||
}
|
||||
|
||||
#categories > a {
|
||||
#categories > a,
|
||||
#accounts > a {
|
||||
display: inline-block;
|
||||
padding: 1em;
|
||||
}
|
||||
#categories > a > i {
|
||||
#categories > a > i,
|
||||
#accounts > a > i {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
.table.col1-1-1 {grid-template-columns: min-content auto min-content}
|
||||
.table.col1-5 {grid-template-columns: min-content repeat(5, auto)}
|
||||
.table.col1-6 {grid-template-columns: min-content repeat(6, auto)}
|
||||
.table.col1-7 {grid-template-columns: min-content repeat(7, auto)}
|
||||
|
||||
.table > div {
|
||||
display: contents;
|
||||
|
|
22
nummi/main/templates/main/account_form.html
Normal file
22
nummi/main/templates/main/account_form.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
{% extends "main/base.html" %}
|
||||
{% load static %}
|
||||
{% load main_extras %}
|
||||
{% load i18n %}
|
||||
{% block link %}
|
||||
{{ block.super }}
|
||||
<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 %}
|
||||
<h1>
|
||||
<i class="fa fa-{{ form.instance.icon }}"></i> {{ form.instance }}
|
||||
</h1>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -33,6 +33,11 @@
|
|||
<img src="{% static 'main/svg/logo.svg' %}" alt="" />
|
||||
<h1>Nummi</h1>
|
||||
</a>
|
||||
<a href="{% url 'account' %}"
|
||||
class="{% if request.resolver_match.url_name == 'account' %}cur{% endif %}"
|
||||
accesskey="n">
|
||||
{% translate "Account" %}
|
||||
</a>
|
||||
<a href="{% url 'transaction' %}"
|
||||
class="{% if request.resolver_match.url_name == 'transaction' %}cur{% endif %}"
|
||||
accesskey="n">
|
||||
|
|
|
@ -12,6 +12,16 @@
|
|||
type="text/css"/>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
{% if accounts %}
|
||||
<h2>{% translate "Accounts" %}</h2>
|
||||
{% spaceless %}
|
||||
<div id="accounts">
|
||||
{% for acc in accounts %}
|
||||
<a href="{% url 'account' acc.id %}"><i class="fa fa-{{ acc.icon }}"></i>{{ acc }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endspaceless %}
|
||||
{% endif %}
|
||||
{% if transactions %}
|
||||
<h2>
|
||||
{% translate "Transactions" %}
|
||||
|
@ -33,10 +43,11 @@
|
|||
{% endif %}
|
||||
{% if snapshots %}
|
||||
<h2>{% translate "Snapshots" %}</h2>
|
||||
<div id="snapshots" class="table col1-5">
|
||||
<div id="snapshots" class="table col1-6">
|
||||
<div class="header">
|
||||
<strong class="attach center"><i class="fa fa-paperclip"></i></strong>
|
||||
<strong class="date center">{% translate "Date" %}</strong>
|
||||
<strong class="account center">{% translate "Account" %}</strong>
|
||||
<strong class="value center">{% translate "Value" %}</strong>
|
||||
<strong class="diff center">{% translate "Difference" %}</strong>
|
||||
<strong class="diff center">{% translate "Transactions" %}</strong>
|
||||
|
@ -54,6 +65,14 @@
|
|||
<span class="date num center">
|
||||
<a href="{% url 'snapshot' snap.id %}">{{ snap.date|date:"Y-m-d" }}</a>
|
||||
</span>
|
||||
<span class="account text center">
|
||||
{% if trans.account %}
|
||||
<i class="fa fa-{{ trans.account.icon }}"></i>
|
||||
<a href="{% url 'account' trans.account.id %}">{{ trans.account }}</a>
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="value num right">{{ snap.value|value }}</span>
|
||||
<span class="diff num right">{{ snap.diff|pmvalue }}</span>
|
||||
{% with sum=snap.sum %}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load main_extras %}
|
||||
{% load i18n %}
|
||||
<div id="transactions" class="table col1-6">
|
||||
<div id="transactions" class="table col1-7">
|
||||
<div class="header">
|
||||
<strong class="attach center"><i class="fa fa-paperclip"></i></strong>
|
||||
<strong class="date center">{% translate "Date" %}</strong>
|
||||
|
@ -8,6 +8,7 @@
|
|||
<strong class="value center">{% translate "Value" %}</strong>
|
||||
<strong class="trader center">{% translate "Trader" %}</strong>
|
||||
<strong class="category center">{% translate "Category" %}</strong>
|
||||
<strong class="account center">{% translate "Account" %}</strong>
|
||||
<strong class="description">{% translate "Description" %}</strong>
|
||||
</div>
|
||||
{% for trans in transactions %}
|
||||
|
@ -33,6 +34,14 @@
|
|||
–
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="account text center">
|
||||
{% if trans.account %}
|
||||
<i class="fa fa-{{ trans.account.icon }}"></i>
|
||||
<a href="{% url 'account' trans.account.id %}">{{ trans.account }}</a>
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="description text">{{ trans.description }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
|
|
@ -7,6 +7,7 @@ urlpatterns = [
|
|||
path("login", views.LoginView.as_view(), name="login"),
|
||||
path("logout", views.LogoutView.as_view(), name="logout"),
|
||||
path("transactions", views.TransactionListView.as_view(), name="transactions"),
|
||||
path("account", views.AccountCreateView.as_view(), name="account"),
|
||||
path("transaction", views.TransactionCreateView.as_view(), name="transaction"),
|
||||
path(
|
||||
"transaction/<transaction_pk>/invoice",
|
||||
|
@ -15,6 +16,7 @@ urlpatterns = [
|
|||
),
|
||||
path("category", views.CategoryCreateView.as_view(), name="category"),
|
||||
path("snapshot", views.SnapshotCreateView.as_view(), name="snapshot"),
|
||||
path("account/<pk>", views.AccountUpdateView.as_view(), name="account"),
|
||||
path("transaction/<pk>", views.TransactionUpdateView.as_view(), name="transaction"),
|
||||
path(
|
||||
"transaction/<transaction_pk>/invoice/<pk>",
|
||||
|
@ -23,6 +25,11 @@ urlpatterns = [
|
|||
),
|
||||
path("category/<pk>", views.CategoryUpdateView.as_view(), name="category"),
|
||||
path("snapshot/<pk>", views.SnapshotUpdateView.as_view(), name="snapshot"),
|
||||
path(
|
||||
"account/<pk>/delete",
|
||||
views.AccountDeleteView.as_view(),
|
||||
name="del_account",
|
||||
),
|
||||
path(
|
||||
"transaction/<pk>/delete",
|
||||
views.TransactionDeleteView.as_view(),
|
||||
|
|
|
@ -19,6 +19,8 @@ from django.views.generic import (
|
|||
from django.views.generic.edit import ProcessFormView
|
||||
|
||||
from .models import (
|
||||
Account,
|
||||
AccountForm,
|
||||
Category,
|
||||
CategoryForm,
|
||||
Invoice,
|
||||
|
@ -35,6 +37,7 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
return super().get_context_data(**kwargs) | {
|
||||
"accounts": Account.objects.filter(user=self.request.user),
|
||||
"transactions": Transaction.objects.filter(user=self.request.user)[:10],
|
||||
"categories": Category.objects.filter(user=self.request.user),
|
||||
"snapshots": Snapshot.objects.filter(user=self.request.user),
|
||||
|
@ -52,6 +55,11 @@ class UserCreateView(LoginRequiredMixin, CreateView):
|
|||
return super().form_valid(form)
|
||||
|
||||
|
||||
class NummiDeleteView(UserMixin, DeleteView):
|
||||
template_name = "main/confirm_delete.html"
|
||||
success_url = reverse_lazy("index")
|
||||
|
||||
|
||||
class LoginView(auth_views.LoginView):
|
||||
template_name = "main/login.html"
|
||||
next_page = "index"
|
||||
|
@ -68,6 +76,11 @@ class TransactionListView(UserMixin, ListView):
|
|||
context_object_name = "transactions"
|
||||
|
||||
|
||||
class AccountCreateView(UserCreateView):
|
||||
model = Account
|
||||
form_class = AccountForm
|
||||
|
||||
|
||||
class TransactionCreateView(UserCreateView):
|
||||
model = Transaction
|
||||
form_class = TransactionForm
|
||||
|
@ -98,6 +111,11 @@ class SnapshotCreateView(UserCreateView):
|
|||
form_class = SnapshotForm
|
||||
|
||||
|
||||
class AccountUpdateView(UserMixin, UpdateView):
|
||||
model = Account
|
||||
form_class = AccountForm
|
||||
|
||||
|
||||
class TransactionUpdateView(UserMixin, UpdateView):
|
||||
model = Transaction
|
||||
form_class = TransactionForm
|
||||
|
@ -132,15 +150,16 @@ class SnapshotUpdateView(UserMixin, UpdateView):
|
|||
form_class = SnapshotForm
|
||||
|
||||
|
||||
class TransactionDeleteView(UserMixin, DeleteView):
|
||||
class AccountDeleteView(NummiDeleteView):
|
||||
model = Account
|
||||
|
||||
|
||||
class TransactionDeleteView(NummiDeleteView):
|
||||
model = Transaction
|
||||
template_name = "main/confirm_delete.html"
|
||||
success_url = reverse_lazy("index")
|
||||
|
||||
|
||||
class InvoiceDeleteView(UserMixin, DeleteView):
|
||||
class InvoiceDeleteView(NummiDeleteView):
|
||||
model = Invoice
|
||||
template_name = "main/confirm_delete.html"
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk})
|
||||
|
@ -151,16 +170,12 @@ class InvoiceDeleteView(UserMixin, DeleteView):
|
|||
)
|
||||
|
||||
|
||||
class CategoryDeleteView(UserMixin, DeleteView):
|
||||
class CategoryDeleteView(NummiDeleteView):
|
||||
model = Category
|
||||
template_name = "main/confirm_delete.html"
|
||||
success_url = reverse_lazy("index")
|
||||
|
||||
|
||||
class SnapshotDeleteView(UserMixin, DeleteView):
|
||||
class SnapshotDeleteView(NummiDeleteView):
|
||||
model = Snapshot
|
||||
template_name = "main/confirm_delete.html"
|
||||
success_url = reverse_lazy("index")
|
||||
|
||||
|
||||
class SearchView(UserMixin, ListView, ProcessFormView):
|
||||
|
|
Loading…
Reference in a new issue