From c153000d3d3b6c6d360edaaba765bdd2895f1e6d Mon Sep 17 00:00:00 2001 From: "Edgar P. Burkhart" <git@edgarpierre.fr> Date: Sun, 5 Jan 2025 16:01:26 +0100 Subject: [PATCH] Add invoice model with metadata and tags; update search and templates for invoice handling Progress #44 --- nummi/main/utils.py | 9 ++++++ .../templates/search/search_results.html | 8 ++++- nummi/search/views.py | 1 + .../0005_invoice_metadata_invoice_tags.py | 22 +++++++++++++ nummi/transaction/models.py | 32 +++++++++++++++++++ .../templates/transaction/invoice_table.html | 17 ++++++---- .../templatetags/transaction_extras.py | 6 ++-- pkgbuild/PKGBUILD | 1 + 8 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 nummi/transaction/migrations/0005_invoice_metadata_invoice_tags.py diff --git a/nummi/main/utils.py b/nummi/main/utils.py index 2a8bb28..d041dba 100644 --- a/nummi/main/utils.py +++ b/nummi/main/utils.py @@ -7,3 +7,12 @@ def get_icons(): data = json.loads(request.urlopen(url).read()) return [i.removesuffix("-line") for i in data.keys() if i.endswith("-line")] + + +def pdf_outline_to_str(outline): + return " ".join( + ( + dest.title if not isinstance(dest, list) else pdf_outline_to_str(dest) + for dest in outline + ) + ) diff --git a/nummi/search/templates/search/search_results.html b/nummi/search/templates/search/search_results.html index d19a974..197a668 100644 --- a/nummi/search/templates/search/search_results.html +++ b/nummi/search/templates/search/search_results.html @@ -41,7 +41,13 @@ {% transaction_table transactions n_max=8 transactions_url=t_url %} </section> {% endif %} - {% if not accounts and not categories and not transactions %} + {% if invoices %} + <section> + <h3>{% translate "Invoices" %}</h3> + {% invoice_table invoices=invoices %} + </section> + {% endif %} + {% if not accounts and not categories and not transactions and not invoices %} <p>{% translate "No results found." %}</p> {% endif %} {% endblock body %} diff --git a/nummi/search/views.py b/nummi/search/views.py index 55abba1..5a64a04 100644 --- a/nummi/search/views.py +++ b/nummi/search/views.py @@ -26,5 +26,6 @@ class SearchView(LoginRequiredMixin, TemplateView): context["transactions"] = _user.transaction_set.search(self.kwargs["search"]) context["accounts"] = _user.account_set.search(self.kwargs["search"]) context["categories"] = _user.category_set.search(self.kwargs["search"]) + context["invoices"] = _user.invoice_set.search(self.kwargs["search"])[:10] return context diff --git a/nummi/transaction/migrations/0005_invoice_metadata_invoice_tags.py b/nummi/transaction/migrations/0005_invoice_metadata_invoice_tags.py new file mode 100644 index 0000000..af684f5 --- /dev/null +++ b/nummi/transaction/migrations/0005_invoice_metadata_invoice_tags.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.7 on 2025-01-05 14:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("transaction", "0004_remove_transaction_account"), + ] + + operations = [ + migrations.AddField( + model_name="invoice", + name="metadata", + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name="invoice", + name="tags", + field=models.TextField(blank=True), + ), + ] diff --git a/nummi/transaction/models.py b/nummi/transaction/models.py index 317b39b..f50e4fa 100644 --- a/nummi/transaction/models.py +++ b/nummi/transaction/models.py @@ -8,7 +8,9 @@ from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ from main.models import NummiModel, NummiQuerySet +from main.utils import pdf_outline_to_str from media.utils import get_path +from pypdf import PdfReader from statement.models import Statement @@ -74,6 +76,13 @@ class Transaction(NummiModel): verbose_name_plural = _("Transactions") +class InvoiceQuerySet(NummiQuerySet): + fields = { + "metadata": "B", + "tags": "C", + } + + class Invoice(NummiModel): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) name = models.CharField( @@ -88,12 +97,35 @@ class Invoice(NummiModel): transaction = models.ForeignKey( Transaction, on_delete=models.CASCADE, editable=False ) + metadata = models.TextField(blank=True) + tags = models.TextField(blank=True) + + objects = InvoiceQuerySet.as_manager() def save(self, *args, **kwargs): if Invoice.objects.filter(id=self.id).exists(): _prever = Invoice.objects.get(id=self.id) if _prever.file and _prever.file != self.file: Path(_prever.file.path).unlink(missing_ok=True) + + reader = PdfReader(self.file) + + self.metadata = " ".join( + ( + m + for m in ( + reader.metadata.title, + reader.metadata.author, + reader.metadata.subject, + ) + if m + ) + ) + + _tags = pdf_outline_to_str(reader.outline) + _tags += " ".join((page.extract_text() for page in reader.pages)) + self.tags = " ".join((tag for tag in _tags.split() if len(tag) >= 3)) + super().save(*args, **kwargs) def __str__(self): diff --git a/nummi/transaction/templates/transaction/invoice_table.html b/nummi/transaction/templates/transaction/invoice_table.html index 7b5919b..07cd9c1 100644 --- a/nummi/transaction/templates/transaction/invoice_table.html +++ b/nummi/transaction/templates/transaction/invoice_table.html @@ -3,14 +3,19 @@ <ul class="invoices"> {% for invoice in invoices %} <li> + {% if not transaction %}<span>{{ invoice.transaction.name }}</span>{% endif %} <a class="title" href="{{ invoice.file.url }}">{{ "file"|remix }}{{ invoice.name }} [{{ invoice.file|extension }}]</a> - <a href="{{ invoice.get_absolute_url }}">{{ "file-edit"|remix }}{% translate "Edit" %}</a> + {% if transaction %} + <a href="{{ invoice.get_absolute_url }}">{{ "file-edit"|remix }}{% translate "Edit" %}</a> + {% endif %} </li> {% endfor %} - <li class="new"> - <span> - <a href="{% url "new_invoice" transaction.pk %}">{{ "file-add"|remix }}{% translate "New invoice" %}</a> - </span> - </li> + {% if transaction %} + <li class="new"> + <span> + <a href="{% url "new_invoice" transaction.pk %}">{{ "file-add"|remix }}{% translate "New invoice" %}</a> + </span> + </li> + {% endif %} </ul> </div> diff --git a/nummi/transaction/templatetags/transaction_extras.py b/nummi/transaction/templatetags/transaction_extras.py index e905a02..7e2c016 100644 --- a/nummi/transaction/templatetags/transaction_extras.py +++ b/nummi/transaction/templatetags/transaction_extras.py @@ -36,10 +36,12 @@ def transaction_filters(context, **kwargs): @register.inclusion_tag("transaction/invoice_table.html") -def invoice_table(transaction, **kwargs): +def invoice_table(transaction=None, **kwargs): + if transaction: + kwargs.setdefault("invoices", transaction.invoice_set.all()) + return kwargs | { "transaction": transaction, - "invoices": transaction.invoice_set.all(), } diff --git a/pkgbuild/PKGBUILD b/pkgbuild/PKGBUILD index 88e06a0..d960964 100644 --- a/pkgbuild/PKGBUILD +++ b/pkgbuild/PKGBUILD @@ -11,6 +11,7 @@ depends=( "python-toml" "python-psycopg" "python-dateutil" + "python-pypdf" ) makedepends=("git") optdepends=("postgresql: database")