from django.contrib import admin from django.db.models import Sum from django.utils import timezone from django.utils.html import format_html from .. import models from ..models import (AppConfiguration, AuditLog, BackupJob, CSVImport, Person, UnterstuetzungWiederkehrend, VierteljahresNachweis) @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(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(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) @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(VierteljahresNachweis) class VierteljahresNachweisAdmin(admin.ModelAdmin): list_display = [ "destinataer", "jahr", "quartal", "status", "completion_percentage", "faelligkeitsdatum", "is_overdue_display", "eingereicht_am", "geprueft_von", ] list_filter = [ "jahr", "quartal", "status", "studiennachweis_erforderlich", "studiennachweis_eingereicht", "einkommenssituation_bestaetigt", "vermogenssituation_bestaetigt", "faelligkeitsdatum", ] search_fields = [ "destinataer__vorname", "destinataer__nachname", "destinataer__email", ] readonly_fields = [ "id", "erstellt_am", "aktualisiert_am", "completion_percentage", "is_overdue_display", ] ordering = ["-jahr", "-quartal", "destinataer__nachname"] fieldsets = ( ( "Grundinformationen", { "fields": ( "destinataer", "jahr", "quartal", "status", "faelligkeitsdatum", ) }, ), ( "Studiennachweis", { "fields": ( "studiennachweis_erforderlich", "studiennachweis_eingereicht", "studiennachweis_datei", "studiennachweis_bemerkung", ), "classes": ("collapse",), }, ), ( "Einkommenssituation", { "fields": ( "einkommenssituation_bestaetigt", "einkommenssituation_text", "einkommenssituation_datei", ), "classes": ("collapse",), }, ), ( "Vermögenssituation", { "fields": ( "vermogenssituation_bestaetigt", "vermogenssituation_text", "vermogenssituation_datei", ), "classes": ("collapse",), }, ), ( "Weitere Dokumente", { "fields": ( "weitere_dokumente", "weitere_dokumente_beschreibung", ), "classes": ("collapse",), }, ), ( "Verwaltung & Prüfung", { "fields": ( "interne_notizen", "eingereicht_am", "geprueft_am", "geprueft_von", ), "classes": ("collapse",), }, ), ( "Metadaten", { "fields": ( "id", "erstellt_am", "aktualisiert_am", "completion_percentage", "is_overdue_display", ) }, ), ) def completion_percentage(self, obj): """Show completion percentage as colored badge""" percentage = obj.get_completion_percentage() if percentage == 100: color = "success" elif percentage >= 70: color = "info" elif percentage >= 30: color = "warning" else: color = "danger" return format_html( '{} %', color, percentage ) completion_percentage.short_description = "Fortschritt" def is_overdue_display(self, obj): """Display overdue status with icon""" if obj.is_overdue(): return format_html( ' Ja' ) return format_html( ' Nein' ) is_overdue_display.short_description = "Überfällig" actions = ["mark_as_approved", "mark_as_needs_revision"] def mark_as_approved(self, request, queryset): """Bulk action to approve submitted confirmations""" count = 0 for nachweis in queryset.filter(status="eingereicht"): nachweis.status = "geprueft" nachweis.geprueft_am = timezone.now() nachweis.geprueft_von = request.user nachweis.save() count += 1 if count: self.message_user( request, f"{count} Nachweise wurden als geprüft und freigegeben markiert." ) else: self.message_user( request, "Keine eingereichten Nachweise gefunden.", level="warning" ) mark_as_approved.short_description = "Ausgewählte Nachweise freigeben" def mark_as_needs_revision(self, request, queryset): """Bulk action to mark confirmations as needing revision""" count = queryset.exclude(status__in=["offen", "nachbesserung"]).update( status="nachbesserung" ) if count: self.message_user( request, f"{count} Nachweise wurden als nachbesserungsbedürftig markiert." ) mark_as_needs_revision.short_description = "Nachbesserung erforderlich markieren"