Refactor models to inherit from NummiModel and implement custom query sets for enhanced search functionality
This commit is contained in:
parent
d44407d9ab
commit
d5292911c2
9 changed files with 116 additions and 61 deletions
nummi
account
category
main
search
transaction
|
@ -4,10 +4,10 @@ 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 UserModel
|
||||
from main.models import NummiModel
|
||||
|
||||
|
||||
class Account(UserModel):
|
||||
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(
|
||||
|
@ -46,7 +46,7 @@ class Account(UserModel):
|
|||
verbose_name_plural = _("Accounts")
|
||||
|
||||
|
||||
class AccountModel(UserModel):
|
||||
class AccountModel(NummiModel):
|
||||
account = models.ForeignKey(
|
||||
Account,
|
||||
on_delete=models.CASCADE,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% load i18n main_extras %}
|
||||
<dl class="accounts">
|
||||
{% for acc in accounts %}
|
||||
<div class="account {% if acc.archived %}archived{% endif %}">
|
||||
<div class="account {% if not search and acc.archived %}archived{% endif %}">
|
||||
<dt>
|
||||
<a href="{{ acc.get_absolute_url }}">{{ acc.icon|remix }}{{ acc }}</a>
|
||||
</dt>
|
||||
|
@ -19,7 +19,7 @@
|
|||
{{ accounts|balance|value }}
|
||||
</dd>
|
||||
</div>
|
||||
{% else %}
|
||||
{% elif not search %}
|
||||
<div class="more account">
|
||||
<dt>
|
||||
<label class="wi" for="show-archived-accounts">
|
||||
|
|
|
@ -3,10 +3,10 @@ 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 UserModel
|
||||
from main.models import NummiModel
|
||||
|
||||
|
||||
class Category(UserModel):
|
||||
class Category(NummiModel):
|
||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
name = models.CharField(
|
||||
max_length=64, default=_("Category"), verbose_name=_("Name")
|
||||
|
|
|
@ -1,15 +1,46 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.postgres.search import (
|
||||
SearchQuery,
|
||||
SearchRank,
|
||||
SearchVector,
|
||||
TrigramSimilarity,
|
||||
)
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class UserModel(models.Model):
|
||||
class NummiQuerySet(models.QuerySet):
|
||||
main_field = "name"
|
||||
fields = dict()
|
||||
|
||||
def search(self, search):
|
||||
return (
|
||||
self.annotate(
|
||||
rank=SearchRank(
|
||||
sum(
|
||||
(
|
||||
SearchVector(field, weight=weight)
|
||||
for field, weight in self.fields.items()
|
||||
),
|
||||
start=SearchVector(self.main_field, weight="A"),
|
||||
),
|
||||
SearchQuery(search, search_type="websearch"),
|
||||
),
|
||||
similarity=TrigramSimilarity("name", search),
|
||||
)
|
||||
.filter(models.Q(rank__gte=0.1) | models.Q(similarity__gte=0.3))
|
||||
.order_by("-rank")
|
||||
)
|
||||
|
||||
|
||||
class NummiModel(models.Model):
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("User"),
|
||||
editable=False,
|
||||
)
|
||||
objects = NummiQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{% extends "main/form/form_base.html" %}
|
||||
{% load i18n %}
|
||||
{% block buttons %}
|
||||
<input type="reset" />
|
||||
<input type="submit" value="{% translate "Search" %}" />
|
||||
{% endblock %}
|
||||
<input type="reset" value="{% translate "Reset" %}" />
|
||||
<a href="{% url "search" %}">{% translate "Clear" %}</a>
|
||||
{% endblock buttons %}
|
||||
|
|
47
nummi/search/templates/search/search_results.html
Normal file
47
nummi/search/templates/search/search_results.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
{% extends "main/base.html" %}
|
||||
{% load i18n static %}
|
||||
{% load main_extras account_extras transaction_extras %}
|
||||
{% block title %}
|
||||
{% translate "Search" %} – Nummi
|
||||
{% endblock title %}
|
||||
{% block link %}
|
||||
{{ block.super }}
|
||||
{% css "main/css/form.css" %}
|
||||
{% css "main/css/table.css" %}
|
||||
{% endblock link %}
|
||||
{% block body %}
|
||||
<h2>{% translate "Search" %}</h2>
|
||||
<form method="post" action="{% url "search" %}">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
</form>
|
||||
{% if accounts %}
|
||||
<section>
|
||||
<h3>{% translate "Accounts" %}</h3>
|
||||
<div class="split">{% account_table accounts search=True %}</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if categories %}
|
||||
<section>
|
||||
<h3>{% translate "Categories" %}</h3>
|
||||
<p>
|
||||
{% for cat in categories %}
|
||||
<a class="category" href="{{ cat.get_absolute_url }}">{{ cat.icon|remix }}{{ cat }}</a>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if transactions %}
|
||||
<section>
|
||||
<h3>{% translate "Transactions" %}</h3>
|
||||
{% url_get "transactions" search=search as t_url %}
|
||||
<p>
|
||||
<a class="big-link" href="{{ t_url }}">{{ "list-check"|remixnl }}{% translate "Show all transactions" %}</a>
|
||||
</p>
|
||||
{% transaction_table transactions n_max=8 transactions_url=t_url %}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if not accounts and not categories and not transactions %}
|
||||
<p>{% translate "No results found." %}</p>
|
||||
{% endif %}
|
||||
{% endblock body %}
|
|
@ -1,14 +1,7 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.postgres.search import (
|
||||
SearchQuery,
|
||||
SearchRank,
|
||||
SearchVector,
|
||||
TrigramSimilarity,
|
||||
)
|
||||
from django.db import models
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
from transaction.views import TransactionListView
|
||||
|
||||
from .forms import SearchForm
|
||||
|
||||
|
@ -21,24 +14,17 @@ class SearchFormView(LoginRequiredMixin, FormView):
|
|||
return redirect("search", search=form.cleaned_data.get("search"))
|
||||
|
||||
|
||||
class SearchView(TransactionListView):
|
||||
def get_queryset(self):
|
||||
self.search = self.kwargs["search"]
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.annotate(
|
||||
rank=SearchRank(
|
||||
SearchVector("name", weight="A")
|
||||
+ SearchVector("description", weight="B")
|
||||
+ SearchVector("trader", weight="B"),
|
||||
SearchQuery(self.search, search_type="websearch"),
|
||||
),
|
||||
similarity=TrigramSimilarity("name", self.search),
|
||||
)
|
||||
.filter(models.Q(rank__gte=0.1) | models.Q(similarity__gte=0.3))
|
||||
.order_by("-rank", "-date")
|
||||
)
|
||||
class SearchView(LoginRequiredMixin, TemplateView):
|
||||
template_name = "search/search_results.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return super().get_context_data(**kwargs) | {"search": self.kwargs["search"]}
|
||||
context = super().get_context_data(**kwargs)
|
||||
_user = self.request.user
|
||||
|
||||
context["form"] = SearchForm(initial={"search": self.kwargs["search"]})
|
||||
context["search"] = self.kwargs["search"]
|
||||
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"])
|
||||
|
||||
return context
|
||||
|
|
|
@ -7,12 +7,20 @@ from django.core.validators import FileExtensionValidator
|
|||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from main.models import UserModel
|
||||
from main.models import NummiModel, NummiQuerySet
|
||||
from media.utils import get_path
|
||||
from statement.models import Statement
|
||||
|
||||
|
||||
class Transaction(UserModel):
|
||||
class TransactionQuerySet(NummiQuerySet):
|
||||
fields = {
|
||||
"description": "B",
|
||||
"trader": "B",
|
||||
"category__name": "C",
|
||||
}
|
||||
|
||||
|
||||
class Transaction(NummiModel):
|
||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
name = models.CharField(
|
||||
max_length=256, default=_("Transaction"), verbose_name=_("Name")
|
||||
|
@ -44,6 +52,8 @@ class Transaction(UserModel):
|
|||
verbose_name=_("Statement"),
|
||||
)
|
||||
|
||||
objects = TransactionQuerySet.as_manager()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
|
@ -64,7 +74,7 @@ class Transaction(UserModel):
|
|||
verbose_name_plural = _("Transactions")
|
||||
|
||||
|
||||
class Invoice(UserModel):
|
||||
class Invoice(NummiModel):
|
||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
name = models.CharField(
|
||||
max_length=256, default=_("Invoice"), verbose_name=_("Name")
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
from account.models import Account
|
||||
from category.models import Category
|
||||
from django.contrib import messages
|
||||
from django.contrib.postgres.search import (
|
||||
SearchQuery,
|
||||
SearchRank,
|
||||
SearchVector,
|
||||
TrigramSimilarity,
|
||||
)
|
||||
from django.db import models
|
||||
from django.forms import ValidationError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
|
@ -193,20 +186,7 @@ class TransactionListView(NummiListView):
|
|||
if statement := self.request.GET.get("statement"):
|
||||
queryset = queryset.filter(statement=statement)
|
||||
if search := self.request.GET.get("search"):
|
||||
queryset = (
|
||||
queryset.annotate(
|
||||
rank=SearchRank(
|
||||
SearchVector("name", weight="A")
|
||||
+ SearchVector("description", weight="B")
|
||||
+ SearchVector("trader", weight="B")
|
||||
+ SearchVector("category__name", weight="C"),
|
||||
SearchQuery(search, search_type="websearch"),
|
||||
),
|
||||
similarity=TrigramSimilarity("name", search),
|
||||
)
|
||||
.filter(models.Q(rank__gte=0.1) | models.Q(similarity__gte=0.3))
|
||||
.order_by("-rank", "-date")
|
||||
)
|
||||
queryset = queryset.search(search)
|
||||
if sort_by := self.request.GET.get("sort_by"):
|
||||
queryset = queryset.order_by(sort_by)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue