Files
stiftung-management-system/app/stiftung/views/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

255 lines
9.7 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.

# 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 django.template.loader import render_to_string
veranstaltung = get_object_or_404(Veranstaltung, pk=pk)
teilnehmer = veranstaltung.teilnehmer.all().order_by("nachname", "vorname")
# Render HTML for all letters
html_string = render_to_string(
"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,
})