Phase 0: forms.py, admin.py und views.py in Domain-Packages aufteilen
- forms.py → forms/ Package (8 Domänen: destinataere, land, finanzen, foerderung, dokumente, veranstaltung, system, geschichte) - admin.py → admin/ Package (7 Domänen, alle 22 @admin.register dekoriert) - views.py (8845 Zeilen) → views/ Package (10 Domänen: dashboard, destinataere, land, paechter, finanzen, foerderung, dokumente, unterstuetzungen, veranstaltung, geschichte, system) - __init__.py in jedem Package re-exportiert alle Symbole für Rückwärtskompatibilität - urls.py bleibt unverändert (funktioniert durch Re-Exports) - Django system check: 0 Fehler, alle URL-Auflösungen funktionieren Keine funktionalen Änderungen – reine Strukturverbesserung für Vision 2026. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
428
app/stiftung/forms/destinataere.py
Normal file
428
app/stiftung/forms/destinataere.py
Normal file
@@ -0,0 +1,428 @@
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
|
||||
from ..models import (Destinataer, DestinataerNotiz, DestinataerUnterstuetzung,
|
||||
UnterstuetzungWiederkehrend, VierteljahresNachweis)
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
class DestinataerForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Destinatären"""
|
||||
|
||||
class Meta:
|
||||
model = Destinataer
|
||||
fields = "__all__"
|
||||
widgets = {
|
||||
"anrede": forms.Select(attrs={"class": "form-select"}),
|
||||
"vorname": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"nachname": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"titel": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"strasse": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"plz": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"ort": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"telefon": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"mobil": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
||||
"aktiv": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
"notizen": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
"ist_abkoemmling": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
"haushaltsgroesse": forms.NumberInput(
|
||||
attrs={"class": "form-control", "min": 1}
|
||||
),
|
||||
"vermoegen": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"unterstuetzung_bestaetigt": forms.CheckboxInput(
|
||||
attrs={"class": "form-check-input"}
|
||||
),
|
||||
"standard_konto": forms.Select(attrs={"class": "form-select"}, choices=[(None, "---")] + [(c.pk, str(c)) for c in getattr(Destinataer, 'konten_queryset', lambda: [])()]),
|
||||
"vierteljaehrlicher_betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"studiennachweis_erforderlich": forms.CheckboxInput(
|
||||
attrs={"class": "form-check-input"}
|
||||
),
|
||||
"letzter_studiennachweis": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"familienzweig": forms.Select(attrs={"class": "form-select"}),
|
||||
"berufsgruppe": forms.Select(attrs={"class": "form-select"}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for field_name, field in self.fields.items():
|
||||
if field_name not in ["vorname", "nachname"]:
|
||||
field.required = False
|
||||
# Set choices for familienzweig and berufsgruppe to match model
|
||||
self.fields["familienzweig"].choices = [("", "Bitte wählen...")] + list(Destinataer.FAMILIENZWIG_CHOICES)
|
||||
self.fields["berufsgruppe"].choices = [("", "Bitte wählen...")] + list(Destinataer.BERUFSGRUPPE_CHOICES)
|
||||
# Set choices for standard_konto to allow blank
|
||||
self.fields["standard_konto"].empty_label = "---"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for field_name, field in self.fields.items():
|
||||
if field_name not in ["vorname", "nachname"]:
|
||||
field.required = False
|
||||
|
||||
|
||||
class DestinataerUnterstuetzungForm(forms.ModelForm):
|
||||
"""Form für geplante/ausgeführte Destinatärunterstützungen"""
|
||||
|
||||
class Meta:
|
||||
model = DestinataerUnterstuetzung
|
||||
fields = [
|
||||
"destinataer",
|
||||
"konto",
|
||||
"betrag",
|
||||
"faellig_am",
|
||||
"status",
|
||||
"beschreibung",
|
||||
"empfaenger_iban",
|
||||
"empfaenger_name",
|
||||
"verwendungszweck",
|
||||
]
|
||||
widgets = {
|
||||
"destinataer": forms.Select(attrs={"class": "form-select"}),
|
||||
"konto": forms.Select(attrs={"class": "form-select"}),
|
||||
"betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"faellig_am": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"status": forms.Select(attrs={"class": "form-select"}),
|
||||
"beschreibung": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"empfaenger_iban": forms.TextInput(
|
||||
attrs={"class": "form-control", "placeholder": "DE89 3704 0044 0532 0130 00"}
|
||||
),
|
||||
"empfaenger_name": forms.TextInput(
|
||||
attrs={"class": "form-control", "placeholder": "Max Mustermann"}
|
||||
),
|
||||
"verwendungszweck": forms.TextInput(
|
||||
attrs={"class": "form-control", "placeholder": "Vierteljährliche Unterstützung Q1/2025"}
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Make faellig_am read-only for automatically generated quarterly payments
|
||||
self.is_auto_generated = False
|
||||
if self.instance and self.instance.pk and self.instance.beschreibung:
|
||||
if "Vierteljährliche Unterstützung" in self.instance.beschreibung and "(automatisch erstellt)" in self.instance.beschreibung:
|
||||
self.is_auto_generated = True
|
||||
|
||||
# Use a TextInput widget with readonly attribute to display the date
|
||||
from django import forms
|
||||
current_date = self.instance.faellig_am
|
||||
if current_date:
|
||||
self.fields['faellig_am'].widget = forms.TextInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"readonly": True,
|
||||
"value": current_date.strftime('%d.%m.%Y'), # German date format
|
||||
"style": "background-color: #f8f9fa; cursor: not-allowed;"
|
||||
}
|
||||
)
|
||||
self.fields['faellig_am'].initial = current_date
|
||||
|
||||
self.fields['faellig_am'].help_text = "Fälligkeitsdatum wird automatisch basierend auf Quartal berechnet"
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
# For auto-generated payments, preserve the original due date
|
||||
if self.is_auto_generated and self.instance and self.instance.pk:
|
||||
cleaned_data['faellig_am'] = self.instance.faellig_am
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class DestinataerNotizForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = DestinataerNotiz
|
||||
fields = ["titel", "text", "datei"]
|
||||
widgets = {
|
||||
"titel": forms.TextInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "z.B. Telefonat vom 29.08.2025",
|
||||
}
|
||||
),
|
||||
"text": forms.Textarea(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"rows": 5,
|
||||
"placeholder": "Notiztext...",
|
||||
}
|
||||
),
|
||||
"datei": forms.ClearableFileInput(attrs={"class": "form-control"}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Make all fields optional
|
||||
self.fields["datei"].required = False
|
||||
self.fields["titel"].required = False
|
||||
self.fields["text"].required = False
|
||||
|
||||
def clean(self):
|
||||
cleaned = super().clean()
|
||||
titel = cleaned.get("titel", "").strip()
|
||||
text = cleaned.get("text", "").strip()
|
||||
if not (titel or text):
|
||||
raise forms.ValidationError(
|
||||
"Bitte geben Sie einen Titel oder einen Text ein."
|
||||
)
|
||||
return cleaned
|
||||
|
||||
|
||||
class UnterstuetzungForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Unterstützungen"""
|
||||
|
||||
# Special field for creating recurring payments
|
||||
ist_wiederkehrend = forms.BooleanField(
|
||||
required=False,
|
||||
label="Wiederkehrende Zahlung",
|
||||
help_text="Aktivieren Sie diese Option um automatisch wiederkehrende Zahlungen zu erstellen",
|
||||
)
|
||||
intervall = forms.ChoiceField(
|
||||
choices=[("", "--- Wählen Sie ein Intervall ---")]
|
||||
+ UnterstuetzungWiederkehrend.INTERVALL_CHOICES,
|
||||
required=False,
|
||||
widget=forms.Select(attrs={"class": "form-select"}),
|
||||
label="Zahlungsintervall",
|
||||
)
|
||||
letzte_zahlung_am = forms.DateField(
|
||||
required=False,
|
||||
widget=forms.DateInput(attrs={"class": "form-control", "type": "date"}),
|
||||
label="Letzte Zahlung am (optional)",
|
||||
help_text="Leer lassen für unbegrenzte Wiederholung",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = DestinataerUnterstuetzung
|
||||
fields = [
|
||||
"destinataer",
|
||||
"konto",
|
||||
"faellig_am",
|
||||
"betrag",
|
||||
"status",
|
||||
"beschreibung",
|
||||
"empfaenger_iban",
|
||||
"empfaenger_name",
|
||||
"verwendungszweck",
|
||||
]
|
||||
|
||||
widgets = {
|
||||
"destinataer": forms.Select(attrs={"class": "form-select"}),
|
||||
"konto": forms.Select(attrs={"class": "form-select"}),
|
||||
"faellig_am": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"status": forms.Select(attrs={"class": "form-select"}),
|
||||
"beschreibung": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"empfaenger_iban": forms.TextInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "DE89 3704 0044 0532 0130 00",
|
||||
}
|
||||
),
|
||||
"empfaenger_name": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"verwendungszweck": forms.TextInput(
|
||||
attrs={"class": "form-control", "maxlength": "140"}
|
||||
),
|
||||
}
|
||||
|
||||
labels = {
|
||||
"destinataer": "Destinatär",
|
||||
"konto": "Zahlungskonto",
|
||||
"faellig_am": "Fällig am",
|
||||
"betrag": "Betrag (€)",
|
||||
"status": "Status",
|
||||
"beschreibung": "Beschreibung",
|
||||
"empfaenger_iban": "Empfänger IBAN",
|
||||
"empfaenger_name": "Empfänger Name",
|
||||
"verwendungszweck": "Verwendungszweck",
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Add onchange event to destinataer field for AJAX IBAN fetching
|
||||
self.fields["destinataer"].widget.attrs["onchange"] = "updateDestinataerInfo()"
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
ist_wiederkehrend = cleaned_data.get("ist_wiederkehrend")
|
||||
intervall = cleaned_data.get("intervall")
|
||||
|
||||
if ist_wiederkehrend and not intervall:
|
||||
raise forms.ValidationError(
|
||||
"Bitte wählen Sie ein Zahlungsintervall für wiederkehrende Zahlungen."
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class UnterstuetzungWiederkehrendForm(forms.ModelForm):
|
||||
"""Form für das Bearbeiten von wiederkehrenden Unterstützungsvorlagen"""
|
||||
|
||||
class Meta:
|
||||
model = UnterstuetzungWiederkehrend
|
||||
fields = [
|
||||
"destinataer",
|
||||
"konto",
|
||||
"betrag",
|
||||
"intervall",
|
||||
"beschreibung",
|
||||
"empfaenger_iban",
|
||||
"empfaenger_name",
|
||||
"verwendungszweck",
|
||||
"erste_zahlung_am",
|
||||
"letzte_zahlung_am",
|
||||
"aktiv",
|
||||
]
|
||||
|
||||
widgets = {
|
||||
"destinataer": forms.Select(attrs={"class": "form-select"}),
|
||||
"konto": forms.Select(attrs={"class": "form-select"}),
|
||||
"betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"intervall": forms.Select(attrs={"class": "form-select"}),
|
||||
"beschreibung": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"empfaenger_iban": forms.TextInput(
|
||||
attrs={
|
||||
"class": "form-control",
|
||||
"placeholder": "DE89 3704 0044 0532 0130 00",
|
||||
}
|
||||
),
|
||||
"empfaenger_name": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"verwendungszweck": forms.TextInput(
|
||||
attrs={"class": "form-control", "maxlength": "140"}
|
||||
),
|
||||
"erste_zahlung_am": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"letzte_zahlung_am": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"aktiv": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
}
|
||||
|
||||
|
||||
class UnterstuetzungMarkAsPaidForm(forms.Form):
|
||||
"""Simple form to mark an Unterstützung as paid"""
|
||||
|
||||
ausgezahlt_am = forms.DateField(
|
||||
widget=forms.DateInput(attrs={"class": "form-control", "type": "date"}),
|
||||
label="Ausgezahlt am",
|
||||
initial=timezone.now().date(),
|
||||
)
|
||||
|
||||
bemerkung = forms.CharField(
|
||||
widget=forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
label="Bemerkung (optional)",
|
||||
required=False,
|
||||
help_text="Optionale Notiz zur Zahlung",
|
||||
)
|
||||
|
||||
|
||||
class VierteljahresNachweisForm(forms.ModelForm):
|
||||
"""Form for quarterly confirmations (Vierteljahresnachweise)"""
|
||||
|
||||
class Meta:
|
||||
model = VierteljahresNachweis
|
||||
fields = [
|
||||
'studiennachweis_eingereicht',
|
||||
'studiennachweis_datei',
|
||||
'studiennachweis_bemerkung',
|
||||
'einkommenssituation_bestaetigt',
|
||||
'einkommenssituation_text',
|
||||
'einkommenssituation_datei',
|
||||
'vermogenssituation_bestaetigt',
|
||||
'vermogenssituation_text',
|
||||
'vermogenssituation_datei',
|
||||
'weitere_dokumente',
|
||||
'weitere_dokumente_beschreibung',
|
||||
'interne_notizen',
|
||||
]
|
||||
|
||||
widgets = {
|
||||
'studiennachweis_eingereicht': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'studiennachweis_datei': forms.FileInput(attrs={'class': 'form-control', 'accept': '.pdf,.jpg,.jpeg,.png,.doc,.docx'}),
|
||||
'studiennachweis_bemerkung': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
'einkommenssituation_bestaetigt': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'einkommenssituation_text': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Z.B. "Keine Änderungen seit letzter Meldung"'}),
|
||||
'einkommenssituation_datei': forms.FileInput(attrs={'class': 'form-control', 'accept': '.pdf,.jpg,.jpeg,.png,.doc,.docx'}),
|
||||
'vermogenssituation_bestaetigt': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'vermogenssituation_text': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Z.B. "Keine Änderungen seit letzter Meldung"'}),
|
||||
'vermogenssituation_datei': forms.FileInput(attrs={'class': 'form-control', 'accept': '.pdf,.jpg,.jpeg,.png,.doc,.docx'}),
|
||||
'weitere_dokumente': forms.FileInput(attrs={'class': 'form-control', 'accept': '.pdf,.jpg,.jpeg,.png,.doc,.docx'}),
|
||||
'weitere_dokumente_beschreibung': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
|
||||
'interne_notizen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
}
|
||||
|
||||
labels = {
|
||||
'studiennachweis_erforderlich': 'Studiennachweis erforderlich',
|
||||
'studiennachweis_eingereicht': 'Studiennachweis eingereicht',
|
||||
'studiennachweis_datei': 'Studiennachweis (Datei)',
|
||||
'studiennachweis_bemerkung': 'Bemerkung zum Studiennachweis',
|
||||
'einkommenssituation_bestaetigt': 'Einkommenssituation bestätigt',
|
||||
'einkommenssituation_text': 'Einkommenssituation (Text)',
|
||||
'einkommenssituation_datei': 'Einkommenssituation (Datei)',
|
||||
'vermogenssituation_bestaetigt': 'Vermögenssituation bestätigt',
|
||||
'vermogenssituation_text': 'Vermögenssituation (Text)',
|
||||
'vermogenssituation_datei': 'Vermögenssituation (Datei)',
|
||||
'weitere_dokumente': 'Weitere Dokumente',
|
||||
'weitere_dokumente_beschreibung': 'Beschreibung weitere Dokumente',
|
||||
'interne_notizen': 'Interne Notizen (nur für Verwaltung)',
|
||||
}
|
||||
|
||||
help_texts = {
|
||||
'einkommenssituation_text': 'Z.B. "Keine Änderungen seit letzter Meldung" oder Details zu Änderungen',
|
||||
'vermogenssituation_text': 'Z.B. "Keine Änderungen seit letzter Meldung" oder Details zu Änderungen',
|
||||
'interne_notizen': 'Diese Notizen sind nur für die interne Verwaltung sichtbar',
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
# Validate that at least one form of confirmation is provided for income situation
|
||||
einkommenssituation_text = cleaned_data.get('einkommenssituation_text')
|
||||
einkommenssituation_datei = cleaned_data.get('einkommenssituation_datei')
|
||||
einkommenssituation_bestaetigt = cleaned_data.get('einkommenssituation_bestaetigt')
|
||||
|
||||
if einkommenssituation_bestaetigt and not einkommenssituation_text and not einkommenssituation_datei:
|
||||
raise ValidationError(
|
||||
'Wenn die Einkommenssituation bestätigt wird, muss entweder ein Text oder eine Datei angegeben werden.'
|
||||
)
|
||||
|
||||
# Validate that at least one form of confirmation is provided for asset situation
|
||||
vermogenssituation_text = cleaned_data.get('vermogenssituation_text')
|
||||
vermogenssituation_datei = cleaned_data.get('vermogenssituation_datei')
|
||||
vermogenssituation_bestaetigt = cleaned_data.get('vermogenssituation_bestaetigt')
|
||||
|
||||
if vermogenssituation_bestaetigt and not vermogenssituation_text and not vermogenssituation_datei:
|
||||
raise ValidationError(
|
||||
'Wenn die Vermögenssituation bestätigt wird, muss entweder ein Text oder eine Datei angegeben werden.'
|
||||
)
|
||||
|
||||
# Validate study proof if required and marked as submitted
|
||||
studiennachweis_erforderlich = cleaned_data.get('studiennachweis_erforderlich')
|
||||
studiennachweis_eingereicht = cleaned_data.get('studiennachweis_eingereicht')
|
||||
studiennachweis_datei = cleaned_data.get('studiennachweis_datei')
|
||||
studiennachweis_bemerkung = cleaned_data.get('studiennachweis_bemerkung')
|
||||
|
||||
if studiennachweis_erforderlich and studiennachweis_eingereicht:
|
||||
if not studiennachweis_datei and not studiennachweis_bemerkung:
|
||||
raise ValidationError(
|
||||
'Wenn der Studiennachweis als eingereicht markiert wird, muss entweder eine Datei oder eine Bemerkung angegeben werden.'
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
Reference in New Issue
Block a user