# views/veranstaltung.py # Phase 0: Vision 2026 – Code-Refactoring import csv import io import json import os import time from datetime import datetime, timedelta, date from decimal import Decimal import qrcode import qrcode.image.svg import requests from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.db.models import (Avg, Count, DecimalField, F, IntegerField, Q, Sum, Value) from django.db.models.functions import Cast, Coalesce, NullIf, Replace from django.http import HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone from django.views.decorators.csrf import csrf_exempt from django_otp.decorators import otp_required from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_static.models import StaticDevice, StaticToken from django_otp.util import random_hex from rest_framework.decorators import api_view from rest_framework.response import Response from stiftung.models import (AppConfiguration, AuditLog, BackupJob, BankTransaction, BriefVorlage, CSVImport, Destinataer, DestinataerEmailEingang, DestinataerNotiz, DestinataerUnterstuetzung, DokumentLink, Foerderung, GeschichteBild, GeschichteSeite, Land, LandAbrechnung, LandVerpachtung, Paechter, Person, Rentmeister, StiftungsKalenderEintrag, StiftungsKonto, UnterstuetzungWiederkehrend, Veranstaltung, Veranstaltungsteilnehmer, Verwaltungskosten, VierteljahresNachweis) from stiftung.forms import ( DestinataerForm, DestinataerUnterstuetzungForm, DestinataerNotizForm, FoerderungForm, GeschichteBildForm, GeschichteSeiteForm, LandForm, LandVerpachtungForm, LandAbrechnungForm, PaechterForm, DokumentLinkForm, RentmeisterForm, StiftungsKontoForm, VerwaltungskostenForm, BankTransactionForm, BankImportForm, UnterstuetzungForm, UnterstuetzungWiederkehrendForm, UnterstuetzungMarkAsPaidForm, VierteljahresNachweisForm, UserCreationForm, UserUpdateForm, PasswordChangeForm, UserPermissionForm, TwoFactorSetupForm, TwoFactorVerifyForm, TwoFactorDisableForm, BackupTokenRegenerateForm, PersonForm, VeranstaltungForm, VeranstaltungsteilnehmerForm, ) @login_required def veranstaltung_list(request): """Liste aller Veranstaltungen""" veranstaltungen = Veranstaltung.objects.all() return render(request, "stiftung/veranstaltung/list.html", {"veranstaltungen": veranstaltungen}) @login_required def veranstaltung_detail(request, pk): """Detail-Ansicht einer Veranstaltung mit RSVP-Übersicht""" veranstaltung = get_object_or_404(Veranstaltung, pk=pk) teilnehmer = veranstaltung.teilnehmer.all() context = { "veranstaltung": veranstaltung, "teilnehmer": teilnehmer, "zugesagte": teilnehmer.filter(rsvp_status="zugesagt"), "abgesagte": teilnehmer.filter(rsvp_status="abgesagt"), "keine_rueckmeldung": teilnehmer.filter(rsvp_status="keine_rueckmeldung"), "eingeladen": teilnehmer.filter(rsvp_status="eingeladen"), } return render(request, "stiftung/veranstaltung/detail.html", context) @login_required def veranstaltung_serienbrief_pdf(request, pk): """Generiert Serienbrief-PDF für alle Teilnehmer einer Veranstaltung""" from weasyprint import HTML from stiftung.utils.vorlagen import render_vorlage veranstaltung = get_object_or_404(Veranstaltung, pk=pk) teilnehmer = veranstaltung.teilnehmer.all().order_by("nachname", "vorname") # Render HTML for all letters (DB-Vorlage first, file fallback) html_string = render_vorlage( "stiftung/veranstaltung/serienbrief_pdf.html", { "veranstaltung": veranstaltung, "teilnehmer": teilnehmer, }, ) pdf = HTML(string=html_string).write_pdf() filename = f"einladungen_{veranstaltung.datum}_{veranstaltung.titel[:30].replace(' ', '_')}.pdf" response = HttpResponse(pdf, content_type="application/pdf") response["Content-Disposition"] = f'attachment; filename="{filename}"' return response @login_required def veranstaltung_serienbrief_vorschau(request, pk): """HTML-Vorschau des Serienbriefs im Browser (kein PDF-Download)""" veranstaltung = get_object_or_404(Veranstaltung, pk=pk) teilnehmer = veranstaltung.teilnehmer.all().order_by("nachname", "vorname") return render( request, "stiftung/veranstaltung/serienbrief_vorschau.html", { "veranstaltung": veranstaltung, "teilnehmer": teilnehmer, }, ) @login_required def veranstaltung_create(request): """Neue Veranstaltung erstellen""" from stiftung.forms import VeranstaltungForm if request.method == "POST": form = VeranstaltungForm(request.POST) if form.is_valid(): veranstaltung = form.save() messages.success(request, f'Veranstaltung "{veranstaltung.titel}" wurde erstellt.') return redirect("stiftung:veranstaltung_detail", pk=veranstaltung.pk) else: messages.error(request, "Bitte korrigieren Sie die Fehler im Formular.") else: form = VeranstaltungForm() return render(request, "stiftung/veranstaltung/form.html", { "form": form, "title": "Neue Veranstaltung erstellen", }) @login_required def veranstaltung_update(request, pk): """Veranstaltung bearbeiten (inkl. Serienbrief-Felder)""" from stiftung.forms import VeranstaltungForm veranstaltung = get_object_or_404(Veranstaltung, pk=pk) if request.method == "POST": form = VeranstaltungForm(request.POST, instance=veranstaltung) if form.is_valid(): form.save() messages.success(request, f'Veranstaltung "{veranstaltung.titel}" wurde aktualisiert.') return redirect("stiftung:veranstaltung_detail", pk=veranstaltung.pk) else: messages.error(request, "Bitte korrigieren Sie die Fehler im Formular.") else: form = VeranstaltungForm(instance=veranstaltung) return render(request, "stiftung/veranstaltung/form.html", { "form": form, "veranstaltung": veranstaltung, "title": f"Veranstaltung bearbeiten: {veranstaltung.titel}", }) @login_required def veranstaltung_delete(request, pk): """Veranstaltung löschen""" veranstaltung = get_object_or_404(Veranstaltung, pk=pk) if request.method == "POST": titel = veranstaltung.titel veranstaltung.delete() messages.success(request, f'Veranstaltung "{titel}" wurde gelöscht.') return redirect("stiftung:veranstaltung_list") return render(request, "stiftung/veranstaltung/delete.html", { "veranstaltung": veranstaltung, }) @login_required def teilnehmer_create(request, veranstaltung_pk): """Teilnehmer zu einer Veranstaltung hinzufügen""" from stiftung.forms import VeranstaltungsteilnehmerForm veranstaltung = get_object_or_404(Veranstaltung, pk=veranstaltung_pk) if request.method == "POST": form = VeranstaltungsteilnehmerForm(request.POST) if form.is_valid(): teilnehmer = form.save(commit=False) teilnehmer.veranstaltung = veranstaltung teilnehmer.save() messages.success(request, f"{teilnehmer.vorname} {teilnehmer.nachname} wurde hinzugefügt.") return redirect("stiftung:veranstaltung_detail", pk=veranstaltung.pk) else: messages.error(request, "Bitte korrigieren Sie die Fehler im Formular.") else: form = VeranstaltungsteilnehmerForm() return render(request, "stiftung/veranstaltung/teilnehmer_form.html", { "form": form, "veranstaltung": veranstaltung, "title": "Teilnehmer hinzufügen", }) @login_required def teilnehmer_update(request, veranstaltung_pk, pk): """Teilnehmer bearbeiten""" from stiftung.forms import VeranstaltungsteilnehmerForm veranstaltung = get_object_or_404(Veranstaltung, pk=veranstaltung_pk) teilnehmer = get_object_or_404(Veranstaltungsteilnehmer, pk=pk, veranstaltung=veranstaltung) if request.method == "POST": form = VeranstaltungsteilnehmerForm(request.POST, instance=teilnehmer) if form.is_valid(): form.save() messages.success(request, f"{teilnehmer.vorname} {teilnehmer.nachname} wurde aktualisiert.") return redirect("stiftung:veranstaltung_detail", pk=veranstaltung.pk) else: messages.error(request, "Bitte korrigieren Sie die Fehler im Formular.") else: form = VeranstaltungsteilnehmerForm(instance=teilnehmer) return render(request, "stiftung/veranstaltung/teilnehmer_form.html", { "form": form, "veranstaltung": veranstaltung, "teilnehmer": teilnehmer, "title": f"Teilnehmer bearbeiten: {teilnehmer.vorname} {teilnehmer.nachname}", }) @login_required def teilnehmer_delete(request, veranstaltung_pk, pk): """Teilnehmer aus Veranstaltung entfernen""" veranstaltung = get_object_or_404(Veranstaltung, pk=veranstaltung_pk) teilnehmer = get_object_or_404(Veranstaltungsteilnehmer, pk=pk, veranstaltung=veranstaltung) if request.method == "POST": name = f"{teilnehmer.vorname} {teilnehmer.nachname}" teilnehmer.delete() messages.success(request, f"{name} wurde aus der Teilnehmerliste entfernt.") return redirect("stiftung:veranstaltung_detail", pk=veranstaltung.pk) return render(request, "stiftung/veranstaltung/teilnehmer_delete.html", { "veranstaltung": veranstaltung, "teilnehmer": teilnehmer, })