- 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)
52 lines
1.2 KiB
Python
52 lines
1.2 KiB
Python
from django import forms
|
|
|
|
from .models import Expense
|
|
|
|
|
|
class ExpenseForm(forms.ModelForm):
|
|
amount = forms.CharField(
|
|
widget=forms.TextInput(
|
|
attrs={
|
|
"class": "input input-bordered w-full",
|
|
"placeholder": "0,00",
|
|
"inputmode": "decimal",
|
|
}
|
|
)
|
|
)
|
|
|
|
class Meta:
|
|
model = Expense
|
|
fields = ["concept", "amount", "category", "subcategory"]
|
|
widgets = {
|
|
"concept": forms.TextInput(
|
|
attrs={
|
|
"class": "input input-bordered w-full",
|
|
"placeholder": "Expense description",
|
|
}
|
|
),
|
|
"category": forms.Select(
|
|
attrs={
|
|
"class": "select select-bordered w-full",
|
|
"data-liveview-function": "update_subcategories",
|
|
"data-action": "change->page#run",
|
|
}
|
|
),
|
|
"subcategory": forms.Select(
|
|
attrs={
|
|
"class": "select select-bordered w-full",
|
|
}
|
|
),
|
|
}
|
|
|
|
def clean_amount(self):
|
|
from decimal import Decimal, InvalidOperation
|
|
|
|
value = self.cleaned_data["amount"].replace(",", ".")
|
|
try:
|
|
amount = Decimal(value)
|
|
except (InvalidOperation, ValueError):
|
|
raise forms.ValidationError("Enter a valid amount.")
|
|
if amount <= 0:
|
|
raise forms.ValidationError("Amount must be greater than zero.")
|
|
return amount
|