Add Vorlagen editor, upload portal, onboarding, and participant import command
- Dokument-Vorlagen-Editor: create/edit/reset document templates (admin) - Upload-Portal: public portal for Nachweis uploads via token - Onboarding: invite Destinatäre via email with multi-step wizard - Bestätigungsschreiben: preview and send confirmation letters - Email settings: SMTP configuration UI - Management command: import_veranstaltung_teilnehmer for bulk participant import Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1878,7 +1878,50 @@ def email_settings(request):
|
||||
},
|
||||
)
|
||||
|
||||
imap_settings = AppConfiguration.objects.filter(category="email", is_active=True).order_by("order")
|
||||
# Ensure SMTP settings exist in DB (auto-init)
|
||||
smtp_defaults = [
|
||||
("smtp_host", "SMTP Server", "Hostname des SMTP-Servers (z.B. smtp.ionos.de)", "smtp.ionos.de", "text", 10),
|
||||
("smtp_port", "SMTP Port", "Port des SMTP-Servers (465 für SSL, 587 für STARTTLS)", "465", "number", 11),
|
||||
("smtp_user", "SMTP Benutzername", "Benutzername / E-Mail-Adresse für die SMTP-Anmeldung", "", "text", 12),
|
||||
("smtp_password", "SMTP Passwort", "Passwort für die SMTP-Anmeldung", "", "password", 13),
|
||||
("smtp_use_ssl", "SSL/TLS verwenden (SMTP)", "Sichere Verbindung zum SMTP-Server (empfohlen für Port 465)", "True", "boolean", 14),
|
||||
("smtp_from_email", "Absenderadresse", "Absenderadresse für ausgehende E-Mails", "buero@vhtv-stiftung.de", "text", 15),
|
||||
]
|
||||
for key, name, desc, default, stype, order in smtp_defaults:
|
||||
AppConfiguration.objects.get_or_create(
|
||||
key=key,
|
||||
defaults={
|
||||
"display_name": name,
|
||||
"description": desc,
|
||||
"value": default,
|
||||
"default_value": default,
|
||||
"setting_type": stype,
|
||||
"category": "email",
|
||||
"order": order,
|
||||
},
|
||||
)
|
||||
|
||||
# Ensure notification settings exist in DB (auto-init)
|
||||
notification_defaults = [
|
||||
("notification_email", "Benachrichtigungs-E-Mail", "Empfänger für interne Benachrichtigungen (z.B. neue Onboardings). Wenn leer, wird die Absenderadresse verwendet.", "", "text", 20),
|
||||
]
|
||||
for key, name, desc, default, stype, order in notification_defaults:
|
||||
AppConfiguration.objects.get_or_create(
|
||||
key=key,
|
||||
defaults={
|
||||
"display_name": name,
|
||||
"description": desc,
|
||||
"value": default,
|
||||
"default_value": default,
|
||||
"setting_type": stype,
|
||||
"category": "email",
|
||||
"order": order,
|
||||
},
|
||||
)
|
||||
|
||||
imap_settings = AppConfiguration.objects.filter(category="email", key__startswith="imap_", is_active=True).order_by("order")
|
||||
smtp_settings = AppConfiguration.objects.filter(category="email", key__startswith="smtp_", is_active=True).order_by("order")
|
||||
notification_settings = AppConfiguration.objects.filter(category="email", key="notification_email", is_active=True).order_by("order")
|
||||
|
||||
test_result = None
|
||||
|
||||
@@ -1887,7 +1930,8 @@ def email_settings(request):
|
||||
|
||||
if action == "save":
|
||||
updated = 0
|
||||
for setting in imap_settings:
|
||||
all_email_settings = AppConfiguration.objects.filter(category="email", is_active=True)
|
||||
for setting in all_email_settings:
|
||||
field_name = f"setting_{setting.key}"
|
||||
if setting.setting_type == "boolean":
|
||||
new_val = "True" if field_name in request.POST else "False"
|
||||
@@ -1954,13 +1998,124 @@ def email_settings(request):
|
||||
"message": f"Verbindungsfehler: {e}",
|
||||
}
|
||||
|
||||
elif action == "test_smtp":
|
||||
import smtplib
|
||||
import ssl as ssl_module
|
||||
host = get_config("smtp_host")
|
||||
port = int(get_config("smtp_port", 465))
|
||||
user = get_config("smtp_user")
|
||||
password = get_config("smtp_password")
|
||||
use_ssl = get_config("smtp_use_ssl", True)
|
||||
|
||||
if not all([host, user, password]):
|
||||
test_result = {
|
||||
"success": False,
|
||||
"message": "SMTP-Server, Benutzername und Passwort müssen konfiguriert sein.",
|
||||
"section": "smtp",
|
||||
}
|
||||
else:
|
||||
try:
|
||||
if use_ssl:
|
||||
context = ssl_module.create_default_context()
|
||||
conn = smtplib.SMTP_SSL(host, port, context=context, timeout=15)
|
||||
else:
|
||||
conn = smtplib.SMTP(host, port, timeout=15)
|
||||
conn.starttls()
|
||||
conn.login(user, password)
|
||||
conn.quit()
|
||||
test_result = {
|
||||
"success": True,
|
||||
"message": f"SMTP-Verbindung erfolgreich! Angemeldet als {user}.",
|
||||
"section": "smtp",
|
||||
}
|
||||
except smtplib.SMTPAuthenticationError as e:
|
||||
test_result = {
|
||||
"success": False,
|
||||
"message": f"SMTP-Authentifizierungsfehler: {e}",
|
||||
"section": "smtp",
|
||||
}
|
||||
except Exception as e:
|
||||
test_result = {
|
||||
"success": False,
|
||||
"message": f"SMTP-Verbindungsfehler: {e}",
|
||||
"section": "smtp",
|
||||
}
|
||||
|
||||
elif action == "test_smtp_send":
|
||||
from django.core.mail import EmailMessage, get_connection
|
||||
from django.utils import timezone
|
||||
|
||||
test_email = request.POST.get("test_email", "").strip()
|
||||
if not test_email:
|
||||
test_result = {
|
||||
"success": False,
|
||||
"message": "Bitte geben Sie eine Empfänger-E-Mail-Adresse ein.",
|
||||
"section": "smtp",
|
||||
}
|
||||
else:
|
||||
host = get_config("smtp_host")
|
||||
port = int(get_config("smtp_port", 465))
|
||||
user = get_config("smtp_user")
|
||||
password = get_config("smtp_password")
|
||||
use_ssl = get_config("smtp_use_ssl", True)
|
||||
from_email = get_config("smtp_from_email", "buero@vhtv-stiftung.de")
|
||||
|
||||
if not all([host, user, password]):
|
||||
test_result = {
|
||||
"success": False,
|
||||
"message": "SMTP-Server, Benutzername und Passwort müssen konfiguriert sein.",
|
||||
"section": "smtp",
|
||||
}
|
||||
else:
|
||||
try:
|
||||
connection = get_connection(
|
||||
backend="django.core.mail.backends.smtp.EmailBackend",
|
||||
host=host,
|
||||
port=port,
|
||||
username=user,
|
||||
password=password,
|
||||
use_ssl=bool(use_ssl),
|
||||
use_tls=False,
|
||||
fail_silently=False,
|
||||
)
|
||||
now = timezone.now().strftime("%d.%m.%Y %H:%M")
|
||||
msg = EmailMessage(
|
||||
subject=f"[vHTV-Stiftung] SMTP-Test ({now})",
|
||||
body=(
|
||||
f"Dies ist eine Test-E-Mail der Stiftungsverwaltung.\n\n"
|
||||
f"Zeitpunkt: {now}\n"
|
||||
f"SMTP-Server: {host}:{port}\n"
|
||||
f"Absender: {from_email}\n\n"
|
||||
f"Wenn Sie diese E-Mail erhalten, funktioniert der E-Mail-Versand korrekt."
|
||||
),
|
||||
from_email=from_email,
|
||||
to=[test_email],
|
||||
connection=connection,
|
||||
)
|
||||
msg.send()
|
||||
test_result = {
|
||||
"success": True,
|
||||
"message": f"Test-E-Mail wurde an {test_email} gesendet! Bitte prüfen Sie den Posteingang (und Spam-Ordner).",
|
||||
"section": "smtp",
|
||||
}
|
||||
except Exception as e:
|
||||
test_result = {
|
||||
"success": False,
|
||||
"message": f"E-Mail-Versand fehlgeschlagen: {e}",
|
||||
"section": "smtp",
|
||||
}
|
||||
|
||||
# Refresh after save
|
||||
imap_settings = AppConfiguration.objects.filter(category="email", is_active=True).order_by("order")
|
||||
imap_settings = AppConfiguration.objects.filter(category="email", key__startswith="imap_", is_active=True).order_by("order")
|
||||
smtp_settings = AppConfiguration.objects.filter(category="email", key__startswith="smtp_", is_active=True).order_by("order")
|
||||
notification_settings = AppConfiguration.objects.filter(category="email", key="notification_email", is_active=True).order_by("order")
|
||||
|
||||
context = {
|
||||
"imap_settings": imap_settings,
|
||||
"smtp_settings": smtp_settings,
|
||||
"notification_settings": notification_settings,
|
||||
"test_result": test_result,
|
||||
"title": "E-Mail / IMAP Konfiguration",
|
||||
"title": "E-Mail-Konfiguration (IMAP & SMTP)",
|
||||
}
|
||||
return render(request, "stiftung/email_settings.html", context)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user