Files
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

171 lines
4.3 KiB
Python

from datetime import date
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.monthly.services import update_budget
from app.monthly.models import Income
def _render_income_table(year, month, editing_id=None):
incomes = Income.objects.filter(
date__year=year,
date__month=month,
)
total = incomes.aggregate(total=Sum("amount"))["total"] or Decimal("0")
return render_to_string(
"pages/monthly/partials/income_table.html",
{
"incomes": incomes,
"total": total,
"year": year,
"month": month,
"today": date.today().isoformat(),
"editing_id": editing_id,
},
)
@liveview_handler("add_income")
def add_income(consumer, content):
form = content.get("form", {})
year = int(form.get("year", 0))
month = int(form.get("month", 0))
concept = form.get("income_concept", "").strip()
amount_raw = form.get("income_amount", "").replace(",", ".")
date_raw = form.get("income_date", "")
if not concept or not amount_raw or not date_raw or not year or not month:
return
try:
amount = Decimal(amount_raw)
income_date = date.fromisoformat(date_raw)
except (InvalidOperation, ValueError):
return
Income.objects.create(
date=income_date,
concept=concept,
amount=amount,
)
html = _render_income_table(year, month)
send(consumer, {"target": "#income-table", "html": html})
update_budget(consumer, year, month)
@liveview_handler("delete_income")
def delete_income(consumer, content):
data = content.get("data", {})
income_id = data.get("data_id", "")
year = int(data.get("data_year", 0))
month = int(data.get("data_month", 0))
if not income_id or not year or not month:
return
Income.objects.filter(id=income_id).delete()
html = _render_income_table(year, month)
send(consumer, {"target": "#income-table", "html": html})
update_budget(consumer, year, month)
@liveview_handler("edit_income")
def edit_income(consumer, content):
data = content.get("data", {})
income_id = data.get("data_id", "")
year = int(data.get("data_year", 0))
month = int(data.get("data_month", 0))
if not income_id:
return
try:
income = Income.objects.get(id=income_id)
except Income.DoesNotExist:
return
html = _render_income_table(year, month, editing_id=income.id)
send(consumer, {"target": "#income-table", "html": html})
@liveview_handler("cancel_edit_income")
def cancel_edit_income(consumer, content):
data = content.get("data", {})
year = int(data.get("data_year", 0))
month = int(data.get("data_month", 0))
if not year or not month:
return
html = _render_income_table(year, month)
send(consumer, {"target": "#income-table", "html": html})
@liveview_handler("save_income")
def save_income(consumer, content):
form = content.get("form", {})
income_id = form.get("income_id", "")
year = int(form.get("year", 0))
month = int(form.get("month", 0))
concept = form.get("income_concept", "").strip()
amount_raw = form.get("income_amount", "").replace(",", ".")
date_raw = form.get("income_date", "")
if not income_id or not concept or not amount_raw or not date_raw:
return
try:
amount = Decimal(amount_raw)
income_date = date.fromisoformat(date_raw)
income = Income.objects.get(id=income_id)
except (InvalidOperation, ValueError, Income.DoesNotExist):
return
income.date = income_date
income.concept = concept
income.amount = amount
income.save()
html = _render_income_table(year, month)
send(consumer, {"target": "#income-table", "html": html})
update_budget(consumer, year, month)
@liveview_handler("clone_income")
def clone_income(consumer, content):
form = content.get("form", {})
source = form.get("clone_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_incomes = Income.objects.filter(
date__year=src_year,
date__month=src_month,
)
today = date.today()
for inc in source_incomes:
Income.objects.create(
date=today,
concept=inc.concept,
amount=inc.amount,
)
html = _render_income_table(year, month)
send(consumer, {"target": "#income-table", "html": html})
update_budget(consumer, year, month)