Files
kakebo/app/expenses/views.py
Andros Fenollosa 625bc22362 Add CSV export, budget subtracts planned expenses
- Export all expenses as CSV from Settings (semicolon, UTF-8 BOM)
- Budget calculation now includes planned expenses:
  income - fixed - savings - planned
- Budget live update also accounts for planned expenses
2026-03-29 11:53:54 +02:00

121 lines
3.2 KiB
Python

import csv
from datetime import timedelta
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.utils import timezone
from .models import Category, Expense, FixedExpenseConcept
from .forms import ExpenseForm
@login_required
def dashboard(request):
form = ExpenseForm()
context = {"form": form}
return render(request, "pages/expenses/dashboard.html", context)
@login_required
def add_expense(request):
if request.method == "POST":
form = ExpenseForm(request.POST)
if form.is_valid():
form.save()
return redirect("expenses:expense_success")
context = {"form": form}
return render(request, "pages/expenses/dashboard.html", context)
return redirect("expenses:dashboard")
@login_required
def expense_success(request):
return render(request, "pages/expenses/expense_success.html")
@login_required
def week_view(request):
from collections import OrderedDict
from decimal import Decimal
today = timezone.localdate()
offset = int(request.GET.get("offset", 0))
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
is_current_week = offset == 0
context = {
"categories_data": categories_data.values(),
"total": total,
"monday": monday,
"sunday": sunday,
"offset": offset,
"prev_offset": offset - 1,
"next_offset": offset + 1,
"is_current_week": is_current_week,
}
return render(request, "pages/expenses/week.html", context)
@login_required
def settings_view(request):
categories = Category.objects.all().prefetch_related("subcategories")
fixed_expenses = FixedExpenseConcept.objects.all()
context = {"categories": categories, "fixed_expenses": fixed_expenses}
return render(request, "pages/expenses/settings.html", context)
@login_required
def export_csv(request):
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="kakebo_expenses.csv"'
response.write("\ufeff") # BOM for Excel UTF-8
writer = csv.writer(response, delimiter=";")
writer.writerow(["Date", "Concept", "Amount", "Category", "Subcategory"])
expenses = Expense.objects.all().select_related("category", "subcategory")
for expense in expenses:
writer.writerow(
[
expense.created_at.strftime("%Y-%m-%d"),
expense.concept,
str(expense.amount),
expense.category.name,
expense.subcategory.name,
]
)
return response