Étape 10 : envoi d'emails par SMTP
- app/email/ : sender (smtplib, STARTTLS ou SSL, connexion réutilisée pour un
lot) + templates (rendu, textes dans locales/fr.json). EMAIL_ENABLED=false
(défaut) ⇒ journalisé seulement, sans destinataire ni corps (RGPD).
- Lien magique : POST .../auth/magic-link envoie désormais l'email
(PUBLIC_BASE_URL + magic_link_path + ?token=). Helper app/auth/magic_link.py
partagé. Sans SMTP, le jeton non nominatif reste journalisé pour le dev.
- Récapitulatif de planning : POST /editions/{id}/notifications/planning
(COORDINATION). subject/body sont des modèles ({prenom}, {nom}, {nb_creneaux},
{nb_heures}, {creneaux}, {lien_connexion} → lien magique personnel émis).
Cible tous les bénévoles avec email ou une liste. Envoi en BackgroundTasks.
- Config : PUBLIC_BASE_URL, EMAIL_ENABLED, SMTP_* ; .env.example,
docker-compose (dev + prod), deploy/README.
- 9 tests (sender activé/désactivé, rendu, notifications : contenu, gabarit par
défaut, lien magique embarqué, sous-ensemble, réservé COORDINATION).
137 tests au total ; ruff + alembic check verts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
977bd4dc34
commit
b268e308ab
@@ -0,0 +1,50 @@
|
||||
"""Rendu des emails. Les textes vivent dans `locales/fr.json` (`email.*`)."""
|
||||
|
||||
from app.i18n import t
|
||||
|
||||
# Variables autorisées dans un modèle d'email de planning saisi par la
|
||||
# coordination.
|
||||
_PLANNING_PLACEHOLDERS = (
|
||||
"prenom",
|
||||
"nom",
|
||||
"nb_creneaux",
|
||||
"nb_heures",
|
||||
"creneaux",
|
||||
"lien_connexion",
|
||||
)
|
||||
|
||||
|
||||
def render_magic_link(*, link: str, minutes: int) -> tuple[str, str]:
|
||||
return (
|
||||
t("email.magic_link.subject"),
|
||||
t("email.magic_link.body", link=link, minutes=minutes),
|
||||
)
|
||||
|
||||
|
||||
def _fill(template: str, values: dict[str, object]) -> str:
|
||||
out = template
|
||||
for key in _PLANNING_PLACEHOLDERS:
|
||||
out = out.replace("{" + key + "}", str(values.get(key, "")))
|
||||
return out
|
||||
|
||||
|
||||
def render_planning(
|
||||
*,
|
||||
subject_template: str,
|
||||
body_template: str,
|
||||
first_name: str,
|
||||
last_name: str,
|
||||
shift_count: int,
|
||||
hours: float,
|
||||
shifts_text: str,
|
||||
login_link: str,
|
||||
) -> tuple[str, str]:
|
||||
values: dict[str, object] = {
|
||||
"prenom": first_name,
|
||||
"nom": last_name,
|
||||
"nb_creneaux": shift_count,
|
||||
"nb_heures": hours,
|
||||
"creneaux": shifts_text,
|
||||
"lien_connexion": login_link,
|
||||
}
|
||||
return _fill(subject_template, values), _fill(body_template, values)
|
||||
Reference in New Issue
Block a user