# views/geschichte.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, EmailEingang, 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 geschichte_list(request): """List all published history pages""" seiten = GeschichteSeite.objects.filter(ist_veroeffentlicht=True).order_by('sortierung', 'titel') context = { 'seiten': seiten, 'title': 'Geschichte der Stiftung' } return render(request, 'stiftung/geschichte/liste.html', context) @login_required def geschichte_detail(request, slug): """Display a specific history page""" seite = get_object_or_404(GeschichteSeite, slug=slug, ist_veroeffentlicht=True) bilder = seite.bilder.all().order_by('sortierung', 'titel') context = { 'seite': seite, 'bilder': bilder, 'title': seite.titel } return render(request, 'stiftung/geschichte/detail.html', context) @login_required def geschichte_create(request): """Create a new history page""" if not request.user.has_perm('stiftung.add_geschichteseite'): messages.error(request, 'Sie haben keine Berechtigung, neue Geschichtsseiten zu erstellen.') return redirect('stiftung:geschichte_list') if request.method == 'POST': form = GeschichteSeiteForm(request.POST) if form.is_valid(): seite = form.save(commit=False) seite.erstellt_von = request.user seite.aktualisiert_von = request.user seite.save() form.save_m2m() # Link selected DMS documents dok_ids = request.POST.getlist("dokument_ids") if dok_ids: from stiftung.models import DokumentDatei seite.dokumente.set(DokumentDatei.objects.filter(pk__in=dok_ids)) messages.success(request, f'Geschichtsseite "{seite.titel}" wurde erfolgreich erstellt.') return redirect('stiftung:geschichte_detail', slug=seite.slug) else: form = GeschichteSeiteForm() # Verfuegbare Stiftungsgeschichte-Dokumente aus DMS from stiftung.models import DokumentDatei geschichte_dokumente = DokumentDatei.objects.filter( kontext="stiftungsgeschichte" ).order_by("-erstellt_am")[:20] context = { 'form': form, 'title': 'Neue Geschichtsseite', 'geschichte_dokumente': geschichte_dokumente, 'selected_dok_ids': [], } return render(request, 'stiftung/geschichte/form.html', context) @login_required def geschichte_edit(request, slug): """Edit an existing history page""" seite = get_object_or_404(GeschichteSeite, slug=slug) if not request.user.has_perm('stiftung.change_geschichteseite'): messages.error(request, 'Sie haben keine Berechtigung, diese Geschichtsseite zu bearbeiten.') return redirect('stiftung:geschichte_detail', slug=slug) if request.method == 'POST': form = GeschichteSeiteForm(request.POST, instance=seite) if form.is_valid(): seite = form.save(commit=False) seite.aktualisiert_von = request.user seite.save() form.save_m2m() # Update linked DMS documents dok_ids = request.POST.getlist("dokument_ids") from stiftung.models import DokumentDatei seite.dokumente.set(DokumentDatei.objects.filter(pk__in=dok_ids)) messages.success(request, f'Geschichtsseite "{seite.titel}" wurde erfolgreich aktualisiert.') return redirect('stiftung:geschichte_detail', slug=seite.slug) else: form = GeschichteSeiteForm(instance=seite) # Verfuegbare Stiftungsgeschichte-Dokumente aus DMS from stiftung.models import DokumentDatei geschichte_dokumente = DokumentDatei.objects.filter( kontext="stiftungsgeschichte" ).order_by("-erstellt_am")[:20] # IDs der bereits verknuepften Dokumente selected_dok_ids = list(seite.dokumente.values_list("pk", flat=True)) context = { 'form': form, 'seite': seite, 'title': f'Bearbeiten: {seite.titel}', 'geschichte_dokumente': geschichte_dokumente, 'selected_dok_ids': selected_dok_ids, } return render(request, 'stiftung/geschichte/form.html', context) @login_required def geschichte_bild_upload(request, slug): """Upload images to a history page""" seite = get_object_or_404(GeschichteSeite, slug=slug) if not request.user.has_perm('stiftung.add_geschichtebild'): messages.error(request, 'Sie haben keine Berechtigung, Bilder hochzuladen.') return redirect('stiftung:geschichte_detail', slug=slug) if request.method == 'POST': form = GeschichteBildForm(request.POST, request.FILES) if form.is_valid(): bild = form.save(commit=False) bild.seite = seite bild.hochgeladen_von = request.user bild.save() messages.success(request, f'Bild "{bild.titel}" wurde erfolgreich hochgeladen.') return redirect('stiftung:geschichte_detail', slug=slug) else: form = GeschichteBildForm() context = { 'form': form, 'seite': seite, 'title': f'Bild hochladen: {seite.titel}' } return render(request, 'stiftung/geschichte/bild_form.html', context) @login_required def geschichte_bild_delete(request, slug, bild_id): """Delete an image from a history page""" seite = get_object_or_404(GeschichteSeite, slug=slug) bild = get_object_or_404(GeschichteBild, id=bild_id, seite=seite) if not request.user.has_perm('stiftung.delete_geschichtebild'): messages.error(request, 'Sie haben keine Berechtigung, Bilder zu löschen.') return redirect('stiftung:geschichte_detail', slug=slug) if request.method == 'POST': bild_titel = bild.titel bild.delete() messages.success(request, f'Bild "{bild_titel}" wurde erfolgreich gelöscht.') return redirect('stiftung:geschichte_detail', slug=slug) context = { 'bild': bild, 'seite': seite, 'title': f'Bild löschen: {bild.titel}' } return render(request, 'stiftung/geschichte/bild_delete.html', context) # Calendar Views @login_required def kalender_view(request): """Main calendar view with different view types""" from stiftung.services.calendar_service import StiftungsKalenderService import calendar as cal calendar_service = StiftungsKalenderService() # Get current date and view parameters today = timezone.now().date() view_type = request.GET.get('view', 'month') # month, week, list, agenda year = int(request.GET.get('year', today.year)) month = int(request.GET.get('month', today.month)) # Calculate date ranges based on view type if view_type == 'month': # Get events for the entire month start_date = date(year, month, 1) _, last_day = cal.monthrange(year, month) end_date = date(year, month, last_day) title_suffix = f"{cal.month_name[month]} {year}" elif view_type == 'week': # Get current week week_start = today - timedelta(days=today.weekday()) start_date = week_start end_date = week_start + timedelta(days=6) title_suffix = f"Woche vom {start_date.strftime('%d.%m')} - {end_date.strftime('%d.%m.%Y')}" elif view_type == 'agenda': # Next 30 days start_date = today end_date = today + timedelta(days=30) title_suffix = "Nächste 30 Tage" else: # list view # Next 90 days start_date = today end_date = today + timedelta(days=90) title_suffix = "Liste (nächste 90 Tage)" # Get events for the date range events = calendar_service.get_all_events(start_date, end_date) # Generate calendar grid for month view calendar_grid = None if view_type == 'month': calendar_grid = [] first_day = date(year, month, 1) month_cal = cal.monthcalendar(year, month) for week in month_cal: week_data = [] for day in week: if day == 0: week_data.append(None) else: day_date = date(year, month, day) day_events = [e for e in events if e.date == day_date] week_data.append({ 'day': day, 'date': day_date, 'is_today': day_date == today, 'events': day_events[:3], # Show max 3 events per day 'event_count': len(day_events) }) calendar_grid.append(week_data) # Navigation dates for month view if month > 1: prev_month = month - 1 prev_year = year else: prev_month = 12 prev_year = year - 1 if month < 12: next_month = month + 1 next_year = year else: next_month = 1 next_year = year + 1 context = { 'title': f'Kalender - {title_suffix}', 'events': events, 'calendar_grid': calendar_grid, 'view_type': view_type, 'year': year, 'month': month, 'today': today, 'start_date': start_date, 'end_date': end_date, 'prev_year': prev_year, 'prev_month': prev_month, 'next_year': next_year, 'next_month': next_month, 'month_name': cal.month_name[month], 'weekdays': ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'], } # Choose template based on view type if view_type == 'month': template = 'stiftung/kalender/month_view.html' elif view_type == 'week': template = 'stiftung/kalender/week_view.html' elif view_type == 'agenda': template = 'stiftung/kalender/agenda_view.html' else: template = 'stiftung/kalender/list_view.html' return render(request, template, context) @login_required def kalender_create(request): """Create new calendar event""" from stiftung.models import StiftungsKalenderEintrag if request.method == 'POST': # Simple form handling - you can enhance this with Django forms titel = request.POST.get('titel') beschreibung = request.POST.get('beschreibung', '') datum = request.POST.get('datum') kategorie = request.POST.get('kategorie', 'termin') prioritaet = request.POST.get('prioritaet', 'normal') if titel and datum: zeit_str = request.POST.get('zeit') uhrzeit = zeit_str if zeit_str else None ganztags = not bool(zeit_str) StiftungsKalenderEintrag.objects.create( titel=titel, beschreibung=beschreibung, datum=datum, uhrzeit=uhrzeit, ganztags=ganztags, kategorie=kategorie, prioritaet=prioritaet, erstellt_von=request.user.username ) messages.success(request, 'Kalendereintrag wurde erfolgreich erstellt.') return redirect('stiftung:kalender') else: messages.error(request, 'Titel und Datum sind erforderlich.') context = { 'title': 'Neuer Kalendereintrag', } return render(request, 'stiftung/kalender/create.html', context) @login_required def kalender_detail(request, pk): """Calendar event detail view""" from stiftung.models import StiftungsKalenderEintrag event = get_object_or_404(StiftungsKalenderEintrag, pk=pk) context = { 'title': f'Kalendereintrag: {event.titel}', 'event': event, } return render(request, 'stiftung/kalender/detail.html', context) @login_required def kalender_edit(request, pk): """Edit calendar event""" from stiftung.models import StiftungsKalenderEintrag event = get_object_or_404(StiftungsKalenderEintrag, pk=pk) if request.method == 'POST': event.titel = request.POST.get('titel', event.titel) event.beschreibung = request.POST.get('beschreibung', event.beschreibung) event.datum = request.POST.get('datum', event.datum) zeit_str = request.POST.get('zeit') if zeit_str: event.uhrzeit = zeit_str event.ganztags = False else: event.uhrzeit = None event.ganztags = True event.kategorie = request.POST.get('kategorie', event.kategorie) event.prioritaet = request.POST.get('prioritaet', event.prioritaet) event.erledigt = 'erledigt' in request.POST event.save() messages.success(request, 'Kalendereintrag wurde aktualisiert.') return redirect('stiftung:kalender_detail', pk=pk) context = { 'title': f'Bearbeiten: {event.titel}', 'event': event, } return render(request, 'stiftung/kalender/edit.html', context) @login_required def kalender_delete(request, pk): """Delete calendar event""" from stiftung.models import StiftungsKalenderEintrag event = get_object_or_404(StiftungsKalenderEintrag, pk=pk) if request.method == 'POST': event_titel = event.titel event.delete() messages.success(request, f'Kalendereintrag "{event_titel}" wurde gelöscht.') return redirect('stiftung:kalender') context = { 'title': f'Löschen: {event.titel}', 'event': event, } return render(request, 'stiftung/kalender/delete_confirm.html', context) @login_required def kalender_admin(request): """Calendar administration with event sources and management""" from stiftung.models import StiftungsKalenderEintrag from stiftung.services.calendar_service import StiftungsKalenderService # Get filter parameters show_custom = request.GET.get('show_custom', 'true') == 'true' show_payments = request.GET.get('show_payments', 'true') == 'true' show_leases = request.GET.get('show_leases', 'true') == 'true' show_birthdays = request.GET.get('show_birthdays', 'true') == 'true' category_filter = request.GET.get('category', '') priority_filter = request.GET.get('priority', '') # Initialize calendar service calendar_service = StiftungsKalenderService() # Get events based on filters from datetime import date, timedelta start_date = date.today() - timedelta(days=30) end_date = date.today() + timedelta(days=90) all_events = [] # Custom calendar entries if show_custom: custom_events = calendar_service.get_calendar_events(start_date, end_date) all_events.extend(custom_events) # Payment events if show_payments: payment_events = calendar_service.get_support_payment_events(start_date, end_date) all_events.extend(payment_events) # Lease events if show_leases: lease_events = calendar_service.get_lease_events(start_date, end_date) all_events.extend(lease_events) # Birthday events if show_birthdays: birthday_events = calendar_service.get_birthday_events(start_date, end_date) all_events.extend(birthday_events) # Filter by category and priority if specified if category_filter: all_events = [e for e in all_events if getattr(e, 'category', '') == category_filter] if priority_filter: all_events = [e for e in all_events if getattr(e, 'priority', '') == priority_filter] # Sort events by date all_events.sort(key=lambda x: x.date) # Get statistics custom_count = StiftungsKalenderEintrag.objects.count() total_events = len(all_events) # Event source statistics stats = { 'custom_events': len([e for e in all_events if getattr(e, 'source', '') == 'custom']), 'payment_events': len([e for e in all_events if getattr(e, 'source', '') == 'payment']), 'lease_events': len([e for e in all_events if getattr(e, 'source', '') == 'lease']), 'birthday_events': len([e for e in all_events if getattr(e, 'source', '') == 'birthday']), 'total_events': total_events, 'custom_count': custom_count, } context = { 'title': 'Kalender Administration', 'events': all_events, 'stats': stats, 'show_custom': show_custom, 'show_payments': show_payments, 'show_leases': show_leases, 'show_birthdays': show_birthdays, 'category_filter': category_filter, 'priority_filter': priority_filter, 'categories': StiftungsKalenderEintrag.KATEGORIE_CHOICES, 'priorities': StiftungsKalenderEintrag.PRIORITAET_CHOICES, } return render(request, 'stiftung/kalender/admin.html', context) @login_required def kalender_api_events(request): """API endpoint for calendar events (JSON)""" from django.http import JsonResponse from stiftung.services.calendar_service import StiftungsKalenderService from datetime import datetime calendar_service = StiftungsKalenderService() # Get date range from request start_date = request.GET.get('start') end_date = request.GET.get('end') if start_date and end_date: try: start_date = datetime.strptime(start_date, '%Y-%m-%d').date() end_date = datetime.strptime(end_date, '%Y-%m-%d').date() except ValueError: return JsonResponse({'error': 'Invalid date format'}, status=400) events = calendar_service.get_all_events(start_date, end_date) else: events = calendar_service.get_all_events() # Convert to FullCalendar format calendar_events = [] for event in events: calendar_events.append({ 'id': getattr(event, 'id', str(event.title)), 'title': event.title, 'start': event.date.strftime('%Y-%m-%d'), 'description': getattr(event, 'description', ''), 'className': f"event-{event.category}", 'backgroundColor': f"var(--bs-{event.color})", 'borderColor': f"var(--bs-{event.color})", }) return JsonResponse(calendar_events, safe=False) # Calendar Views @login_required def kalender_view(request): """Full calendar view with all events""" from stiftung.services.calendar_service import StiftungsKalenderService calendar_service = StiftungsKalenderService() # Get current month events by default today = timezone.now().date() events = calendar_service.get_events_for_month(today.year, today.month) context = { 'events': events, 'title': 'Stiftungskalender', 'current_month': today.strftime('%B %Y'), } return render(request, 'stiftung/kalender/kalender.html', context) context = { 'title': 'Kalendereintrag löschen' } return render(request, 'stiftung/kalender/delete.html', context) # ============================================================================= # E-Mail-Eingang – Destinatäre # ============================================================================= @login_required def email_eingang_list(request): """ Uebersicht aller eingegangenen E-Mails. Filtert nach Status und Kategorie, zeigt ungeklaerte Absender zuerst. """ status_filter = request.GET.get("status", "") kategorie_filter = request.GET.get("kategorie", "") search = request.GET.get("q", "").strip() qs = EmailEingang.objects.select_related("destinataer", "quartalsnachweis", "verwaltungskosten") if status_filter: qs = qs.filter(status=status_filter) if kategorie_filter: qs = qs.filter(kategorie=kategorie_filter) if search: qs = qs.filter( Q(absender_email__icontains=search) | Q(absender_name__icontains=search) | Q(betreff__icontains=search) | Q(destinataer__vorname__icontains=search) | Q(destinataer__nachname__icontains=search) ) # Unbekannte Absender zuerst, dann nach Datum absteigend qs = qs.order_by( "status", "-eingangsdatum", ) paginator = Paginator(qs, 30) page_obj = paginator.get_page(request.GET.get("page")) context = { "title": "E-Mail-Eingang", "page_obj": page_obj, "status_filter": status_filter, "kategorie_filter": kategorie_filter, "search": search, "status_choices": EmailEingang.STATUS_CHOICES, "kategorie_choices": EmailEingang.KATEGORIE_CHOICES, "counts": { "gesamt": EmailEingang.objects.count(), "neu": EmailEingang.objects.filter(status="neu").count(), "unbekannt": EmailEingang.objects.filter(status="unbekannt").count(), "rechnung": EmailEingang.objects.filter(kategorie="rechnung").count(), "fehler": EmailEingang.objects.filter(status="fehler").count(), }, } return render(request, "stiftung/email_eingang/list.html", context) @login_required def email_eingang_detail(request, pk): """Detailansicht einer eingegangenen E-Mail mit Zuordnung und Rechnungserfassung.""" eingang = get_object_or_404(EmailEingang, pk=pk) if request.method == "POST": action = request.POST.get("action") if action == "assign_destinataer": dest_id = request.POST.get("destinataer_id") if dest_id: try: destinataer = Destinataer.objects.get(pk=dest_id) eingang.destinataer = destinataer eingang.kategorie = "destinataer" eingang.status = "zugewiesen" eingang.save() eingang.dokument_dateien.filter(destinataer__isnull=True).update( destinataer=destinataer ) messages.success(request, f"E-Mail wurde {destinataer} zugeordnet.") except Destinataer.DoesNotExist: messages.error(request, "Destinataer nicht gefunden.") return redirect("stiftung:email_eingang_detail", pk=pk) elif action == "erfasse_rechnung": # Erstelle Verwaltungskosten-Eintrag aus Email bezeichnung = request.POST.get("bezeichnung", eingang.betreff[:200]).strip() betrag = request.POST.get("betrag", "0").strip().replace(",", ".") kategorie = request.POST.get("vk_kategorie", "rechnung_intern") lieferant = request.POST.get("lieferant", eingang.absender_name or eingang.absender_email).strip() rechnungsnummer = request.POST.get("rechnungsnummer", "").strip() try: from decimal import Decimal vk = Verwaltungskosten( bezeichnung=bezeichnung[:200], kategorie=kategorie, betrag=Decimal(betrag) if betrag else Decimal("0"), datum=eingang.eingangsdatum.date(), lieferant_firma=lieferant[:200], rechnungsnummer=rechnungsnummer[:100], status="erhalten", beschreibung=f"Automatisch erfasst aus E-Mail-Eingang.\nBetreff: {eingang.betreff}\nAbsender: {eingang.absender_email}", ) vk.save() # Verknuepfe Email mit Verwaltungskosten eingang.verwaltungskosten = vk eingang.kategorie = "rechnung" eingang.status = "rechnung_erfasst" eingang.save() # Verknuepfe angehaengte Dokumente mit Verwaltungskosten for dok in eingang.dokument_dateien.all(): dok.verwaltungskosten = vk dok.kontext = "rechnung" dok.save() messages.success(request, f'Rechnung "{bezeichnung}" erfasst (€{betrag}).') except Exception as exc: messages.error(request, f"Fehler beim Erfassen der Rechnung: {exc}") return redirect("stiftung:email_eingang_detail", pk=pk) elif action == "set_kategorie": new_kategorie = request.POST.get("kategorie", "") if new_kategorie in dict(EmailEingang.KATEGORIE_CHOICES): eingang.kategorie = new_kategorie eingang.save() messages.success(request, f"Kategorie auf '{dict(EmailEingang.KATEGORIE_CHOICES)[new_kategorie]}' gesetzt.") return redirect("stiftung:email_eingang_detail", pk=pk) elif action == "mark_verarbeitet": eingang.status = "verarbeitet" eingang.notizen = request.POST.get("notizen", eingang.notizen) eingang.save() messages.success(request, "E-Mail als verarbeitet markiert.") return redirect("stiftung:email_eingang_list") elif action == "save_notizen": eingang.notizen = request.POST.get("notizen", "") eingang.save() messages.success(request, "Notizen gespeichert.") return redirect("stiftung:email_eingang_detail", pk=pk) # DMS-Dokumente dms_dokumente = eingang.dokument_dateien.all().order_by("erstellt_am") # Alle aktiven Destinataere fuer manuelle Zuordnung alle_destinataere = Destinataer.objects.filter(aktiv=True).order_by("nachname", "vorname") context = { "title": f"E-Mail-Eingang: {eingang}", "eingang": eingang, "dms_dokumente": dms_dokumente, "alle_destinataere": alle_destinataere, "vk_kategorie_choices": Verwaltungskosten.KATEGORIE_CHOICES, } return render(request, "stiftung/email_eingang/detail.html", context) @login_required def email_eingang_poll_trigger(request): """Loest den IMAP-Poll manuell aus – sucht alle E-Mails der letzten 30 Tage.""" if request.method == "POST": from stiftung.tasks import poll_emails try: result = poll_emails.apply(kwargs={"search_all_recent_days": 30}).get(timeout=300) processed = result.get("processed", 0) if isinstance(result, dict) else 0 if result and result.get("status") == "skipped": messages.warning(request, "IMAP ist nicht konfiguriert. Bitte Einstellungen unter Administration → E-Mail / IMAP prüfen.") elif processed > 0: error_count = result.get("errors", 0) if isinstance(result, dict) else 0 if error_count > 0: messages.warning(request, f"{processed} E-Mail(s) importiert, aber {error_count} Fehler aufgetreten. Bitte Logs prüfen.") else: messages.success(request, f"{processed} neue E-Mail(s) importiert.") else: error_count = result.get("errors", 0) if isinstance(result, dict) else 0 if error_count > 0: messages.warning(request, f"Keine neuen E-Mails importiert, aber {error_count} Fehler aufgetreten. Bitte Logs prüfen.") else: messages.info(request, "Keine neuen E-Mails gefunden.") except Exception as exc: messages.error(request, f"Fehler beim E-Mail-Abruf: {exc}") return redirect("stiftung:email_eingang_list") @login_required def email_eingang_delete(request, pk): """Loescht eine eingegangene E-Mail.""" eingang = get_object_or_404(EmailEingang, pk=pk) if request.method == "POST": betreff = eingang.betreff or "(kein Betreff)" eingang.delete() messages.success(request, f'E-Mail "{betreff}" wurde gelöscht.') return redirect("stiftung:email_eingang_list") return redirect("stiftung:email_eingang_detail", pk=pk) # ============================================================ # Veranstaltungsmodul # ============================================================