unified approval mails

This commit is contained in:
Oliver Zander 2025-10-17 15:14:16 +02:00
parent 5a5962b619
commit b3484965b3
6 changed files with 29 additions and 50 deletions

View File

@ -4,7 +4,7 @@ from django.contrib import admin
from django.db import models, transaction from django.db import models, transaction
from django.http import HttpResponse from django.http import HttpResponse
from input.utils.mail import send_decision_mail, send_staff_decision_mail from input.utils.mail import send_decision_mails
from .forms import BaseProjectForm from .forms import BaseProjectForm
from .models import ( from .models import (
@ -181,15 +181,10 @@ class ProjectRequestAdmin(BaseProjectAdmin):
if obj.granted is None: if obj.granted is None:
return None return None
transaction.on_commit(lambda: self.send_decision_mails(obj)) transaction.on_commit(lambda: send_decision_mails(obj))
return obj.granted return obj.granted
@staticmethod
def send_decision_mails(obj):
send_decision_mail(obj, TYPE_PROJ, obj.granted)
send_staff_decision_mail(obj, TYPE_PROJ, obj.granted)
@admin.register(ProjectDeclined) @admin.register(ProjectDeclined)
class ProjectDeclinedAdmin(BaseProjectAdmin): class ProjectDeclinedAdmin(BaseProjectAdmin):

View File

@ -1,9 +1,8 @@
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template from django.template.loader import get_template
from django.utils.html import strip_tags
from input.models import TYPE_CHOICES from input.models import Project
from .attachments import collect_attachment_paths, attach_files from .attachments import collect_attachment_paths, attach_files
@ -12,8 +11,9 @@ __all__ = [
'send_email', 'send_email',
'collect_attachment_paths', 'collect_attachment_paths',
'attach_files', 'attach_files',
'send_decision_mail', 'send_applicant_decision_mail',
'send_staff_decision_mail', 'send_staff_decision_mail',
'send_decision_mails',
] ]
@ -38,63 +38,47 @@ def send_email(template_name: str, context: dict, subject: str, *recipients: str
return build_email(template_name, context, subject, *recipients, **kwargs).send(fail_silently) return build_email(template_name, context, subject, *recipients, **kwargs).send(fail_silently)
def _type_labels(choice: str): def get_decision_mail_context(obj: Project):
"""
Resolve the human-readable type label.
Returns (HTML label, plain text label).
"""
html = TYPE_CHOICES.get(choice, choice)
plain = strip_tags(html)
return html, plain
def _decision_context(obj, choice_code: str) -> dict:
""" """
Build a minimal, consistent context for decision mails (applicant & staff). Build a minimal, consistent context for decision mails (applicant & staff).
Also exposes the full project object as 'project' for template access.
""" """
type_html, type_plain = _type_labels(choice_code)
realname = getattr(obj, 'realname', '') or getattr(obj, 'email', '')
return { return {
'data': {
'realname': realname,
'type_label': type_html,
'type_label_plain': type_plain,
'name': getattr(obj, 'name', None),
},
'project': obj, 'project': obj,
'data': {
'realname': obj.realname or obj.email,
'name': obj.name,
},
} }
def send_decision_mail(obj, choice_code: str, granted: bool) -> None: def send_base_decision_mail(obj: Project, scope: str, subject: str, recipient: str):
context = get_decision_mail_context(obj)
decision = 'granted' if obj.granted else 'denied'
decision_label = 'bewilligt' if obj.granted else 'abgelehnt'
subject = subject.format(name=obj.name, decision=decision_label)
return send_email(f'approval_{scope}_{decision}', context, subject, recipient)
def send_applicant_decision_mail(obj: Project):
""" """
Send a decision email to the applicant after manual approval/denial. Send a decision email to the applicant after manual approval/denial.
Uses: input/approval_granted.(txt|html) or input/approval_denied.(txt|html)
""" """
recipient = getattr(obj, 'email', None)
if not recipient:
return # no recipient -> skip
ctx = _decision_context(obj, choice_code) if recipient := obj.email:
template_suffix = 'granted' if granted else 'denied' return send_base_decision_mail(obj, 'applicant', 'Deine Förderanfrage „{name} {decision}', recipient)
project_name = getattr(obj, 'name', None) or '(ohne Projektnamen)' return 0
decision_word = 'bewilligt' if granted else 'abgelehnt'
subject = f'Deine Förderanfrage „{project_name} {decision_word}'
return send_email(f'approval_{template_suffix}', ctx, subject, recipient)
def send_staff_decision_mail(obj, choice_code: str, granted: bool) -> None: def send_staff_decision_mail(obj: Project):
""" """
Send a decision email to the internal team (staff) after approval/denial. Send a decision email to the internal team (staff) after approval/denial.
Uses: input/approval_granted_staff.(txt|html) or input/approval_denied_staff.(txt|html)
""" """
ctx = _decision_context(obj, choice_code)
template_suffix = 'granted' if granted else 'denied'
project_name = getattr(obj, 'name', None) or '(ohne Projektnamen)' return send_base_decision_mail(obj, 'staff', 'Entscheidung: {name} ({decision})', settings.IF_EMAIL)
decision_word = 'bewilligt' if granted else 'abgelehnt'
subject = f'Entscheidung: {project_name} ({decision_word})'
return send_email(f'approval_{template_suffix}_staff', ctx, subject, settings.IF_EMAIL)
def send_decision_mails(obj: Project):
return send_applicant_decision_mail(obj) + send_staff_decision_mail(obj)