nummi/nummi/main/models.py

313 lines
9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pathlib
import uuid
from datetime import date
from django.core.files.storage import Storage
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 _
class Category(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(
max_length=64, default=_("Category"), verbose_name=_("Name")
)
icon = models.CharField(max_length=64, default="folder", verbose_name=_("Icon"))
budget = models.BooleanField(default=True, verbose_name=_("Budget"))
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("category", kwargs={"pk": self.pk})
def get_delete_url(self):
return reverse("del_category", kwargs={"pk": self.pk})
@property
def adding(self):
return self._state.adding
@property
def transactions(self):
return Transaction.objects.filter(category=self)
class Meta:
ordering = ["name"]
verbose_name = _("Category")
verbose_name_plural = _("Categories")
class CategoryForm(ModelForm):
template_name = "main/form/base.html"
class Meta:
model = Category
fields = ["name", "icon", "budget"]
class Transaction(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(
max_length=256, default=_("Transaction"), verbose_name=_("Name")
)
description = models.TextField(null=True, blank=True, verbose_name=_("Description"))
value = models.DecimalField(
max_digits=12, decimal_places=2, default=0, verbose_name=_("Value")
)
date = models.DateField(default=date.today, verbose_name=_("Date"))
real_date = models.DateField(blank=True, null=True, verbose_name=_("Real date"))
trader = models.CharField(
max_length=128, blank=True, null=True, verbose_name=_("Trader")
)
payment = models.CharField(
max_length=128, blank=True, null=True, verbose_name=_("Payment")
)
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_("Category"),
)
def __str__(self):
return f"{self.date} {self.name}"
def get_absolute_url(self):
return reverse("transaction", kwargs={"pk": self.pk})
def get_delete_url(self):
return reverse("del_transaction", kwargs={"pk": self.pk})
@property
def adding(self):
return self._state.adding
@property
def invoices(self):
return Invoice.objects.filter(transaction=self)
@property
def has_invoice(self):
return self.invoices.count() > 0
class Meta:
ordering = ["-date"]
verbose_name = _("Transaction")
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(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(
max_length=256, default=_("Invoice"), verbose_name=_("Name")
)
file = models.FileField(
upload_to=invoice_path,
validators=[FileExtensionValidator(["pdf"])],
verbose_name=_("File"),
max_length=128,
)
transaction = models.ForeignKey(
Transaction, on_delete=models.CASCADE, editable=False
)
def __str__(self):
if hasattr(self, "transaction"):
return f"{self.name} {self.transaction.name}"
return self.name
def delete(self, *args, **kwargs):
self.file.delete()
super().delete(*args, **kwargs)
def get_absolute_url(self):
return reverse(
"invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk}
)
def get_delete_url(self):
return reverse(
"del_invoice", kwargs={"transaction_pk": self.transaction.pk, "pk": self.pk}
)
@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(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
date = models.DateField(default=date.today, unique=True, verbose_name=_("Date"))
value = models.DecimalField(
max_digits=12, decimal_places=2, default=0, verbose_name=_("Value")
)
previous = models.OneToOneField(
"self", on_delete=models.SET_NULL, blank=True, null=True, editable=False
)
diff = models.DecimalField(
max_digits=12, decimal_places=2, editable=False, blank=True, null=True
)
file = models.FileField(
upload_to=snapshot_path,
validators=[FileExtensionValidator(["pdf"])],
verbose_name=_("File"),
max_length=256,
blank=True,
)
def __str__(self):
return _("%(date)s snapshot") % {"date": self.date}
def save(self, *args, only_super=False, **kwargs):
if not only_super:
_prever = Snapshot.objects.get(id=self.id)
if _prever.file and _prever.file != self.file:
pathlib.Path(_prever.file.path).unlink(missing_ok=True)
_prev = (
self.__class__.objects.order_by("-date")
.exclude(id=self.id)
.filter(date__lt=self.date)
.first()
)
try:
_next = self.__class__.objects.exclude(id=self.id).get(previous=_prev)
except self.__class__.DoesNotExist:
pass
else:
try:
_prevnext = self.__class__.objects.exclude(id=self.id).get(
previous=self
)
except self.__class__.DoesNotExist:
pass
else:
_prevnext.previous = (
self.__class__.objects.order_by("-date")
.exclude(id=self.id)
.filter(date__lt=_prevnext.date)
.first()
)
_prevnext.save(only_super=True)
_next.previous = None
super().save(*args, **kwargs)
_next.previous = self
_next.save(only_super=True)
self.previous = _prev
if self.previous is None:
self.diff = None
else:
self.diff = self.value - self.previous.value
super().save(*args, **kwargs)
try:
_next = self.__class__.objects.get(previous=self)
except self.__class__.DoesNotExist:
pass
else:
_next.save(only_super=True)
def delete(self, *args, only_super=False, **kwargs):
self.file.delete()
if not only_super:
try:
_next = self.__class__.objects.get(previous=self)
except self.__class__.DoesNotExist:
super().delete(*args, **kwargs)
else:
_next.previous = self.previous
super().delete(*args, **kwargs)
_next.save(only_super=True)
else:
super().delete(*args, **kwargs)
@property
def sum(self):
if self.previous is None:
return 0
trans = self.transactions.aggregate(sum=models.Sum("value"))
return trans["sum"] or 0
@property
def transactions(self):
if self.previous is None:
return Transaction.objects.none()
return Transaction.objects.filter(
date__lte=self.date, date__gt=self.previous.date
)
@property
def pos(self):
return (
self.transactions.filter(value__gt=0).aggregate(sum=models.Sum("value"))[
"sum"
]
or 0
)
@property
def neg(self):
return (
self.transactions.filter(value__lt=0).aggregate(sum=models.Sum("value"))[
"sum"
]
or 0
)
class Meta:
ordering = ["-date"]
verbose_name = _("Snapshot")
verbose_name_plural = _("Snapshots")
class SnapshotForm(ModelForm):
template_name = "main/form/base.html"
class Meta:
model = Snapshot
fields = ["date", "value", "file"]