424 lines
12 KiB
Python
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
|