expenses_manager/expenses_manager/expenses/forms.py
JKuijperM 488e370a04 feat: redirección del check is_transfer al formulario de traspaso
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 08:52:13 +02:00

237 lines
7.1 KiB
Python

from django import forms
from .models import (
Expense,
Category,
Income,
Tag,
Account,
FuelEntry,
Goal,
Transfer,
)
def _hide_is_transfer_when_editing(form):
"""Quita la casilla de traspaso al editar un movimiento que ya existe.
Al crear, la casilla es un atajo: lleva al formulario de traspaso, que es
quien crea las dos patas. Al editar no significa nada, porque convertir un
gasto suelto en traspaso no es editarlo (habría que inventarse la otra
cuenta), y marcarla solo dejaría el movimiento marcado pero sin pareja: justo
el estado a medias que Transfer viene a evitar.
"""
if form.instance.pk:
form.fields.pop("is_transfer", None)
class ExpenseForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
# Get user
user = kwargs.pop("user", None)
super().__init__(*args, **kwargs)
if user:
self.fields["category"].queryset = Category.objects.filter(owner=user)
self.fields["tags"].queryset = Tag.objects.filter(owner=user)
self.fields["account"].queryset = Account.objects.filter(
owner=user, active=True
)
self.fields["date"].input_formats = ["%Y-%m-%d"]
_hide_is_transfer_when_editing(self)
class Meta:
model = Expense
fields = [
"date",
"amount",
"description",
"category",
"account",
"tags",
"is_transfer",
]
widgets = {
"date": forms.DateInput(format="%Y-%m-%d", attrs={"type": "date"}),
"tags": forms.CheckboxSelectMultiple(),
}
class TagForm(forms.ModelForm):
class Meta:
model = Tag
fields = ["name"]
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = ["name", "initial_balance", "active"]
class IncomeForm(forms.ModelForm):
class Meta:
model = Income
fields = ["account", "name", "amount", "date", "is_transfer"]
widgets = {"date": forms.DateInput(format="%Y-%m-%d", attrs={"type": "date"})}
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
super().__init__(*args, **kwargs)
self.fields["account"].queryset = Account.objects.filter(
owner=user,
active=True,
)
_hide_is_transfer_when_editing(self)
class TransferForm(forms.ModelForm):
"""Formulario del traspaso. Las patas no se editan aquí ni en ninguna parte:
las deriva Transfer.sync() de estos campos."""
class Meta:
model = Transfer
fields = ["date", "amount", "from_account", "to_account", "fee_amount"]
widgets = {"date": forms.DateInput(format="%Y-%m-%d", attrs={"type": "date"})}
labels = {
"from_account": "Cuenta de origen",
"to_account": "Cuenta de destino",
"fee_amount": "Comisión",
}
help_texts = {
"fee_amount": (
"Déjalo vacío si el traspaso no tiene comisión. La comisión sí "
"cuenta como gasto real en el dashboard y en los presupuestos."
),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
super().__init__(*args, **kwargs)
accounts = Account.objects.filter(owner=user, active=True)
self.fields["from_account"].queryset = accounts
self.fields["to_account"].queryset = accounts
self.fields["date"].input_formats = ["%Y-%m-%d"]
class FuelEntryForm(forms.ModelForm):
odometer = forms.DecimalField(label="Kilómetros actuales")
liters = forms.DecimalField(max_digits=8, decimal_places=2, label="Litros")
class Meta:
model = Expense
fields = ["date", "amount", "account"]
widgets = {
"date": forms.DateInput(format="%Y-%m-%d", attrs={"type":"date"}),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
super().__init__(*args, **kwargs)
if user:
self.fields["account"].queryset = user.accounts.filter(active=True)
if self.instance.pk and hasattr(self.instance, "fuel_data"):
fuel = self.instance.fuel_data
self.fields["odometer"].initial = fuel.odometer
self.fields["liters"].initial = fuel.liters
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ["name", "parent"]
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
super().__init__(*args, **kwargs)
if user:
queryset = Category.objects.filter(owner=user)
if self.instance.pk:
queryset = queryset.exclude(
pk__in=self.instance.descendant_ids()
)
self.fields["parent"].queryset = queryset
def clean_parent(self):
parent = self.cleaned_data.get("parent")
if parent and self.instance.pk:
if parent.pk in self.instance.descendant_ids():
raise forms.ValidationError(
"Una categoría no puede ser su propio padre ni depender "
"de una de sus subcategorías."
)
return parent
class GoalForm(forms.ModelForm):
class Meta:
model = Goal
fields = [
"name",
"kind",
"target_amount",
"category",
"include_subcategories",
"account",
"start_date",
"period",
"show_on_home",
]
widgets = {
"start_date": forms.DateInput(
format="%Y-%m-%d", attrs={"type": "date"}
),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['category'].queryset = user.categories.all()
self.fields['account'].queryset = user.accounts.filter(active=True)
self.fields['category'].required = False
self.fields['account'].required = False
self.fields['start_date'].input_formats = ["%Y-%m-%d"]
def clean(self):
cleaned = super().clean()
kind = cleaned.get("kind")
if kind in (Goal.KIND_PAYMENT, Goal.KIND_BUDGET):
if not cleaned.get('category'):
self.add_error(
"category",
"Selecciona una categoría para este tipo de objetivo.",
)
cleaned["account"] = None
if kind == Goal.KIND_SAVING:
if not cleaned.get("account"):
self.add_error(
"account",
"Selecciona la cuenta donde se acumula el ahorro.",
)
cleaned["category"] = None
if kind == Goal.KIND_BUDGET:
if cleaned.get("period") == Goal.PERIOD_NONE:
self.add_error(
"period",
"Un presupuesto necesita un periodo: mensual o anual",
)
else:
cleaned["period"] = Goal.PERIOD_NONE
return cleaned