"""Views für den web-basierten Dokument-Vorlagen-Editor.""" from django.contrib import messages from django.contrib.auth.decorators import login_required from django.http import HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.views.decorators.http import require_POST from stiftung.models import DokumentVorlage from stiftung.utils.vorlagen import get_vorlage_original @login_required def vorlagen_liste(request): """Übersicht aller Dokument-Vorlagen nach Kategorie.""" vorlagen = DokumentVorlage.objects.select_related("zuletzt_bearbeitet_von").all() kategorien = {} for v in vorlagen: if v.kategorie not in kategorien: kategorien[v.kategorie] = [] kategorien[v.kategorie].append(v) # Kategorie-Labels kategorie_labels = dict(DokumentVorlage.KATEGORIE_CHOICES) return render(request, "stiftung/vorlagen_liste.html", { "kategorien": kategorien, "kategorie_labels": kategorie_labels, "vorlagen_count": vorlagen.count(), }) @login_required def vorlage_editor(request, pk): """Editor für eine einzelne Vorlage.""" vorlage = get_object_or_404(DokumentVorlage, pk=pk) if request.method == "POST": html_inhalt = request.POST.get("html_inhalt", "") vorlage.html_inhalt = html_inhalt vorlage.zuletzt_bearbeitet_von = request.user vorlage.save() messages.success(request, f'Vorlage „{vorlage.bezeichnung}" wurde gespeichert.') return redirect("stiftung:vorlage_editor", pk=pk) try: original = get_vorlage_original(vorlage.schluessel) hat_original = True except FileNotFoundError: original = None hat_original = False import json from django.utils.safestring import mark_safe # JSON-encode and escape to prevent XSS in script tag html_json = json.dumps(vorlage.html_inhalt) html_json = html_json.replace("<", "\\u003c").replace(">", "\\u003e") # Serienbrief templates are full HTML documents with Django template tags # ({% for %}, {% if %}) — Summernote WYSIWYG mangles these. # Use a plain code editor textarea instead. use_code_editor = vorlage.kategorie == "serienbrief" return render(request, "stiftung/vorlage_editor.html", { "vorlage": vorlage, "hat_original": hat_original, "variablen": vorlage.verfuegbare_variablen, "html_inhalt_json": mark_safe(html_json), "use_code_editor": use_code_editor, }) @login_required @require_POST def vorlage_zuruecksetzen(request, pk): """Setzt eine Vorlage auf den Datei-Original-Inhalt zurück.""" vorlage = get_object_or_404(DokumentVorlage, pk=pk) try: original = get_vorlage_original(vorlage.schluessel) vorlage.html_inhalt = original vorlage.zuletzt_bearbeitet_von = request.user vorlage.save() messages.success(request, f'Vorlage „{vorlage.bezeichnung}" wurde auf die Original-Datei zurückgesetzt.') except FileNotFoundError: messages.error(request, "Original-Datei nicht gefunden. Zurücksetzen nicht möglich.") return redirect("stiftung:vorlage_editor", pk=pk) @login_required @require_POST def vorlagen_alle_zuruecksetzen(request): """Setzt ALLE Vorlagen auf die Original-Datei-Inhalte zurück.""" vorlagen = DokumentVorlage.objects.all() restored = 0 for vorlage in vorlagen: try: original = get_vorlage_original(vorlage.schluessel) vorlage.html_inhalt = original vorlage.zuletzt_bearbeitet_von = request.user vorlage.save() restored += 1 except FileNotFoundError: pass messages.success(request, f"{restored} Vorlage(n) auf Original zurückgesetzt.") return redirect("stiftung:vorlagen_liste") @login_required def vorlage_vorschau(request, pk): """Rendert eine Vorschau der Vorlage mit Beispieldaten (JSON-Response).""" vorlage = get_object_or_404(DokumentVorlage, pk=pk) # Rohinhalt aus POST (live-preview) oder aus DB html_inhalt = request.POST.get("html_inhalt") if request.method == "POST" else vorlage.html_inhalt # Einfache Beispieldaten je Kategorie beispiel_context = _get_beispiel_context(vorlage.schluessel) try: from django.template import Context, Engine engine = Engine.get_default() t = engine.from_string(html_inhalt) rendered = t.render(Context(beispiel_context)) return HttpResponse(rendered, content_type="text/html; charset=utf-8") except Exception as exc: return HttpResponse( f"
Template-Fehler: {exc}
", content_type="text/html; charset=utf-8", ) def _get_beispiel_context(schluessel: str) -> dict: """Gibt Beispieldaten für Vorschau-Rendering zurück.""" from datetime import date, time class FakeObj(dict): def __getattr__(self, k): return self.get(k, "") destinataer = FakeObj( vorname="Maria", nachname="Mustermann", anrede="Frau", strasse="Musterstraße 1", plz="46499", ort="Hamminkeln", email="m.mustermann@example.com", ) einladung = FakeObj( vorname="Maria", nachname="Mustermann", email="m.mustermann@example.com", ) base = { "destinataer": destinataer, "einladung": einladung, "datum": date.today(), "zeitraum": "01.01.2025 – 31.12.2025", "betrag_quartal": 500, "betrag_jaehrlich": 2000, "gesamtbetrag": 2000, "zweck": "Studienförderung", "unterstuetzungen": [], "halbjahr_label": "1. Halbjahr 2025", "upload_url": "https://vhtv-stiftung.de/portal/upload/beispiel-token/", "gueltig_bis": date.today(), "qr_code_base64": "", "ist_erinnerung": False, "onboarding_url": "https://vhtv-stiftung.de/portal/onboarding/beispiel/", "veranstaltung": FakeObj(titel="Stiftungsessen 2025"), "teilnehmer_list": [], } # Serienbrief-Vorlage: vollständige Veranstaltungs- und Teilnehmer-Beispieldaten if "serienbrief" in schluessel: base["veranstaltung"] = FakeObj( titel="Stiftungsessen 2025", datum=date.today(), uhrzeit=time(18, 0), ort="Gasthaus zur Linde", adresse="Lindenstraße 12, 46499 Hamminkeln", betreff="", briefvorlage="", unterschrift_1_name="Katrin Kleinpaß", unterschrift_1_titel="Rentmeisterin", unterschrift_2_name="Jan Remmer Siebels", unterschrift_2_titel="Rentmeister", ) base["teilnehmer"] = [ FakeObj( anrede="Frau", vorname="Maria", nachname="Mustermann", strasse="Musterstraße 1", plz="46499", ort="Hamminkeln", ), FakeObj( anrede="Herr", vorname="Hans", nachname="Beispiel", strasse="Beispielweg 7", plz="46499", ort="Hamminkeln", ), ] return base