from collections import OrderedDict
from datetime import timedelta
from decimal import Decimal, InvalidOperation
from django.template.loader import render_to_string
from django.utils import timezone
from liveview import liveview_handler, send
from app.expenses.models import Category, Expense, Subcategory
def _build_week_data(offset):
today = timezone.localdate()
monday = today - timedelta(days=today.weekday()) + timedelta(weeks=offset)
sunday = monday + timedelta(days=6)
expenses = Expense.objects.filter(
created_at__date__gte=monday,
created_at__date__lte=sunday,
).select_related("category", "subcategory")
categories_data = OrderedDict()
total = Decimal("0")
for expense in expenses:
cat = expense.category
if cat.id not in categories_data:
categories_data[cat.id] = {
"category": cat,
"expenses": [],
"subcategory_totals": OrderedDict(),
"total": Decimal("0"),
}
entry = categories_data[cat.id]
entry["expenses"].append(expense)
entry["total"] += expense.amount
total += expense.amount
sub = expense.subcategory
if sub.id not in entry["subcategory_totals"]:
entry["subcategory_totals"][sub.id] = {
"subcategory": sub,
"total": Decimal("0"),
}
entry["subcategory_totals"][sub.id]["total"] += expense.amount
return {
"categories_data": categories_data.values(),
"total": total,
"monday": monday,
"sunday": sunday,
"offset": offset,
"prev_offset": offset - 1,
"next_offset": offset + 1,
"is_current_week": offset == 0,
}
def _render_week(offset, editing_id=None):
context = _build_week_data(offset)
context["editing_id"] = editing_id
if editing_id:
try:
expense = Expense.objects.get(id=editing_id)
context["editing_expense"] = expense
context["user_categories"] = Category.objects.all()
context["user_subcategories"] = Subcategory.objects.filter(
category=expense.category
)
except Expense.DoesNotExist:
pass
return render_to_string("pages/expenses/partials/week_content.html", context)
@liveview_handler("delete_expense")
def delete_expense(consumer, content):
data = content.get("data", {})
expense_id = data.get("data_id", "")
offset = int(data.get("data_offset", 0))
if not expense_id:
return
Expense.objects.filter(id=expense_id).delete()
html = _render_week(offset)
send(consumer, {"target": "#week-content", "html": html})
@liveview_handler("edit_expense")
def edit_expense(consumer, content):
data = content.get("data", {})
expense_id = data.get("data_id", "")
offset = int(data.get("data_offset", 0))
if not expense_id:
return
html = _render_week(offset, editing_id=int(expense_id))
send(consumer, {"target": "#week-content", "html": html})
@liveview_handler("save_expense")
def save_expense(consumer, content):
form = content.get("form", {})
expense_id = form.get("expense_id", "")
offset = int(form.get("offset", 0))
concept = form.get("expense_concept", "").strip()
amount_raw = form.get("expense_amount", "").replace(",", ".")
category_id = form.get("expense_category", "")
subcategory_id = form.get("expense_subcategory", "")
if not expense_id or not concept or not amount_raw:
return
try:
expense = Expense.objects.get(id=expense_id)
expense.concept = concept
expense.amount = Decimal(amount_raw)
if category_id:
expense.category_id = int(category_id)
if subcategory_id:
expense.subcategory_id = int(subcategory_id)
expense.save()
except (Expense.DoesNotExist, ValueError, InvalidOperation):
return
html = _render_week(offset)
send(consumer, {"target": "#week-content", "html": html})
@liveview_handler("update_week_subcategories")
def update_week_subcategories(consumer, content):
form = content.get("form", {})
category_id = form.get("expense_category", "")
if not category_id:
return
try:
subcategories = Subcategory.objects.filter(category_id=int(category_id))
except (ValueError, Category.DoesNotExist):
return
options = "".join(
f'' for sub in subcategories
)
html = f''
send(consumer, {"target": "#week-subcategory-wrapper", "html": html})
@liveview_handler("cancel_edit_expense")
def cancel_edit_expense(consumer, content):
data = content.get("data", {})
offset = int(data.get("data_offset", 0))
html = _render_week(offset)
send(consumer, {"target": "#week-content", "html": html})