Files
stiftung-management-system/app/stiftung/admin/veranstaltung.py
SysAdmin Agent 3ca2706e5d 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>
2026-03-11 09:55:15 +00:00

191 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django import forms
from django.contrib import admin
from django.utils.html import format_html
from ..models import BriefVorlage, Veranstaltung, Veranstaltungsteilnehmer
class VeranstaltungsteilnehmerInline(admin.TabularInline):
model = Veranstaltungsteilnehmer
extra = 1
fields = [
"anrede", "vorname", "nachname", "strasse", "plz", "ort",
"email", "rsvp_status", "bemerkungen",
]
class BriefVorlageWidget(forms.Textarea):
"""Erweitertes Textarea-Widget für HTML-Briefvorlagen mit Editor-Panel und Platzhalter-Hilfe."""
class Media:
js = ["stiftung/js/briefvorlage_editor.js"]
def __init__(self, attrs=None):
default_attrs = {"rows": 18, "cols": 80, "class": "briefvorlage-textarea", "style": "font-family: monospace; font-size: 13px;"}
if attrs:
default_attrs.update(attrs)
super().__init__(attrs=default_attrs)
class VeranstaltungAdminForm(forms.ModelForm):
class Meta:
model = Veranstaltung
fields = "__all__"
widgets = {
"briefvorlage": BriefVorlageWidget(),
}
@admin.register(Veranstaltung)
class VeranstaltungAdmin(admin.ModelAdmin):
form = VeranstaltungAdminForm
list_display = [
"titel", "datum", "uhrzeit", "ort", "status",
"get_teilnehmer_count", "get_zugesagte_count", "budget_pro_person",
]
list_filter = ["status", "datum"]
search_fields = ["titel", "ort", "beschreibung"]
ordering = ["-datum"]
readonly_fields = ["id", "erstellt_am", "aktualisiert_am", "serienbrief_aktionen", "platzhalter_dokumentation"]
inlines = [VeranstaltungsteilnehmerInline]
fieldsets = (
("Grunddaten", {"fields": ("titel", "datum", "uhrzeit", "status")}),
("Veranstaltungsort", {"fields": ("ort", "adresse")}),
("Details", {"fields": ("beschreibung", "budget_pro_person")}),
(
"Serienbrief Vorlage",
{
"fields": (
"platzhalter_dokumentation",
"betreff",
"briefvorlage",
),
},
),
(
"Serienbrief Unterschriften & Aktionen",
{
"fields": (
"unterschrift_1_name", "unterschrift_1_titel",
"unterschrift_2_name", "unterschrift_2_titel",
"serienbrief_aktionen",
),
},
),
("System", {"fields": ("id", "erstellt_am", "aktualisiert_am"), "classes": ("collapse",)}),
)
def get_teilnehmer_count(self, obj):
return obj.get_teilnehmer_count()
get_teilnehmer_count.short_description = "Teilnehmer gesamt"
def get_zugesagte_count(self, obj):
return obj.get_zugesagte_count()
get_zugesagte_count.short_description = "Zugesagt"
def platzhalter_dokumentation(self, obj):
return format_html(
"""<div class="help" style="background:#f8f9fa;border:1px solid #dee2e6;border-radius:4px;padding:10px 14px;margin-bottom:4px;">
<strong>Verfügbare Platzhalter im Brieftext:</strong><br>
<table style="margin-top:6px;border-collapse:collapse;font-size:13px;">
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ anrede }}}}</td><td>Anredetitel (Herr / Frau)</td></tr>
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ vorname }}}}</td><td>Vorname des Empfängers</td></tr>
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ nachname }}}}</td><td>Nachname des Empfängers</td></tr>
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ strasse }}}}</td><td>Straße und Hausnummer</td></tr>
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ plz }}}}</td><td>Postleitzahl</td></tr>
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ ort }}}}</td><td>Wohnort des Empfängers</td></tr>
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ datum }}}}</td><td>Datum der Veranstaltung (z.B. Freitag, 17. April 2026)</td></tr>
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ uhrzeit }}}}</td><td>Uhrzeit der Veranstaltung (z.B. 19:00 Uhr)</td></tr>
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ veranstaltungsort }}}}</td><td>Name des Veranstaltungsorts / Gasthaus</td></tr>
<tr><td style="padding:2px 12px 2px 0;font-family:monospace;color:#d63384;">{{{{ gasthaus_adresse }}}}</td><td>Adresse des Gasthauses</td></tr>
</table>
<div style="margin-top:8px;font-size:12px;color:#6c757d;">
Platzhalter werden beim PDF-Export automatisch mit den Empfänger- und Veranstaltungsdaten befüllt.
Tipp: Vorlagen unter <a href="/admin/stiftung/briefvorlage/" target="_blank">Verwaltung → Briefvorlagen</a> speichern und wiederverwenden.
</div>
</div>"""
)
platzhalter_dokumentation.short_description = "Platzhalter-Dokumentation"
platzhalter_dokumentation.allow_tags = True
def serienbrief_aktionen(self, obj):
if obj.pk:
from django.urls import reverse as url_reverse
pdf_url = url_reverse("stiftung:veranstaltung_serienbrief_pdf", args=[obj.pk])
vorschau_url = url_reverse("stiftung:veranstaltung_serienbrief_vorschau", args=[obj.pk])
return format_html(
'<a href="{}" target="_blank" class="button" style="margin-right:8px;">Serienbrief-PDF generieren</a>'
'<a href="{}" target="_blank" class="button default">Vorschau im Browser</a>',
pdf_url, vorschau_url,
)
return ""
serienbrief_aktionen.short_description = "Aktionen"
actions = ["generate_serienbrief"]
def generate_serienbrief(self, request, queryset):
if queryset.count() != 1:
self.message_user(
request,
"Bitte genau eine Veranstaltung auswählen.",
level="error",
)
return
from django.urls import reverse as url_reverse
from django.shortcuts import redirect
veranstaltung = queryset.first()
url = url_reverse("stiftung:veranstaltung_serienbrief_pdf", args=[veranstaltung.pk])
return redirect(url)
generate_serienbrief.short_description = "Serienbrief-PDF generieren"
@admin.register(BriefVorlage)
class BriefVorlageAdmin(admin.ModelAdmin):
list_display = ["name", "beschreibung_kurz", "erstellt_am", "aktualisiert_am"]
search_fields = ["name", "beschreibung"]
ordering = ["name"]
readonly_fields = ["erstellt_am", "aktualisiert_am"]
fieldsets = (
(None, {"fields": ("name", "beschreibung")}),
(
"Briefinhalt",
{
"fields": ("betreff", "briefvorlage"),
"description": (
"Verfügbare Platzhalter: {{ anrede }}, {{ vorname }}, {{ nachname }}, "
"{{ strasse }}, {{ plz }}, {{ ort }}, {{ datum }}, {{ uhrzeit }}, "
"{{ veranstaltungsort }}, {{ gasthaus_adresse }}"
),
},
),
("System", {"fields": ("erstellt_am", "aktualisiert_am"), "classes": ("collapse",)}),
)
def beschreibung_kurz(self, obj):
return obj.beschreibung[:80] + "" if len(obj.beschreibung) > 80 else obj.beschreibung
beschreibung_kurz.short_description = "Beschreibung"
@admin.register(Veranstaltungsteilnehmer)
class VeranstaltungsteilnehmerAdmin(admin.ModelAdmin):
list_display = [
"nachname", "vorname", "anrede", "veranstaltung", "rsvp_status", "ort",
]
list_filter = ["rsvp_status", "veranstaltung", "anrede"]
search_fields = ["vorname", "nachname", "ort", "email"]
ordering = ["veranstaltung", "nachname", "vorname"]
readonly_fields = ["id", "erstellt_am"]
fieldsets = (
("Zuordnung", {"fields": ("veranstaltung", "paechter", "destinataer")}),
(
"Persönliche Daten",
{"fields": ("anrede", "vorname", "nachname", "email")},
),
("Adresse", {"fields": ("strasse", "plz", "ort")}),
("RSVP", {"fields": ("rsvp_status", "bemerkungen")}),
("System", {"fields": ("id", "erstellt_am"), "classes": ("collapse",)}),
)