nummi/nummi/main/views.py

424 lines
12 KiB
Python

from django.conf import settings
from django.contrib.auth import views as auth_views
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.postgres.search import (
SearchQuery,
SearchRank,
SearchVector,
TrigramSimilarity,
)
from django.core.exceptions import PermissionDenied
from django.db import models
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
from django.views import View
from django.views.generic import (
CreateView,
DeleteView,
FormView,
ListView,
TemplateView,
UpdateView,
)
from django.views.static import serve
from .forms import (
AccountForm,
CategoryForm,
InvoiceForm,
SearchForm,
SnapshotForm,
TransactionForm,
)
from .models import Account, Category, Invoice, Snapshot, Transaction
class IndexView(LoginRequiredMixin, TemplateView):
template_name = "main/index.html"
def get_context_data(self, **kwargs):
_max = 8
_transactions = Transaction.objects.filter(user=self.request.user)
_snapshots = Snapshot.objects.filter(user=self.request.user)
_history = (
_transactions.filter(category__budget=True)
.values(month=models.functions.TruncMonth("date"))
.annotate(
sum_p=models.Sum("value", filter=models.Q(value__gt=0)),
sum_m=models.Sum("value", filter=models.Q(value__lt=0)),
sum=models.Sum("value"),
)
.order_by("-month")
)
res = {
"accounts": Account.objects.filter(user=self.request.user),
"transactions": _transactions[:_max],
"categories": Category.objects.filter(user=self.request.user),
"snapshots": _snapshots[:_max],
"history": {
"data": _history,
"max": max(
_history.aggregate(
max=models.Max("sum_p"),
min=-models.Min("sum_m"),
).values(),
),
},
}
if _transactions.count() > _max:
res["transactions_url"] = reverse_lazy("transactions")
if _snapshots.count() > _max:
res["snapshots_url"] = reverse_lazy("snapshots")
return super().get_context_data(**kwargs) | res
class UserMixin(LoginRequiredMixin):
def get_queryset(self, **kwargs):
return super().get_queryset().filter(user=self.request.user)
class UserFormMixin:
def get_form_kwargs(self):
return super().get_form_kwargs() | {
"user": self.request.user,
}
class NummiCreateView(UserMixin, UserFormMixin, CreateView):
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class NummiUpdateView(UserMixin, UserFormMixin, UpdateView):
pass
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"
class LogoutView(auth_views.LogoutView):
next_page = "login"
class AccountCreateView(NummiCreateView):
model = Account
form_class = AccountForm
class TransactionCreateView(NummiCreateView):
model = Transaction
form_class = TransactionForm
def get_form_kwargs(self):
return super().get_form_kwargs() | {
"initial": {
"snapshot": (
self.kwargs.get("snapshot")
or Snapshot.objects.filter(user=self.request.user).first()
),
},
}
class InvoiceCreateView(NummiCreateView):
model = Invoice
form_class = InvoiceForm
def form_valid(self, form):
form.instance.transaction = get_object_or_404(
Transaction.objects.filter(user=self.request.user),
pk=self.kwargs["transaction_pk"],
)
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk})
class CategoryCreateView(NummiCreateView):
model = Category
form_class = CategoryForm
class SnapshotCreateView(NummiCreateView):
model = Snapshot
form_class = SnapshotForm
def get_form_kwargs(self):
return super().get_form_kwargs() | {
"initial": {
"account": (
self.kwargs.get("account")
or Account.objects.filter(user=self.request.user)
.order_by("-default")
.first()
),
},
}
class AccountUpdateView(NummiUpdateView):
model = Account
form_class = AccountForm
def get_context_data(self, **kwargs):
_max = 8
data = super().get_context_data(**kwargs)
account = data["form"].instance
_transactions = account.transaction_set.all()
if _transactions.count() > _max:
data["transactions_url"] = reverse_lazy(
"account_transactions", args=(account.pk,)
)
_snapshots = account.snapshot_set.all()
if _snapshots.count() > _max:
data["snapshots_url"] = reverse_lazy(
"account_snapshots", args=(account.pk,)
)
return data | {
"transactions": _transactions[:8],
"new_snapshot_url": reverse_lazy(
"snapshot", kwargs={"account": account.pk}
),
"snapshots": _snapshots[:8],
}
class TransactionUpdateView(NummiUpdateView):
model = Transaction
form_class = TransactionForm
class InvoiceUpdateView(NummiUpdateView):
model = Invoice
form_class = InvoiceForm
def get_success_url(self):
return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk})
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
transaction=get_object_or_404(
Transaction, pk=self.kwargs["transaction_pk"]
)
)
)
class CategoryUpdateView(NummiUpdateView):
model = Category
form_class = CategoryForm
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
category = data["form"].instance
return data | {
"transactions": category.transaction_set.all()[:8],
"transactions_url": reverse_lazy(
"category_transactions", args=(category.pk,)
),
}
class SnapshotUpdateView(NummiUpdateView):
model = Snapshot
form_class = SnapshotForm
def get_context_data(self, **kwargs):
_max = 8
data = super().get_context_data(**kwargs)
snapshot = data["form"].instance
_transactions = snapshot.transaction_set.all()
if _transactions.count() > _max:
data["transactions_url"] = reverse_lazy(
"snapshot_transactions", args=(snapshot.pk,)
)
if _transactions:
_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")
)
data["categories"] = {
"data": _categories,
"max": max(
_categories.aggregate(
max=models.Max("sum_p"),
min=models.Min("sum_m"),
).values(),
),
}
return data | {
"new_transaction_url": reverse_lazy(
"transaction", kwargs={"snapshot": snapshot.pk}
),
"transactions": _transactions[:_max],
}
class AccountDeleteView(NummiDeleteView):
model = Account
class TransactionDeleteView(NummiDeleteView):
model = Transaction
class InvoiceDeleteView(NummiDeleteView):
model = Invoice
def get_success_url(self):
return reverse_lazy("transaction", kwargs={"pk": self.object.transaction.pk})
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
transaction=get_object_or_404(
Transaction, pk=self.kwargs["transaction_pk"]
)
)
)
class CategoryDeleteView(NummiDeleteView):
model = Category
class SnapshotDeleteView(NummiDeleteView):
model = Snapshot
class NummiListView(UserMixin, ListView):
paginate_by = 96
class TransactionListView(NummiListView):
model = Transaction
template_name = "main/list/transaction.html"
context_object_name = "transactions"
class SnapshotListView(NummiListView):
model = Snapshot
template_name = "main/list/snapshot.html"
context_object_name = "snapshots"
class AccountMixin:
def get_queryset(self):
return super().get_queryset().filter(account=self.kwargs.get("pk"))
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {
"object": Account.objects.get(pk=self.kwargs.get("pk")),
"account": True,
}
class SnapshotMixin:
def get_queryset(self):
return super().get_queryset().filter(snapshot=self.kwargs.get("pk"))
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {
"object": Snapshot.objects.get(pk=self.kwargs.get("pk")),
"snapshot": True,
}
class CategoryMixin:
def get_queryset(self):
return super().get_queryset().filter(category=self.kwargs.get("pk"))
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {
"object": Category.objects.get(pk=self.kwargs.get("pk")),
"category": True,
}
class AccountTListView(AccountMixin, TransactionListView):
pass
class AccountSListView(AccountMixin, SnapshotListView):
pass
class SnapshotTListView(SnapshotMixin, TransactionListView):
pass
class CategoryTListView(CategoryMixin, TransactionListView):
pass
class SearchFormView(LoginRequiredMixin, FormView):
template_name = "main/search.html"
form_class = SearchForm
def form_valid(self, form):
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")
)
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {"search": self.kwargs["search"]}
class MediaView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
_username = kwargs.get("username")
_path = kwargs.get("path")
if request.user.get_username() != _username:
raise PermissionDenied
if settings.DEBUG:
return serve(request, f"user/{_username}/{_path}", settings.MEDIA_ROOT)
_res = HttpResponse()
_res["Content-Type"] = ""
_res["X-Accel-Redirect"] = f"/internal/media/user/{_username}/{_path}"
return _res