- Add LandVerpachtung model with Land and Paechter relationships - Implement full CRUD operations for Verpachtungen - Add responsive Bootstrap templates with JavaScript calculations - Integrate document linking functionality similar to other entities - Add navigation links and URL patterns - Include CSV import support for Paechter data - Fix template encoding issues for proper UTF-8 support - Enhance administration interface with Verpachtung CSV import This implements the complete Verpachtung management feature requested, allowing users to manage land lease agreements with proper relationships to properties (Laenderei) and tenants (Paechter), following the same patterns as Foerderung/Destinataer relationships.
1366 lines
50 KiB
Python
1366 lines
50 KiB
Python
import re
|
|
|
|
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils import timezone
|
|
|
|
from .models import (BankTransaction, Destinataer, DestinataerNotiz,
|
|
DestinataerUnterstuetzung, DokumentLink, Foerderung, Land,
|
|
LandAbrechnung, LandVerpachtung, Paechter, Person, Rentmeister,
|
|
StiftungsKonto, UnterstuetzungWiederkehrend,
|
|
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 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
|
|
|
|
|
|
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}),
|
|
}
|
|
|
|
|
|
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}
|
|
),
|
|
# renamed in UI: use vierteljaehrlicher_betrag field
|
|
"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"}),
|
|
"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"}
|
|
),
|
|
}
|
|
|
|
|
|
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 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"}
|
|
),
|
|
}
|
|
|
|
|
|
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",
|
|
}
|
|
|
|
|
|
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 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 DestinataerUnterstuetzungForm(forms.ModelForm):
|
|
"""Form für geplante/ausgeführte Destinatärunterstützungen"""
|
|
|
|
class Meta:
|
|
model = DestinataerUnterstuetzung
|
|
fields = [
|
|
"destinataer",
|
|
"konto",
|
|
"betrag",
|
|
"faellig_am",
|
|
"status",
|
|
"beschreibung",
|
|
]
|
|
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"}),
|
|
}
|
|
|
|
|
|
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 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)",
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# USER MANAGEMENT FORMS
|
|
# =============================================================================
|
|
|
|
|
|
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()
|
|
|
|
# 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, field, permission)
|
|
)
|
|
elif (
|
|
any(
|
|
word in codename for word in ["documents", "link_documents"]
|
|
)
|
|
or "dokument" in label
|
|
):
|
|
groups["documents"]["permissions"].append(
|
|
(field_name, 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, 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, field, permission)
|
|
)
|
|
else:
|
|
groups["system"]["permissions"].append(
|
|
(field_name, field, permission)
|
|
)
|
|
except Permission.DoesNotExist:
|
|
# Fallback for permissions that don't exist
|
|
groups["system"]["permissions"].append((field_name, field, None))
|
|
|
|
return groups
|