"""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