from django.contrib import admin from django.db.models import Count, Sum from django.urls import reverse from django.utils.html import format_html from django.utils.safestring import mark_safe from . import models from .models import (AppConfiguration, AuditLog, BackupJob, BankTransaction, CSVImport, Destinataer, DestinataerUnterstuetzung, DokumentLink, Foerderung, Land, LandVerpachtung, Paechter, Person, Rentmeister, StiftungsKonto, UnterstuetzungWiederkehrend, Verwaltungskosten) @admin.register(CSVImport) class CSVImportAdmin(admin.ModelAdmin): list_display = [ "import_type", "filename", "status", "total_rows", "imported_rows", "failed_rows", "created_by", "started_at", "duration_display", ] list_filter = ["import_type", "status", "started_at"] search_fields = ["filename", "created_by"] readonly_fields = ["id", "started_at", "completed_at", "get_success_rate"] ordering = ["-started_at"] fieldsets = ( ( "Grundinformationen", {"fields": ("import_type", "filename", "file_size", "status")}, ), ( "Ergebnisse", { "fields": ( "total_rows", "imported_rows", "failed_rows", "get_success_rate", "error_log", ) }, ), ("Metadaten", {"fields": ("created_by", "started_at", "completed_at")}), ) def duration_display(self, obj): duration = obj.get_duration() if duration: return f"{duration.total_seconds():.1f}s" return "-" duration_display.short_description = "Dauer" def get_success_rate(self, obj): rate = obj.get_success_rate() if rate >= 90: color = "success" elif rate >= 70: color = "warning" else: color = "danger" return format_html('{:.1f}%', color, rate) get_success_rate.short_description = "Erfolgsrate" @admin.register(Person) class PersonAdmin(admin.ModelAdmin): list_display = [ "nachname", "vorname", "familienzweig", "geburtsdatum", "email", "iban_display", ] list_filter = ["familienzweig", "geburtsdatum"] search_fields = ["nachname", "vorname", "email", "familienzweig"] ordering = ["nachname", "vorname"] readonly_fields = ["id"] fieldsets = ( ( "Persönliche Daten", {"fields": ("vorname", "nachname", "geburtsdatum", "email", "telefon")}, ), ("Stiftungsdaten", {"fields": ("familienzweig", "iban", "adresse")}), ("Zusätzlich", {"fields": ("notizen", "aktiv")}), ("System", {"fields": ("id",), "classes": ("collapse",)}), ) def iban_display(self, obj): if obj.iban: return format_html( '{}', obj.iban ) return "-" iban_display.short_description = "IBAN" def get_queryset(self, request): return ( super() .get_queryset(request) .annotate(total_foerderungen=Sum("foerderung__betrag")) ) @admin.register(Paechter) class PaechterAdmin(admin.ModelAdmin): list_display = [ "nachname", "vorname", "pachtnummer", "pachtzins_aktuell", "landwirtschaftliche_ausbildung", "aktiv", ] list_filter = ["landwirtschaftliche_ausbildung", "aktiv"] search_fields = ["nachname", "vorname", "email", "pachtnummer"] ordering = ["nachname", "vorname"] readonly_fields = ["id"] fieldsets = ( ( "Persönliche Daten", {"fields": ("vorname", "nachname", "geburtsdatum", "email", "telefon")}, ), ( "Pacht-Informationen", { "fields": ( "pachtnummer", "pachtbeginn_erste", "pachtende_letzte", "pachtzins_aktuell", ) }, ), ( "Landwirtschaftliche Qualifikation", { "fields": ( "landwirtschaftliche_ausbildung", "berufserfahrung_jahre", "spezialisierung", ) }, ), ("Kontaktdaten", {"fields": ("iban", "strasse", "plz", "ort")}), ("Pächter-Typ", {"fields": ("personentyp",)}), ("Zusätzlich", {"fields": ("notizen", "aktiv")}), ("System", {"fields": ("id",), "classes": ("collapse",)}), ) def iban_display(self, obj): if obj.iban: return format_html( '{}', obj.iban ) return "-" iban_display.short_description = "IBAN" @admin.register(Destinataer) class DestinataerAdmin(admin.ModelAdmin): list_display = [ "nachname", "vorname", "familienzweig", "berufsgruppe", "institution", "finanzielle_notlage", "aktiv", ] list_filter = ["familienzweig", "berufsgruppe", "finanzielle_notlage", "aktiv"] search_fields = ["nachname", "vorname", "email", "institution", "familienzweig"] ordering = ["nachname", "vorname"] readonly_fields = ["id"] fieldsets = ( ( "Persönliche Daten", {"fields": ("vorname", "nachname", "geburtsdatum", "email", "telefon")}, ), ( "Berufliche Informationen", {"fields": ("berufsgruppe", "ausbildungsstand", "institution")}, ), ( "Projekt & Finanzen", { "fields": ( "projekt_beschreibung", "jaehrliches_einkommen", "finanzielle_notlage", ) }, ), ( "Stiftungsdaten", {"fields": ("familienzweig", "iban", "strasse", "plz", "ort")}, ), ("Zusätzlich", {"fields": ("notizen", "aktiv")}), ("System", {"fields": ("id",), "classes": ("collapse",)}), ) def iban_display(self, obj): if obj.iban: return format_html( '{}', obj.iban ) return "-" iban_display.short_description = "IBAN" @admin.register(Land) class LandAdmin(admin.ModelAdmin): list_display = [ "lfd_nr", "gemeinde", "gemarkung", "flur", "flurstueck", "groesse_qm", "verp_flaeche_aktuell", "verpachtungsgrad_display", "aktiv", ] list_filter = ["gemeinde", "gemarkung", "aktiv"] search_fields = ["lfd_nr", "gemeinde", "gemarkung", "flur", "flurstueck"] ordering = ["gemeinde", "gemarkung", "flur", "flurstueck"] readonly_fields = ["id", "gesamtflaeche_berechnet", "verpachtungsgrad_berechnet"] fieldsets = ( ("Identifikation", {"fields": ("lfd_nr", "ew_nummer")}), ("Gerichtliche Zuständigkeit", {"fields": ("amtsgericht",)}), ( "Verwaltungsstruktur", {"fields": ("gemeinde", "gemarkung", "flur", "flurstueck")}, ), ( "Flächenangaben", { "fields": ( "groesse_qm", "gruenland_qm", "acker_qm", "wald_qm", "sonstiges_qm", ) }, ), ( "Verpachtung", { "fields": ( "verpachtete_gesamtflaeche", "flaeche_alte_liste", "verp_flaeche_aktuell", ) }, ), ("Steuern und Abgaben", {"fields": ("anteil_grundsteuer", "anteil_lwk")}), ("Status", {"fields": ("aktiv", "notizen")}), ( "System", { "fields": ("id", "erstellt_am", "aktualisiert_am"), "classes": ("collapse",), }, ), ) def verpachtungsgrad_display(self, obj): grad = obj.get_verpachtungsgrad() if grad > 90: color = "green" elif grad > 70: color = "orange" else: color = "red" return format_html('{:.1f}%', color, grad) verpachtungsgrad_display.short_description = "Verpachtungsgrad" def gesamtflaeche_berechnet(self, obj): return f"{obj.get_gesamtflaeche():.2f} qm" gesamtflaeche_berechnet.short_description = "Berechnete Gesamtfläche" def verpachtungsgrad_berechnet(self, obj): return f"{obj.get_verpachtungsgrad():.1f}%" verpachtungsgrad_berechnet.short_description = "Verpachtungsgrad" @admin.register(LandVerpachtung) class LandVerpachtungAdmin(admin.ModelAdmin): list_display = [ "land", "paechter", "pachtzins_pauschal", "pachtbeginn", "pachtende", "status_display", "erstellt_am", ] list_filter = ["status", "pachtbeginn", "pachtende", "erstellt_am"] search_fields = ["land__lfd_nr", "land__gemeinde", "paechter__vorname", "paechter__nachname", "vertragsnummer"] ordering = ["-erstellt_am"] readonly_fields = ["id", "erstellt_am", "aktualisiert_am"] fieldsets = ( ("Verpachtungsdetails", { "fields": ("land", "paechter", "vertragsnummer", "status") }), ("Laufzeit", { "fields": ("pachtbeginn", "pachtende", "verlaengerung_klausel") }), ("Fläche", { "fields": ("verpachtete_flaeche",) }), ("Pachtzins", { "fields": ("pachtzins_pauschal", "pachtzins_pro_ha", "zahlungsweise") }), ("Umsatzsteuer", { "fields": ("ust_option", "ust_satz"), "classes": ("collapse",) }), ("Umlagen", { "fields": ("grundsteuer_umlage", "versicherungen_umlage", "verbandsbeitraege_umlage", "jagdpacht_anteil_umlage"), "classes": ("collapse",) }), ("Zusatzinformationen", { "fields": ("bemerkungen",), "classes": ("collapse",) }), ("System", { "fields": ("id", "erstellt_am", "aktualisiert_am"), "classes": ("collapse",) }), ) def status_display(self, obj): colors = { 'aktiv': 'green', 'beendet': 'red', 'geplant': 'orange', 'gekündigt': 'red' } color = colors.get(obj.status, 'black') return format_html( '{}', color, obj.get_status_display() ) status_display.short_description = "Status" @admin.register(DokumentLink) class DokumentLinkAdmin(admin.ModelAdmin): list_display = ["titel", "kontext", "paperless_document_id"] list_filter = ["kontext"] search_fields = ["titel", "kontext"] ordering = ["titel"] readonly_fields = ["id"] fieldsets = ( ( "Dokument", {"fields": ("titel", "kontext", "paperless_document_id", "beschreibung")}, ), ("System", {"fields": ("id",), "classes": ("collapse",)}), ) @admin.register(Foerderung) class FoerderungAdmin(admin.ModelAdmin): list_display = [ "destinataer", "jahr", "betrag", "verwendungsnachweis_link", "total_for_destinataer", ] list_filter = ["jahr", "destinataer__familienzweig"] search_fields = [ "destinataer__nachname", "destinataer__vorname", "destinataer__familienzweig", ] ordering = ["-jahr", "-betrag"] readonly_fields = ["id"] fieldsets = ( ( "Förderung", { "fields": ( "destinataer", "person", "jahr", "betrag", "kategorie", "status", ) }, ), ("Dokumentation", {"fields": ("verwendungsnachweis", "bemerkungen")}), ("Daten", {"fields": ("antragsdatum", "entscheidungsdatum")}), ("System", {"fields": ("id",), "classes": ("collapse",)}), ) def verwendungsnachweis_link(self, obj): if obj.verwendungsnachweis: return format_html( '{}', reverse( "admin:stiftung_dokumentlink_change", args=[obj.verwendungsnachweis.id], ), obj.verwendungsnachweis.titel, ) return "-" verwendungsnachweis_link.short_description = "Verwendungsnachweis" def total_for_destinataer(self, obj): total = ( Foerderung.objects.filter(destinataer=obj.destinataer).aggregate( Sum("betrag") )["betrag__sum"] or 0 ) return f"€{total:,.2f}" total_for_destinataer.short_description = "Gesamt für Destinatär" @admin.register(Rentmeister) class RentmeisterAdmin(admin.ModelAdmin): list_display = [ "__str__", "email", "telefon", "seit_datum", "bis_datum", "aktiv", "monatliche_verguetung", ] list_filter = ["aktiv", "seit_datum", "anrede"] search_fields = ["vorname", "nachname", "email", "telefon", "ort"] ordering = ["nachname", "vorname"] readonly_fields = ["id", "erstellt_am", "aktualisiert_am"] fieldsets = ( ("Persönliche Daten", {"fields": ("anrede", "vorname", "nachname", "titel")}), ( "Kontaktdaten", {"fields": ("email", "telefon", "mobil", "strasse", "plz", "ort")}, ), ( "Bankdaten", {"fields": ("iban", "bic", "bank_name"), "classes": ["collapse"]}, ), ( "Stiftungsdaten", { "fields": ( "seit_datum", "bis_datum", "aktiv", "monatliche_verguetung", "km_pauschale", ) }, ), ( "Zusätzliche Informationen", {"fields": ("notizen",), "classes": ["collapse"]}, ), ( "System", { "fields": ("id", "erstellt_am", "aktualisiert_am"), "classes": ["collapse"], }, ), ) @admin.register(StiftungsKonto) class StiftungsKontoAdmin(admin.ModelAdmin): list_display = [ "kontoname", "bank_name", "konto_typ", "saldo", "saldo_datum", "aktiv", ] list_filter = ["konto_typ", "aktiv", "bank_name"] search_fields = ["kontoname", "bank_name", "iban"] ordering = ["bank_name", "kontoname"] readonly_fields = ["id", "erstellt_am", "aktualisiert_am"] fieldsets = ( ( "Kontodaten", {"fields": ("kontoname", "bank_name", "iban", "bic", "konto_typ")}, ), ( "Finanzdaten", {"fields": ("saldo", "saldo_datum", "zinssatz", "laufzeit_bis")}, ), ("Status", {"fields": ("aktiv", "notizen")}), ( "System", { "fields": ("id", "erstellt_am", "aktualisiert_am"), "classes": ["collapse"], }, ), ) @admin.register(Verwaltungskosten) class VerwaltungskostenAdmin(admin.ModelAdmin): list_display = [ "bezeichnung", "kategorie", "betrag", "datum", "status", "rentmeister", "konto", ] list_filter = ["kategorie", "status", "datum", "rentmeister", "konto"] search_fields = [ "bezeichnung", "lieferant_firma", "rechnungsnummer", "beschreibung", ] ordering = ["-datum", "-erstellt_am"] readonly_fields = ["id", "erstellt_am", "aktualisiert_am"] date_hierarchy = "datum" fieldsets = ( ( "Grunddaten", {"fields": ("bezeichnung", "kategorie", "betrag", "datum", "status")}, ), ("Zuordnung", {"fields": ("rentmeister", "konto")}), ( "Lieferant/Rechnung", {"fields": ("lieferant_firma", "rechnungsnummer"), "classes": ["collapse"]}, ), ( "Fahrtkosten", { "fields": ("km_anzahl", "km_satz", "von_ort", "nach_ort", "zweck"), "classes": ["collapse"], "description": 'Nur für Kategorie "Fahrtkosten" relevant', }, ), ( "Zusätzliche Informationen", {"fields": ("beschreibung", "notizen"), "classes": ["collapse"]}, ), ( "System", { "fields": ("id", "erstellt_am", "aktualisiert_am"), "classes": ["collapse"], }, ), ) @admin.register(BankTransaction) class BankTransactionAdmin(admin.ModelAdmin): list_display = [ "datum", "konto", "betrag", "empfaenger_zahlungspflichtiger", "transaction_type", "status", "verwaltungskosten", ] list_filter = ["konto", "transaction_type", "status", "datum", "importiert_am"] search_fields = ["verwendungszweck", "empfaenger_zahlungspflichtiger", "referenz"] readonly_fields = ["importiert_am", "import_datei"] ordering = ["-datum", "-importiert_am"] fieldsets = ( ("Basisdaten", {"fields": ("konto", "datum", "valuta", "betrag", "waehrung")}), ( "Transaktionsdetails", { "fields": ( "verwendungszweck", "empfaenger_zahlungspflichtiger", "iban_gegenpartei", "bic_gegenpartei", "referenz", "transaction_type", ) }, ), ("Verwaltung", {"fields": ("status", "kommentare", "verwaltungskosten")}), ( "Import-Information", { "fields": ("import_datei", "importiert_am", "saldo_nach_buchung"), "classes": ("collapse",), }, ), ) def get_queryset(self, request): return ( super().get_queryset(request).select_related("konto", "verwaltungskosten") ) @admin.register(AuditLog) class AuditLogAdmin(admin.ModelAdmin): list_display = [ "timestamp", "username", "action", "entity_type", "entity_name", "ip_address", ] list_filter = ["action", "entity_type", "timestamp", "username"] search_fields = ["username", "entity_name", "description", "ip_address"] readonly_fields = [ "id", "timestamp", "user", "username", "action", "entity_type", "entity_id", "entity_name", "description", "changes", "ip_address", "user_agent", "session_key", ] ordering = ["-timestamp"] date_hierarchy = "timestamp" fieldsets = ( ( "Benutzer und Zeit", {"fields": ("timestamp", "user", "username", "session_key")}, ), ( "Aktion", { "fields": ( "action", "entity_type", "entity_id", "entity_name", "description", ) }, ), ("Änderungsdetails", {"fields": ("changes",), "classes": ["collapse"]}), ( "Request-Informationen", {"fields": ("ip_address", "user_agent"), "classes": ["collapse"]}, ), ("System", {"fields": ("id",), "classes": ["collapse"]}), ) def has_add_permission(self, request): return False # Don't allow manual creation def has_change_permission(self, request, obj=None): return False # Don't allow editing @admin.register(BackupJob) class BackupJobAdmin(admin.ModelAdmin): list_display = [ "created_at", "backup_type", "status", "backup_size_display", "duration_display", "created_by", ] list_filter = ["backup_type", "status", "created_at"] search_fields = ["backup_filename", "created_by__username"] readonly_fields = [ "id", "created_at", "started_at", "completed_at", "backup_size", "get_duration", ] ordering = ["-created_at"] fieldsets = ( ("Job-Details", {"fields": ("backup_type", "status", "created_by")}), ( "Zeitpunkte", {"fields": ("created_at", "started_at", "completed_at", "get_duration")}, ), ( "Ergebnis", { "fields": ( "backup_filename", "backup_size", "database_size", "files_count", ) }, ), ("Fehlerbehandlung", {"fields": ("error_message",), "classes": ["collapse"]}), ("System", {"fields": ("id",), "classes": ["collapse"]}), ) def backup_size_display(self, obj): return obj.get_size_display() backup_size_display.short_description = "Backup-Größe" def duration_display(self, obj): duration = obj.get_duration() if duration: return f"{duration.total_seconds():.1f}s" return "-" duration_display.short_description = "Dauer" def has_add_permission(self, request): return False # Use the web interface for creating backups @admin.register(AppConfiguration) class AppConfigurationAdmin(admin.ModelAdmin): list_display = [ "display_name", "key", "value_display", "category", "setting_type", "is_active", "updated_at", ] list_filter = ["category", "setting_type", "is_active"] search_fields = ["key", "display_name", "description"] readonly_fields = ["id", "created_at", "updated_at"] ordering = ["category", "order", "display_name"] fieldsets = ( ( "Basic Information", { "fields": ( "key", "display_name", "description", "category", "setting_type", ) }, ), ("Value Configuration", {"fields": ("value", "default_value")}), ("Options", {"fields": ("is_active", "is_system", "order")}), ( "Metadata", {"fields": ("created_at", "updated_at"), "classes": ("collapse",)}, ), ) def value_display(self, obj): """Display value with type formatting""" value = obj.value if obj.setting_type == "boolean": icon = "✅" if obj.get_typed_value() else "❌" return format_html("{} {}", icon, value) elif obj.setting_type == "url": return format_html( '{}', value, value[:50] + "..." if len(value) > 50 else value, ) elif len(value) > 100: return value[:100] + "..." return value value_display.short_description = "Current Value" def get_readonly_fields(self, request, obj=None): readonly = list(self.readonly_fields) if obj and obj.is_system: readonly.extend(["key", "setting_type", "is_system"]) return readonly @admin.register(DestinataerUnterstuetzung) class DestinataerUnterstuetzungAdmin(admin.ModelAdmin): list_display = [ "__str__", "destinataer", "betrag", "faellig_am", "status", "wiederkehrend_von", "ausgezahlt_am", ] list_filter = ["status", "faellig_am", "erstellt_am", "konto"] search_fields = [ "destinataer__vorname", "destinataer__nachname", "beschreibung", "empfaenger_name", ] readonly_fields = ["id", "erstellt_am", "aktualisiert_am"] fieldsets = ( ( "Grundinformationen", { "fields": ( "destinataer", "konto", "betrag", "faellig_am", "status", "beschreibung", ) }, ), ( "Überweisungsdaten", {"fields": ("empfaenger_iban", "empfaenger_name", "verwendungszweck")}, ), ("Zahlungsinformationen", {"fields": ("ausgezahlt_am", "ausgezahlt_von")}), ("Wiederkehrend", {"fields": ("wiederkehrend_von",)}), ( "Metadaten", { "fields": ("id", "erstellt_am", "aktualisiert_am"), "classes": ("collapse",), }, ), ) @admin.register(UnterstuetzungWiederkehrend) class UnterstuetzungWiederkehrendAdmin(admin.ModelAdmin): list_display = [ "__str__", "destinataer", "betrag", "intervall", "aktiv", "naechste_generierung", ] list_filter = ["intervall", "aktiv", "erstellt_am"] search_fields = [ "destinataer__vorname", "destinataer__nachname", "beschreibung", "empfaenger_name", ] readonly_fields = ["id", "erstellt_am"] fieldsets = ( ( "Grundinformationen", { "fields": ( "destinataer", "konto", "betrag", "intervall", "beschreibung", "aktiv", ) }, ), ( "Überweisungsdaten", {"fields": ("empfaenger_iban", "empfaenger_name", "verwendungszweck")}, ), ( "Zeitplanung", { "fields": ( "erste_zahlung_am", "letzte_zahlung_am", "naechste_generierung", ) }, ), ( "Metadaten", {"fields": ("id", "erstellt_von", "erstellt_am"), "classes": ("collapse",)}, ), ) @admin.register(models.HelpBox) class HelpBoxAdmin(admin.ModelAdmin): list_display = [ "get_page_display", "title", "is_active", "updated_at", "updated_by", ] list_filter = ["page_key", "is_active", "updated_at"] search_fields = ["title", "content"] fieldsets = ( ("Grundinformationen", {"fields": ("page_key", "title", "is_active")}), ( "Inhalt", { "fields": ("content",), "description": "Markdown wird unterstützt: **fett**, *kursiv*, `code`, [Link](url), Listen mit - oder 1.", }, ), ( "Metadaten", { "fields": ("created_at", "updated_at", "created_by", "updated_by"), "classes": ("collapse",), }, ), ) readonly_fields = ["created_at", "updated_at"] def get_page_display(self, obj): return obj.get_page_key_display() get_page_display.short_description = "Seite" def save_model(self, request, obj, form, change): if not change: # Neues Objekt obj.created_by = request.user.username obj.updated_by = request.user.username super().save_model(request, obj, form, change) # Customize admin site admin.site.site_header = "Stiftungsverwaltung Administration" admin.site.site_title = "Stiftungsverwaltung Admin" admin.site.index_title = "Willkommen zur Stiftungsverwaltung"