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:
58
app/stiftung/forms/__init__.py
Normal file
58
app/stiftung/forms/__init__.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from .destinataere import (DestinataerForm, DestinataerNotizForm,
|
||||
DestinataerUnterstuetzungForm,
|
||||
UnterstuetzungForm, UnterstuetzungMarkAsPaidForm,
|
||||
UnterstuetzungWiederkehrendForm,
|
||||
VierteljahresNachweisForm)
|
||||
from .dokumente import DokumentLinkForm
|
||||
from .finanzen import (BankImportForm, BankTransactionForm, RentmeisterForm,
|
||||
StiftungsKontoForm, VerwaltungskostenForm)
|
||||
from .foerderung import FoerderungForm
|
||||
from .geschichte import GeschichteBildForm, GeschichteSeiteForm
|
||||
from .land import LandAbrechnungForm, LandForm, LandVerpachtungForm, PaechterForm
|
||||
from .system import (BackupTokenRegenerateForm, PasswordChangeForm, PersonForm,
|
||||
TwoFactorDisableForm, TwoFactorSetupForm,
|
||||
TwoFactorVerifyForm, UserCreationForm, UserPermissionForm,
|
||||
UserUpdateForm)
|
||||
from .veranstaltung import VeranstaltungForm, VeranstaltungsteilnehmerForm
|
||||
|
||||
__all__ = [
|
||||
# destinataere
|
||||
"DestinataerForm",
|
||||
"DestinataerNotizForm",
|
||||
"DestinataerUnterstuetzungForm",
|
||||
"UnterstuetzungForm",
|
||||
"UnterstuetzungMarkAsPaidForm",
|
||||
"UnterstuetzungWiederkehrendForm",
|
||||
"VierteljahresNachweisForm",
|
||||
# dokumente
|
||||
"DokumentLinkForm",
|
||||
# finanzen
|
||||
"BankImportForm",
|
||||
"BankTransactionForm",
|
||||
"RentmeisterForm",
|
||||
"StiftungsKontoForm",
|
||||
"VerwaltungskostenForm",
|
||||
# foerderung
|
||||
"FoerderungForm",
|
||||
# geschichte
|
||||
"GeschichteBildForm",
|
||||
"GeschichteSeiteForm",
|
||||
# land
|
||||
"LandAbrechnungForm",
|
||||
"LandForm",
|
||||
"LandVerpachtungForm",
|
||||
"PaechterForm",
|
||||
# system
|
||||
"BackupTokenRegenerateForm",
|
||||
"PasswordChangeForm",
|
||||
"PersonForm",
|
||||
"TwoFactorDisableForm",
|
||||
"TwoFactorSetupForm",
|
||||
"TwoFactorVerifyForm",
|
||||
"UserCreationForm",
|
||||
"UserPermissionForm",
|
||||
"UserUpdateForm",
|
||||
# veranstaltung
|
||||
"VeranstaltungForm",
|
||||
"VeranstaltungsteilnehmerForm",
|
||||
]
|
||||
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
|
||||
19
app/stiftung/forms/dokumente.py
Normal file
19
app/stiftung/forms/dokumente.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django import forms
|
||||
|
||||
from ..models import DokumentLink
|
||||
|
||||
|
||||
class DokumentLinkForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Dokumentverknüpfungen"""
|
||||
|
||||
class Meta:
|
||||
model = DokumentLink
|
||||
fields = "__all__"
|
||||
widgets = {
|
||||
"paperless_id": forms.NumberInput(attrs={"class": "form-control"}),
|
||||
"content_type": forms.Select(attrs={"class": "form-select"}),
|
||||
"object_id": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"verknuepft_am": forms.DateTimeInput(
|
||||
attrs={"class": "form-control", "type": "datetime-local"}
|
||||
),
|
||||
}
|
||||
351
app/stiftung/forms/finanzen.py
Normal file
351
app/stiftung/forms/finanzen.py
Normal file
@@ -0,0 +1,351 @@
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from ..models import BankTransaction, Rentmeister, StiftungsKonto, Verwaltungskosten
|
||||
|
||||
|
||||
class RentmeisterForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Rentmeistern"""
|
||||
|
||||
class Meta:
|
||||
model = Rentmeister
|
||||
fields = [
|
||||
"anrede",
|
||||
"vorname",
|
||||
"nachname",
|
||||
"titel",
|
||||
"email",
|
||||
"telefon",
|
||||
"mobil",
|
||||
"strasse",
|
||||
"plz",
|
||||
"ort",
|
||||
"iban",
|
||||
"bic",
|
||||
"bank_name",
|
||||
"seit_datum",
|
||||
"bis_datum",
|
||||
"aktiv",
|
||||
"monatliche_verguetung",
|
||||
"km_pauschale",
|
||||
"notizen",
|
||||
]
|
||||
|
||||
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"}),
|
||||
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
||||
"telefon": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"mobil": 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"}),
|
||||
"iban": forms.TextInput(
|
||||
attrs={"class": "form-control", "placeholder": "DE89370400440532013000"}
|
||||
),
|
||||
"bic": forms.TextInput(
|
||||
attrs={"class": "form-control", "placeholder": "COBADEFFXXX"}
|
||||
),
|
||||
"bank_name": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"seit_datum": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"bis_datum": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"aktiv": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
"monatliche_verguetung": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"km_pauschale": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01", "value": "0.30"}
|
||||
),
|
||||
"notizen": forms.Textarea(attrs={"class": "form-control", "rows": 4}),
|
||||
}
|
||||
|
||||
labels = {
|
||||
"anrede": "Anrede",
|
||||
"vorname": "Vorname *",
|
||||
"nachname": "Nachname *",
|
||||
"titel": "Titel",
|
||||
"email": "E-Mail",
|
||||
"telefon": "Telefon",
|
||||
"mobil": "Mobil",
|
||||
"strasse": "Straße",
|
||||
"plz": "PLZ",
|
||||
"ort": "Ort",
|
||||
"iban": "IBAN",
|
||||
"bic": "BIC",
|
||||
"bank_name": "Bank",
|
||||
"seit_datum": "Rentmeister seit *",
|
||||
"bis_datum": "Rentmeister bis",
|
||||
"aktiv": "Aktiv",
|
||||
"monatliche_verguetung": "Monatliche Vergütung (€)",
|
||||
"km_pauschale": "Kilometerpauschale (€/km)",
|
||||
"notizen": "Notizen",
|
||||
}
|
||||
|
||||
help_texts = {
|
||||
"iban": "Internationale Bankkontonummer für Abrechnungen",
|
||||
"km_pauschale": "Standard: 0,30 € pro Kilometer",
|
||||
"seit_datum": "Datum des Amtsantritts als Rentmeister",
|
||||
"bis_datum": "Leer lassen für aktive Rentmeister",
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Markiere Pflichtfelder
|
||||
self.fields["vorname"].required = True
|
||||
self.fields["nachname"].required = True
|
||||
self.fields["seit_datum"].required = True
|
||||
|
||||
def clean_iban(self):
|
||||
"""Validierung der IBAN"""
|
||||
iban = self.cleaned_data.get("iban")
|
||||
if iban:
|
||||
# Entferne Leerzeichen und konvertiere zu Großbuchstaben
|
||||
iban = re.sub(r"\s+", "", iban.upper())
|
||||
|
||||
# Einfache IBAN-Längenvalidierung für deutsche IBANs
|
||||
if iban.startswith("DE") and len(iban) != 22:
|
||||
raise ValidationError("Deutsche IBANs müssen 22 Zeichen lang sein.")
|
||||
|
||||
# Speichere die bereinigte IBAN
|
||||
return iban
|
||||
return iban
|
||||
|
||||
def clean_plz(self):
|
||||
"""Validierung der PLZ"""
|
||||
plz = self.cleaned_data.get("plz")
|
||||
if plz and not re.match(r"^\d{5}$", plz):
|
||||
raise ValidationError("PLZ muss aus 5 Ziffern bestehen.")
|
||||
return plz
|
||||
|
||||
def clean(self):
|
||||
"""Übergreifende Validierung"""
|
||||
from django.utils.dateparse import parse_date
|
||||
|
||||
cleaned_data = super().clean()
|
||||
seit_datum = cleaned_data.get("seit_datum")
|
||||
bis_datum = cleaned_data.get("bis_datum")
|
||||
|
||||
# Helper function to ensure we have date objects
|
||||
def ensure_date(date_value):
|
||||
if not date_value:
|
||||
return None
|
||||
if isinstance(date_value, str):
|
||||
return parse_date(date_value)
|
||||
return date_value
|
||||
|
||||
# Convert to date objects if they're strings
|
||||
seit_datum = ensure_date(seit_datum)
|
||||
bis_datum = ensure_date(bis_datum)
|
||||
|
||||
# Prüfe Datum-Logik
|
||||
if seit_datum and bis_datum and bis_datum <= seit_datum:
|
||||
raise ValidationError("Das End-Datum muss nach dem Start-Datum liegen.")
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class StiftungsKontoForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Stiftungskonten"""
|
||||
|
||||
class Meta:
|
||||
model = StiftungsKonto
|
||||
fields = [
|
||||
"kontoname",
|
||||
"bank_name",
|
||||
"iban",
|
||||
"bic",
|
||||
"konto_typ",
|
||||
"saldo",
|
||||
"saldo_datum",
|
||||
"zinssatz",
|
||||
"laufzeit_bis",
|
||||
"aktiv",
|
||||
"notizen",
|
||||
]
|
||||
|
||||
widgets = {
|
||||
"kontoname": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"bank_name": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"iban": forms.TextInput(
|
||||
attrs={"class": "form-control", "placeholder": "DE89370400440532013000"}
|
||||
),
|
||||
"bic": forms.TextInput(
|
||||
attrs={"class": "form-control", "placeholder": "COBADEFFXXX"}
|
||||
),
|
||||
"konto_typ": forms.Select(attrs={"class": "form-select"}),
|
||||
"saldo": forms.NumberInput(attrs={"class": "form-control", "step": "0.01"}),
|
||||
"saldo_datum": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"zinssatz": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"laufzeit_bis": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"aktiv": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
"notizen": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
}
|
||||
|
||||
|
||||
class VerwaltungskostenForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Verwaltungskosten"""
|
||||
|
||||
class Meta:
|
||||
model = Verwaltungskosten
|
||||
fields = [
|
||||
"bezeichnung",
|
||||
"kategorie",
|
||||
"betrag",
|
||||
"datum",
|
||||
"status",
|
||||
"rentmeister",
|
||||
"zahlungskonto",
|
||||
"quellkonto",
|
||||
"lieferant_firma",
|
||||
"rechnungsnummer",
|
||||
"km_anzahl",
|
||||
"km_satz",
|
||||
"von_ort",
|
||||
"nach_ort",
|
||||
"zweck",
|
||||
"beschreibung",
|
||||
"notizen",
|
||||
]
|
||||
|
||||
widgets = {
|
||||
"bezeichnung": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"kategorie": forms.Select(attrs={"class": "form-select"}),
|
||||
"betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"datum": forms.DateInput(attrs={"class": "form-control", "type": "date"}),
|
||||
"status": forms.Select(attrs={"class": "form-select"}),
|
||||
"rentmeister": forms.Select(attrs={"class": "form-select"}),
|
||||
"zahlungskonto": forms.Select(attrs={"class": "form-select"}),
|
||||
"quellkonto": forms.Select(attrs={"class": "form-select"}),
|
||||
"lieferant_firma": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"rechnungsnummer": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"km_anzahl": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.1"}
|
||||
),
|
||||
"km_satz": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"von_ort": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"nach_ort": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"zweck": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"beschreibung": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
"notizen": forms.Textarea(attrs={"class": "form-control", "rows": 2}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Filtere nur aktive Rentmeister und Konten
|
||||
self.fields["rentmeister"].queryset = Rentmeister.objects.filter(aktiv=True)
|
||||
self.fields["zahlungskonto"].queryset = StiftungsKonto.objects.filter(
|
||||
aktiv=True
|
||||
)
|
||||
self.fields["quellkonto"].queryset = StiftungsKonto.objects.filter(aktiv=True)
|
||||
|
||||
# Standardwerte setzen
|
||||
if not self.instance.pk: # Nur bei neuen Objekten
|
||||
# Standard km_satz auf 0.30 Euro setzen
|
||||
self.fields["km_satz"].initial = 0.30
|
||||
|
||||
|
||||
class BankTransactionForm(forms.ModelForm):
|
||||
"""Form für das Bearbeiten von Banktransaktionen"""
|
||||
|
||||
class Meta:
|
||||
model = BankTransaction
|
||||
fields = [
|
||||
"konto",
|
||||
"datum",
|
||||
"valuta",
|
||||
"betrag",
|
||||
"waehrung",
|
||||
"verwendungszweck",
|
||||
"empfaenger_zahlungspflichtiger",
|
||||
"iban_gegenpartei",
|
||||
"bic_gegenpartei",
|
||||
"transaction_type",
|
||||
"status",
|
||||
"kommentare",
|
||||
"verwaltungskosten",
|
||||
]
|
||||
|
||||
widgets = {
|
||||
"konto": forms.Select(attrs={"class": "form-select"}),
|
||||
"datum": forms.DateInput(attrs={"class": "form-control", "type": "date"}),
|
||||
"valuta": forms.DateInput(attrs={"class": "form-control", "type": "date"}),
|
||||
"betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"waehrung": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"verwendungszweck": forms.Textarea(
|
||||
attrs={"class": "form-control", "rows": 3}
|
||||
),
|
||||
"empfaenger_zahlungspflichtiger": forms.TextInput(
|
||||
attrs={"class": "form-control"}
|
||||
),
|
||||
"iban_gegenpartei": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"bic_gegenpartei": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"transaction_type": forms.Select(attrs={"class": "form-select"}),
|
||||
"status": forms.Select(attrs={"class": "form-select"}),
|
||||
"kommentare": forms.Textarea(attrs={"class": "form-control", "rows": 4}),
|
||||
"verwaltungskosten": forms.Select(attrs={"class": "form-select"}),
|
||||
}
|
||||
|
||||
|
||||
class BankImportForm(forms.Form):
|
||||
"""Form für den Import von Bankdaten"""
|
||||
|
||||
konto = forms.ModelChoiceField(
|
||||
queryset=StiftungsKonto.objects.filter(aktiv=True),
|
||||
widget=forms.Select(attrs={"class": "form-select"}),
|
||||
label="Zielkonto",
|
||||
)
|
||||
|
||||
datei = forms.FileField(
|
||||
widget=forms.FileInput(attrs={"class": "form-control", "accept": ".csv,.txt"}),
|
||||
label="Bankdatei",
|
||||
help_text="Unterstützte Formate: CSV, TXT (Sparkasse, Volksbank, etc.)",
|
||||
)
|
||||
|
||||
encoding = forms.ChoiceField(
|
||||
choices=[
|
||||
("utf-8", "UTF-8"),
|
||||
("latin1", "Latin-1 / ISO-8859-1"),
|
||||
("cp1252", "Windows-1252"),
|
||||
],
|
||||
initial="utf-8",
|
||||
widget=forms.Select(attrs={"class": "form-select"}),
|
||||
label="Zeichenkodierung",
|
||||
)
|
||||
|
||||
delimiter = forms.ChoiceField(
|
||||
choices=[
|
||||
(";", "Semikolon (;)"),
|
||||
(",", "Komma (,)"),
|
||||
("\t", "Tab"),
|
||||
],
|
||||
initial=";",
|
||||
widget=forms.Select(attrs={"class": "form-select"}),
|
||||
label="Trennzeichen",
|
||||
)
|
||||
|
||||
skip_header = forms.BooleanField(
|
||||
initial=True,
|
||||
required=False,
|
||||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
label="Erste Zeile überspringen (Spaltenüberschriften)",
|
||||
)
|
||||
73
app/stiftung/forms/foerderung.py
Normal file
73
app/stiftung/forms/foerderung.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from django import forms
|
||||
|
||||
from ..models import Destinataer, DokumentLink, Foerderung
|
||||
|
||||
|
||||
class FoerderungForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Förderungen"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Add empty option for optional fields
|
||||
self.fields["verwendungsnachweis"].empty_label = (
|
||||
"--- Kein Dokument verknüpfen ---"
|
||||
)
|
||||
# Ensure destinataer has proper choices
|
||||
from django.utils import timezone
|
||||
|
||||
from ..models import Destinataer, DokumentLink
|
||||
|
||||
self.fields["destinataer"].queryset = Destinataer.objects.all().order_by(
|
||||
"nachname", "vorname"
|
||||
)
|
||||
self.fields["verwendungsnachweis"].queryset = (
|
||||
DokumentLink.objects.all().order_by("titel")
|
||||
)
|
||||
# Set current year as default for new forms
|
||||
if not self.instance.pk:
|
||||
self.fields["jahr"].initial = timezone.now().year
|
||||
|
||||
class Meta:
|
||||
model = Foerderung
|
||||
fields = [
|
||||
"destinataer",
|
||||
"jahr",
|
||||
"betrag",
|
||||
"kategorie",
|
||||
"status",
|
||||
"antragsdatum",
|
||||
"entscheidungsdatum",
|
||||
"verwendungsnachweis",
|
||||
"bemerkungen",
|
||||
]
|
||||
widgets = {
|
||||
"destinataer": forms.Select(attrs={"class": "form-select"}),
|
||||
"jahr": forms.NumberInput(attrs={"class": "form-control"}),
|
||||
"betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"kategorie": forms.Select(attrs={"class": "form-select"}),
|
||||
"status": forms.Select(attrs={"class": "form-select"}),
|
||||
"antragsdatum": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"entscheidungsdatum": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"verwendungsnachweis": forms.Select(attrs={"class": "form-select"}),
|
||||
"bemerkungen": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
}
|
||||
|
||||
labels = {
|
||||
"destinataer": "Destinatär",
|
||||
"verwendungsnachweis": "Verknüpftes Dokument",
|
||||
"bemerkungen": "Bemerkungen/Beschreibung",
|
||||
"antragsdatum": "Antragsdatum",
|
||||
"entscheidungsdatum": "Entscheidungsdatum",
|
||||
}
|
||||
|
||||
help_texts = {
|
||||
"verwendungsnachweis": "Optionale Verknüpfung zu einem Dokument aus dem Paperless-System",
|
||||
"entscheidungsdatum": "Datum der Bewilligung/Ablehnung (optional)",
|
||||
"bemerkungen": "Zusätzliche Informationen zur Förderung",
|
||||
}
|
||||
107
app/stiftung/forms/geschichte.py
Normal file
107
app/stiftung/forms/geschichte.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from django import forms
|
||||
|
||||
from ..models import GeschichteBild, GeschichteSeite
|
||||
|
||||
|
||||
class GeschichteSeiteForm(forms.ModelForm):
|
||||
"""Form for creating and editing history pages"""
|
||||
|
||||
class Meta:
|
||||
from ..models import GeschichteSeite
|
||||
model = GeschichteSeite
|
||||
fields = ['titel', 'slug', 'inhalt', 'ist_veroeffentlicht', 'sortierung']
|
||||
widgets = {
|
||||
'titel': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'z.B. Gründung der Stiftung'
|
||||
}),
|
||||
'slug': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'z.B. gruendung-der-stiftung'
|
||||
}),
|
||||
'inhalt': forms.Textarea(attrs={
|
||||
'class': 'form-control rich-text-editor',
|
||||
'rows': 20,
|
||||
'placeholder': 'Schreiben Sie hier den Inhalt der Geschichtsseite...'
|
||||
}),
|
||||
'ist_veroeffentlicht': forms.CheckboxInput(attrs={
|
||||
'class': 'form-check-input'
|
||||
}),
|
||||
'sortierung': forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'min': 0
|
||||
})
|
||||
}
|
||||
help_texts = {
|
||||
'slug': 'URL-freundliche Version des Titels (nur Buchstaben, Zahlen und Bindestriche)',
|
||||
'inhalt': 'Unterstützt Rich-Text-Formatierung, Bilder und Videos',
|
||||
'sortierung': 'Niedrigere Zahlen erscheinen zuerst in der Navigation'
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Auto-generate slug from title if not provided
|
||||
if not self.instance.pk:
|
||||
self.fields['slug'].required = False
|
||||
|
||||
def clean_slug(self):
|
||||
slug = self.cleaned_data.get('slug')
|
||||
titel = self.cleaned_data.get('titel', '')
|
||||
|
||||
if not slug and titel:
|
||||
# Auto-generate slug from title
|
||||
from django.utils.text import slugify
|
||||
slug = slugify(titel)
|
||||
|
||||
if not slug:
|
||||
raise forms.ValidationError('Slug ist erforderlich. Bitte geben Sie einen Titel ein.')
|
||||
|
||||
return slug
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
titel = cleaned_data.get('titel', '')
|
||||
slug = cleaned_data.get('slug', '')
|
||||
|
||||
# Auto-generate slug if empty
|
||||
if titel and not slug:
|
||||
from django.utils.text import slugify
|
||||
cleaned_data['slug'] = slugify(titel)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class GeschichteBildForm(forms.ModelForm):
|
||||
"""Form for uploading images to history pages"""
|
||||
|
||||
class Meta:
|
||||
from ..models import GeschichteBild
|
||||
model = GeschichteBild
|
||||
fields = ['titel', 'bild', 'beschreibung', 'alt_text', 'sortierung']
|
||||
widgets = {
|
||||
'titel': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'z.B. Gründungsurkunde 1895'
|
||||
}),
|
||||
'bild': forms.ClearableFileInput(attrs={
|
||||
'class': 'form-control'
|
||||
}),
|
||||
'beschreibung': forms.Textarea(attrs={
|
||||
'class': 'form-control',
|
||||
'rows': 3,
|
||||
'placeholder': 'Beschreibung des Bildes...'
|
||||
}),
|
||||
'alt_text': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Alternativtext für Bildschirmleser'
|
||||
}),
|
||||
'sortierung': forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'min': 0
|
||||
})
|
||||
}
|
||||
help_texts = {
|
||||
'bild': 'Unterstützte Formate: JPG, PNG, GIF (max. 10MB)',
|
||||
'alt_text': 'Wichtig für Barrierefreiheit',
|
||||
'sortierung': 'Reihenfolge in der Bildergalerie'
|
||||
}
|
||||
293
app/stiftung/forms/land.py
Normal file
293
app/stiftung/forms/land.py
Normal file
@@ -0,0 +1,293 @@
|
||||
from django import forms
|
||||
|
||||
from ..models import Land, LandAbrechnung, LandVerpachtung, Paechter
|
||||
|
||||
|
||||
class LandForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Ländern"""
|
||||
|
||||
class Meta:
|
||||
model = Land
|
||||
fields = [
|
||||
# Grundlegende Identifikation
|
||||
"lfd_nr",
|
||||
"ew_nummer",
|
||||
"grundbuchblatt",
|
||||
# Gerichtliche Zuständigkeit
|
||||
"amtsgericht",
|
||||
# Verwaltungsstruktur
|
||||
"gemeinde",
|
||||
"gemarkung",
|
||||
"flur",
|
||||
"flurstueck",
|
||||
"adresse",
|
||||
# Flächenangaben
|
||||
"groesse_qm",
|
||||
"gruenland_qm",
|
||||
"acker_qm",
|
||||
"wald_qm",
|
||||
"sonstiges_qm",
|
||||
# Legacy Verpachtung (für Kompatibilität)
|
||||
"verpachtete_gesamtflaeche",
|
||||
"flaeche_alte_liste",
|
||||
"verp_flaeche_aktuell",
|
||||
# Aktuelle Verpachtung
|
||||
"aktueller_paechter",
|
||||
"paechter_name",
|
||||
"paechter_anschrift",
|
||||
"pachtbeginn",
|
||||
"pachtende",
|
||||
"verlaengerung_klausel",
|
||||
"zahlungsweise",
|
||||
"pachtzins_pro_ha",
|
||||
"pachtzins_pauschal",
|
||||
# Umsatzsteuer
|
||||
"ust_option",
|
||||
"ust_satz",
|
||||
# Umlagen
|
||||
"grundsteuer_umlage",
|
||||
"versicherungen_umlage",
|
||||
"verbandsbeitraege_umlage",
|
||||
"jagdpacht_anteil_umlage",
|
||||
# Legacy Steuern
|
||||
"anteil_grundsteuer",
|
||||
"anteil_lwk",
|
||||
# Status
|
||||
"aktiv",
|
||||
"notizen",
|
||||
]
|
||||
widgets = {
|
||||
# Grundlegende Identifikation
|
||||
"lfd_nr": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"ew_nummer": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"grundbuchblatt": forms.TextInput(attrs={"class": "form-control"}),
|
||||
# Gerichtliche Zuständigkeit
|
||||
"amtsgericht": forms.TextInput(attrs={"class": "form-control"}),
|
||||
# Verwaltungsstruktur
|
||||
"gemeinde": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"gemarkung": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"flur": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"flurstueck": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"adresse": forms.TextInput(attrs={"class": "form-control"}),
|
||||
# Flächenangaben
|
||||
"groesse_qm": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"gruenland_qm": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"acker_qm": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"wald_qm": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"sonstiges_qm": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
# Legacy Verpachtung
|
||||
"verpachtete_gesamtflaeche": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"flaeche_alte_liste": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"verp_flaeche_aktuell": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
# Aktuelle Verpachtung
|
||||
"aktueller_paechter": forms.Select(attrs={"class": "form-select"}),
|
||||
"paechter_name": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"paechter_anschrift": forms.Textarea(
|
||||
attrs={"class": "form-control", "rows": 3}
|
||||
),
|
||||
"pachtbeginn": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"pachtende": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"verlaengerung_klausel": forms.CheckboxInput(
|
||||
attrs={"class": "form-check-input"}
|
||||
),
|
||||
"zahlungsweise": forms.Select(attrs={"class": "form-select"}),
|
||||
"pachtzins_pro_ha": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"pachtzins_pauschal": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
# Umsatzsteuer
|
||||
"ust_option": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
"ust_satz": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
# Umlagen
|
||||
"grundsteuer_umlage": forms.CheckboxInput(
|
||||
attrs={"class": "form-check-input"}
|
||||
),
|
||||
"versicherungen_umlage": forms.CheckboxInput(
|
||||
attrs={"class": "form-check-input"}
|
||||
),
|
||||
"verbandsbeitraege_umlage": forms.CheckboxInput(
|
||||
attrs={"class": "form-check-input"}
|
||||
),
|
||||
"jagdpacht_anteil_umlage": forms.CheckboxInput(
|
||||
attrs={"class": "form-check-input"}
|
||||
),
|
||||
# Legacy
|
||||
"anteil_grundsteuer": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"anteil_lwk": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
# Status
|
||||
"aktiv": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
"notizen": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
}
|
||||
|
||||
|
||||
class LandVerpachtungForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Verpachtungen"""
|
||||
|
||||
class Meta:
|
||||
model = LandVerpachtung
|
||||
fields = [
|
||||
'land',
|
||||
'paechter',
|
||||
'vertragsnummer',
|
||||
'pachtbeginn',
|
||||
'pachtende',
|
||||
'verlaengerung_klausel',
|
||||
'verpachtete_flaeche',
|
||||
'pachtzins_pauschal',
|
||||
'pachtzins_pro_ha',
|
||||
'zahlungsweise',
|
||||
'ust_option',
|
||||
'ust_satz',
|
||||
'grundsteuer_umlage',
|
||||
'versicherungen_umlage',
|
||||
'verbandsbeitraege_umlage',
|
||||
'jagdpacht_anteil_umlage',
|
||||
'status',
|
||||
'bemerkungen'
|
||||
]
|
||||
widgets = {
|
||||
'land': forms.Select(attrs={'class': 'form-select'}),
|
||||
'paechter': forms.Select(attrs={'class': 'form-select'}),
|
||||
'vertragsnummer': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'pachtbeginn': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
'pachtende': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
'verlaengerung_klausel': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'verpachtete_flaeche': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'pachtzins_pauschal': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'pachtzins_pro_ha': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'zahlungsweise': forms.Select(attrs={'class': 'form-select'}),
|
||||
'ust_option': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'ust_satz': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'grundsteuer_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'versicherungen_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'verbandsbeitraege_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'jagdpacht_anteil_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'status': forms.Select(attrs={'class': 'form-select'}),
|
||||
'bemerkungen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
}
|
||||
|
||||
|
||||
class LandAbrechnungForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Landabrechnungen"""
|
||||
|
||||
class Meta:
|
||||
model = LandAbrechnung
|
||||
fields = [
|
||||
"land",
|
||||
"abrechnungsjahr",
|
||||
# Einnahmen
|
||||
"pacht_vereinnahmt",
|
||||
"umlagen_vereinnahmt",
|
||||
"sonstige_einnahmen",
|
||||
# Ausgaben
|
||||
"grundsteuer_bescheid_nr",
|
||||
"grundsteuer_betrag",
|
||||
"versicherungen_betrag",
|
||||
"verbandsbeitraege_betrag",
|
||||
"sonstige_abgaben_betrag",
|
||||
"instandhaltung_betrag",
|
||||
"verwaltung_recht_betrag",
|
||||
# Umsatzsteuer
|
||||
"vorsteuer_aus_umlagen",
|
||||
# Sonstiges
|
||||
"offene_posten",
|
||||
"bemerkungen",
|
||||
# Dokumente werden über Paperless verknüpft, nicht hochgeladen
|
||||
]
|
||||
widgets = {
|
||||
"land": forms.Select(attrs={"class": "form-select"}),
|
||||
"abrechnungsjahr": forms.NumberInput(
|
||||
attrs={"class": "form-control", "min": "2000", "max": "2050"}
|
||||
),
|
||||
# Einnahmen
|
||||
"pacht_vereinnahmt": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"umlagen_vereinnahmt": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"sonstige_einnahmen": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
# Ausgaben
|
||||
"grundsteuer_bescheid_nr": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"grundsteuer_betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"versicherungen_betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"verbandsbeitraege_betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"sonstige_abgaben_betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"instandhaltung_betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"verwaltung_recht_betrag": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
# Umsatzsteuer
|
||||
"vorsteuer_aus_umlagen": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
# Sonstiges
|
||||
"offene_posten": forms.NumberInput(
|
||||
attrs={"class": "form-control", "step": "0.01"}
|
||||
),
|
||||
"bemerkungen": forms.Textarea(attrs={"class": "form-control", "rows": 4}),
|
||||
}
|
||||
|
||||
|
||||
class PaechterForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Pächtern"""
|
||||
|
||||
class Meta:
|
||||
model = Paechter
|
||||
fields = "__all__"
|
||||
widgets = {
|
||||
"anrede": forms.Select(attrs={"class": "form-select"}),
|
||||
"vorname": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"nachname": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
||||
"telefon": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"mobil": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"geburtsdatum": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"strasse": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"plz": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"ort": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"aktiv": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
"notizen": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
}
|
||||
460
app/stiftung/forms/system.py
Normal file
460
app/stiftung/forms/system.py
Normal file
@@ -0,0 +1,460 @@
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from ..models import Person
|
||||
|
||||
|
||||
class UserCreationForm(forms.Form):
|
||||
"""Form für die Erstellung neuer Benutzer"""
|
||||
|
||||
username = forms.CharField(
|
||||
label="Benutzername",
|
||||
max_length=150,
|
||||
help_text="Eindeutiger Benutzername für die Anmeldung",
|
||||
widget=forms.TextInput(attrs={"class": "form-control"}),
|
||||
)
|
||||
|
||||
email = forms.EmailField(
|
||||
label="E-Mail-Adresse",
|
||||
help_text="E-Mail-Adresse des Benutzers",
|
||||
widget=forms.EmailInput(attrs={"class": "form-control"}),
|
||||
)
|
||||
|
||||
first_name = forms.CharField(
|
||||
label="Vorname",
|
||||
max_length=30,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={"class": "form-control"}),
|
||||
)
|
||||
|
||||
last_name = forms.CharField(
|
||||
label="Nachname",
|
||||
max_length=150,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={"class": "form-control"}),
|
||||
)
|
||||
|
||||
password1 = forms.CharField(
|
||||
label="Passwort",
|
||||
widget=forms.PasswordInput(attrs={"class": "form-control"}),
|
||||
help_text="Mindestens 8 Zeichen",
|
||||
)
|
||||
|
||||
password2 = forms.CharField(
|
||||
label="Passwort bestätigen",
|
||||
widget=forms.PasswordInput(attrs={"class": "form-control"}),
|
||||
help_text="Geben Sie das Passwort zur Bestätigung erneut ein",
|
||||
)
|
||||
|
||||
is_active = forms.BooleanField(
|
||||
label="Aktiv",
|
||||
required=False,
|
||||
initial=True,
|
||||
help_text="Benutzer kann sich anmelden",
|
||||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
)
|
||||
|
||||
is_staff = forms.BooleanField(
|
||||
label="Staff-Status",
|
||||
required=False,
|
||||
help_text="Benutzer kann auf Django Admin zugreifen",
|
||||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
)
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data["username"]
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
if User.objects.filter(username=username).exists():
|
||||
raise forms.ValidationError(
|
||||
"Ein Benutzer mit diesem Namen existiert bereits."
|
||||
)
|
||||
return username
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data["email"]
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
if User.objects.filter(email=email).exists():
|
||||
raise forms.ValidationError(
|
||||
"Ein Benutzer mit dieser E-Mail-Adresse existiert bereits."
|
||||
)
|
||||
return email
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
password1 = cleaned_data.get("password1")
|
||||
password2 = cleaned_data.get("password2")
|
||||
|
||||
if password1 and password2:
|
||||
if password1 != password2:
|
||||
raise forms.ValidationError("Die Passwörter stimmen nicht überein.")
|
||||
if len(password1) < 8:
|
||||
raise forms.ValidationError(
|
||||
"Das Passwort muss mindestens 8 Zeichen lang sein."
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class UserUpdateForm(forms.ModelForm):
|
||||
"""Form für die Bearbeitung bestehender Benutzer"""
|
||||
|
||||
class Meta:
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
model = User
|
||||
fields = [
|
||||
"username",
|
||||
"email",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"is_active",
|
||||
"is_staff",
|
||||
]
|
||||
widgets = {
|
||||
"username": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
||||
"first_name": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"last_name": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
"is_staff": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
}
|
||||
labels = {
|
||||
"username": "Benutzername",
|
||||
"email": "E-Mail-Adresse",
|
||||
"first_name": "Vorname",
|
||||
"last_name": "Nachname",
|
||||
"is_active": "Aktiv",
|
||||
"is_staff": "Staff-Status",
|
||||
}
|
||||
help_texts = {
|
||||
"username": "Eindeutiger Benutzername für die Anmeldung",
|
||||
"email": "E-Mail-Adresse des Benutzers",
|
||||
"is_active": "Benutzer kann sich anmelden",
|
||||
"is_staff": "Benutzer kann auf Django Admin zugreifen",
|
||||
}
|
||||
|
||||
|
||||
class PasswordChangeForm(forms.Form):
|
||||
"""Form für Passwort-Änderungen"""
|
||||
|
||||
new_password1 = forms.CharField(
|
||||
label="Neues Passwort",
|
||||
widget=forms.PasswordInput(attrs={"class": "form-control"}),
|
||||
help_text="Mindestens 8 Zeichen",
|
||||
)
|
||||
|
||||
new_password2 = forms.CharField(
|
||||
label="Neues Passwort bestätigen",
|
||||
widget=forms.PasswordInput(attrs={"class": "form-control"}),
|
||||
help_text="Geben Sie das neue Passwort zur Bestätigung erneut ein",
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
password1 = cleaned_data.get("new_password1")
|
||||
password2 = cleaned_data.get("new_password2")
|
||||
|
||||
if password1 and password2:
|
||||
if password1 != password2:
|
||||
raise forms.ValidationError("Die Passwörter stimmen nicht überein.")
|
||||
if len(password1) < 8:
|
||||
raise forms.ValidationError(
|
||||
"Das Passwort muss mindestens 8 Zeichen lang sein."
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class UserPermissionForm(forms.Form):
|
||||
"""Form für die Zuweisung von Berechtigungen"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop("user", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
# Get all custom permissions for stiftung app
|
||||
app_permissions = Permission.objects.filter(
|
||||
content_type__app_label="stiftung"
|
||||
).order_by("name")
|
||||
|
||||
# Create checkbox fields for each permission
|
||||
for perm in app_permissions:
|
||||
field_name = f"perm_{perm.id}"
|
||||
self.fields[field_name] = forms.BooleanField(
|
||||
label=perm.name,
|
||||
required=False,
|
||||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
)
|
||||
|
||||
# Set initial values if user is provided
|
||||
if user:
|
||||
self.fields[field_name].initial = user.has_perm(
|
||||
f"stiftung.{perm.codename}"
|
||||
)
|
||||
|
||||
def get_permission_groups(self):
|
||||
"""Group permissions by functionality for template rendering"""
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
groups = {
|
||||
"entities": {
|
||||
"name": "Entitäten verwalten",
|
||||
"permissions": [],
|
||||
"icon": "fas fa-users",
|
||||
},
|
||||
"documents": {
|
||||
"name": "Dokumentenverwaltung",
|
||||
"permissions": [],
|
||||
"icon": "fas fa-folder-open",
|
||||
},
|
||||
"financial": {
|
||||
"name": "Finanzverwaltung",
|
||||
"permissions": [],
|
||||
"icon": "fas fa-euro-sign",
|
||||
},
|
||||
"administration": {
|
||||
"name": "Administration",
|
||||
"permissions": [],
|
||||
"icon": "fas fa-cogs",
|
||||
},
|
||||
"system": {"name": "System", "permissions": [], "icon": "fas fa-server"},
|
||||
}
|
||||
|
||||
# Get all permissions to properly categorize them
|
||||
for field_name, field in self.fields.items():
|
||||
if field_name.startswith("perm_"):
|
||||
# Extract permission ID from field name
|
||||
perm_id = field_name.replace("perm_", "")
|
||||
try:
|
||||
permission = Permission.objects.get(id=perm_id)
|
||||
label = permission.name.lower()
|
||||
codename = permission.codename.lower()
|
||||
|
||||
# Get bound field for proper template rendering
|
||||
bound_field = self[field_name]
|
||||
|
||||
# More precise categorization based on both name and codename
|
||||
if (
|
||||
any(
|
||||
word in codename
|
||||
for word in [
|
||||
"destinataer",
|
||||
"land",
|
||||
"paechter",
|
||||
"verpachtung",
|
||||
"foerderung",
|
||||
]
|
||||
)
|
||||
and "manage_" in codename
|
||||
or "view_" in codename
|
||||
):
|
||||
groups["entities"]["permissions"].append(
|
||||
(field_name, bound_field, permission)
|
||||
)
|
||||
elif (
|
||||
any(
|
||||
word in codename for word in ["documents", "link_documents"]
|
||||
)
|
||||
or "dokument" in label
|
||||
):
|
||||
groups["documents"]["permissions"].append(
|
||||
(field_name, bound_field, permission)
|
||||
)
|
||||
elif any(
|
||||
word in codename
|
||||
for word in [
|
||||
"verwaltungskosten",
|
||||
"konten",
|
||||
"rentmeister",
|
||||
"approve_payments",
|
||||
]
|
||||
) or any(
|
||||
word in label
|
||||
for word in [
|
||||
"verwaltungskosten",
|
||||
"konto",
|
||||
"rentmeister",
|
||||
"zahlung",
|
||||
]
|
||||
):
|
||||
groups["financial"]["permissions"].append(
|
||||
(field_name, bound_field, permission)
|
||||
)
|
||||
elif any(
|
||||
word in codename
|
||||
for word in [
|
||||
"administration",
|
||||
"audit",
|
||||
"backup",
|
||||
"manage_users",
|
||||
"manage_permissions",
|
||||
]
|
||||
) or any(
|
||||
word in label
|
||||
for word in [
|
||||
"administration",
|
||||
"audit",
|
||||
"backup",
|
||||
"benutzer",
|
||||
"berechtigung",
|
||||
]
|
||||
):
|
||||
groups["administration"]["permissions"].append(
|
||||
(field_name, bound_field, permission)
|
||||
)
|
||||
else:
|
||||
groups["system"]["permissions"].append(
|
||||
(field_name, bound_field, permission)
|
||||
)
|
||||
except Permission.DoesNotExist:
|
||||
# Create a fallback permission-like object with proper display
|
||||
class FallbackPermission:
|
||||
def __init__(self, field_name):
|
||||
self.name = field_name.replace('_', ' ').title()
|
||||
self.codename = field_name
|
||||
|
||||
fallback_perm = FallbackPermission(field_name)
|
||||
bound_field = self[field_name] # Get bound field for exception case too
|
||||
groups["system"]["permissions"].append((field_name, bound_field, fallback_perm))
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
class TwoFactorSetupForm(forms.Form):
|
||||
"""Form for setting up 2FA with TOTP verification"""
|
||||
token = forms.CharField(
|
||||
max_length=6,
|
||||
min_length=6,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'form-control text-center',
|
||||
'placeholder': '000000',
|
||||
'autocomplete': 'off',
|
||||
'pattern': '[0-9]{6}',
|
||||
'inputmode': 'numeric'
|
||||
}),
|
||||
label='Bestätigungscode',
|
||||
help_text='6-stelliger Code aus Ihrer Authenticator-App'
|
||||
)
|
||||
|
||||
def clean_token(self):
|
||||
token = self.cleaned_data.get('token')
|
||||
if token and not token.isdigit():
|
||||
raise ValidationError('Der Code darf nur Zahlen enthalten.')
|
||||
return token
|
||||
|
||||
|
||||
class TwoFactorVerifyForm(forms.Form):
|
||||
"""Form for verifying 2FA during login"""
|
||||
otp_token = forms.CharField(
|
||||
max_length=8,
|
||||
min_length=6,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'form-control form-control-lg text-center',
|
||||
'placeholder': '000000',
|
||||
'autocomplete': 'off',
|
||||
'autofocus': True
|
||||
}),
|
||||
label='Authentifizierungscode',
|
||||
help_text='6-stelliger Code aus der App oder 8-stelliger Backup-Code'
|
||||
)
|
||||
|
||||
def clean_otp_token(self):
|
||||
token = self.cleaned_data.get('otp_token')
|
||||
if token:
|
||||
token = token.strip().lower()
|
||||
# Allow 6-digit TOTP codes or 8-character backup codes
|
||||
if len(token) == 6 and token.isdigit():
|
||||
return token
|
||||
elif len(token) == 8 and re.match(r'^[a-f0-9]{8}$', token):
|
||||
return token
|
||||
else:
|
||||
raise ValidationError(
|
||||
'Bitte geben Sie einen 6-stelligen Authenticator-Code oder 8-stelligen Backup-Code ein.'
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
class TwoFactorDisableForm(forms.Form):
|
||||
"""Form for disabling 2FA with password confirmation"""
|
||||
password = forms.CharField(
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'class': 'form-control',
|
||||
'autocomplete': 'current-password',
|
||||
'autofocus': True
|
||||
}),
|
||||
label='Passwort',
|
||||
help_text='Geben Sie Ihr aktuelles Passwort zur Bestätigung ein'
|
||||
)
|
||||
|
||||
|
||||
class BackupTokenRegenerateForm(forms.Form):
|
||||
"""Form for regenerating backup tokens"""
|
||||
password = forms.CharField(
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'class': 'form-control',
|
||||
'autocomplete': 'current-password'
|
||||
}),
|
||||
label='Passwort',
|
||||
help_text='Geben Sie Ihr Passwort ein, um neue Backup-Codes zu generieren'
|
||||
)
|
||||
|
||||
|
||||
class PersonForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Personen (Legacy)"""
|
||||
|
||||
class Meta:
|
||||
model = Person
|
||||
fields = [
|
||||
"familienzweig",
|
||||
"vorname",
|
||||
"nachname",
|
||||
"geburtsdatum",
|
||||
"email",
|
||||
"telefon",
|
||||
"iban",
|
||||
"adresse",
|
||||
"notizen",
|
||||
"aktiv",
|
||||
]
|
||||
|
||||
widgets = {
|
||||
"familienzweig": forms.Select(attrs={"class": "form-select"}),
|
||||
"vorname": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"nachname": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"geburtsdatum": forms.DateInput(
|
||||
attrs={"class": "form-control", "type": "date"}
|
||||
),
|
||||
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
||||
"telefon": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"iban": forms.TextInput(
|
||||
attrs={"class": "form-control", "placeholder": "DE89370400440532013000"}
|
||||
),
|
||||
"adresse": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
"notizen": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
"aktiv": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
}
|
||||
|
||||
labels = {
|
||||
"familienzweig": "Familienzweig",
|
||||
"vorname": "Vorname *",
|
||||
"nachname": "Nachname *",
|
||||
"geburtsdatum": "Geburtsdatum",
|
||||
"email": "E-Mail",
|
||||
"telefon": "Telefon",
|
||||
"iban": "IBAN",
|
||||
"adresse": "Adresse",
|
||||
"notizen": "Notizen",
|
||||
"aktiv": "Aktiv",
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Markiere Pflichtfelder
|
||||
self.fields["vorname"].required = True
|
||||
self.fields["nachname"].required = True
|
||||
59
app/stiftung/forms/veranstaltung.py
Normal file
59
app/stiftung/forms/veranstaltung.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from django import forms
|
||||
|
||||
from ..models import Veranstaltung, Veranstaltungsteilnehmer
|
||||
|
||||
|
||||
class VeranstaltungForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Veranstaltungen inkl. Serienbrief-Felder"""
|
||||
|
||||
class Meta:
|
||||
model = Veranstaltung
|
||||
fields = [
|
||||
"titel", "datum", "uhrzeit", "ort", "adresse",
|
||||
"beschreibung", "status", "budget_pro_person",
|
||||
"betreff", "briefvorlage",
|
||||
"unterschrift_1_name", "unterschrift_1_titel",
|
||||
"unterschrift_2_name", "unterschrift_2_titel",
|
||||
]
|
||||
widgets = {
|
||||
"titel": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"datum": forms.DateInput(attrs={"class": "form-control", "type": "date"}),
|
||||
"uhrzeit": forms.TimeInput(attrs={"class": "form-control", "type": "time"}),
|
||||
"ort": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"adresse": forms.Textarea(attrs={"class": "form-control", "rows": 2}),
|
||||
"beschreibung": forms.Textarea(attrs={"class": "form-control", "rows": 3}),
|
||||
"status": forms.Select(attrs={"class": "form-select"}),
|
||||
"budget_pro_person": forms.NumberInput(attrs={"class": "form-control", "step": "0.01"}),
|
||||
"betreff": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"briefvorlage": forms.Textarea(attrs={"class": "form-control", "rows": 12}),
|
||||
"unterschrift_1_name": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"unterschrift_1_titel": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"unterschrift_2_name": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"unterschrift_2_titel": forms.TextInput(attrs={"class": "form-control"}),
|
||||
}
|
||||
|
||||
|
||||
class VeranstaltungsteilnehmerForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Veranstaltungsteilnehmern"""
|
||||
|
||||
class Meta:
|
||||
model = Veranstaltungsteilnehmer
|
||||
fields = [
|
||||
"anrede", "vorname", "nachname",
|
||||
"strasse", "plz", "ort", "email",
|
||||
"rsvp_status", "bemerkungen",
|
||||
"paechter", "destinataer",
|
||||
]
|
||||
widgets = {
|
||||
"anrede": forms.Select(attrs={"class": "form-select"}),
|
||||
"vorname": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"nachname": 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"}),
|
||||
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
||||
"rsvp_status": forms.Select(attrs={"class": "form-select"}),
|
||||
"bemerkungen": forms.Textarea(attrs={"class": "form-control", "rows": 2}),
|
||||
"paechter": forms.Select(attrs={"class": "form-select"}),
|
||||
"destinataer": forms.Select(attrs={"class": "form-select"}),
|
||||
}
|
||||
Reference in New Issue
Block a user