Files
kakebo/app/monthly/liveview_handlers/fixed_expenses.py
Andros Fenollosa 7fa9f5db86 Code quality: fix N+1 queries, remove dead code, improve architecture
- Yearly view: replace N*12 queries with annotate/ExtractMonth (6 queries)
- Fixed expenses: bulk_create instead of get_or_create loop
- Use DB aggregate(Sum) instead of Python sum() for totals
- Remove 10 unused CRUD views, 13 URLs, 6 templates, 3 forms
- Fix overly broad Exception catch in save_expense
- Move update_budget to app/monthly/services.py (no cross-handler imports)
2026-03-29 11:14:07 +02:00

97 lines
2.9 KiB
Python

from decimal import Decimal, InvalidOperation
from django.db.models import Sum
from django.template.loader import render_to_string
from liveview import liveview_handler, send
from app.expenses.models import FixedExpenseConcept
from app.expenses.templatetags.money import format_money
from app.monthly.models import MonthlyFixedExpense
from app.monthly.services import update_budget
def _ensure_entries_exist(year, month):
"""Create missing MonthlyFixedExpense entries in bulk."""
concepts = FixedExpenseConcept.objects.all()
existing_ids = set(
MonthlyFixedExpense.objects.filter(year=year, month=month).values_list(
"concept_id", flat=True
)
)
to_create = [
MonthlyFixedExpense(year=year, month=month, concept=c)
for c in concepts
if c.id not in existing_ids
]
if to_create:
MonthlyFixedExpense.objects.bulk_create(to_create)
def _render_fixed_expenses_table(year, month):
_ensure_entries_exist(year, month)
entries = MonthlyFixedExpense.objects.filter(year=year, month=month).select_related(
"concept"
)
total = entries.aggregate(total=Sum("amount"))["total"] or Decimal("0")
return render_to_string(
"pages/monthly/partials/fixed_expenses_table.html",
{"entries": entries, "total": total, "year": year, "month": month},
)
@liveview_handler("save_monthly_fixed_expense")
def save_monthly_fixed_expense(consumer, content):
form = content.get("form", {})
entry_id = form.get("entry_id", "")
year = int(form.get("year", 0))
month = int(form.get("month", 0))
amount_raw = form.get("fe_amount", "").replace(",", ".")
if not entry_id or not year or not month:
return
try:
amount = Decimal(amount_raw) if amount_raw else Decimal("0")
entry = MonthlyFixedExpense.objects.get(id=entry_id)
entry.amount = amount
entry.save()
except (InvalidOperation, ValueError, MonthlyFixedExpense.DoesNotExist):
return
total = MonthlyFixedExpense.objects.filter(year=year, month=month).aggregate(
total=Sum("amount")
)["total"] or Decimal("0")
send(consumer, {"target": "#fe-total", "html": f"{format_money(total)}"})
update_budget(consumer, year, month)
@liveview_handler("clone_fixed_expenses")
def clone_fixed_expenses(consumer, content):
form = content.get("form", {})
source = form.get("clone_fe_source", "")
year = int(form.get("year", 0))
month = int(form.get("month", 0))
if not source or not year or not month:
return
try:
src_year, src_month = source.split("-")
src_year, src_month = int(src_year), int(src_month)
except ValueError:
return
source_entries = MonthlyFixedExpense.objects.filter(
year=src_year, month=src_month
).select_related("concept")
_ensure_entries_exist(year, month)
for src in source_entries:
MonthlyFixedExpense.objects.filter(
year=year, month=month, concept=src.concept
).update(amount=src.amount)
html = _render_fixed_expenses_table(year, month)
send(consumer, {"target": "#fixed-expenses-table", "html": html})
update_budget(consumer, year, month)