eptm_dashboard/src/email_sender.py

92 lines
3.1 KiB
Python

"""Envoi d'emails SMTP et utilitaires de template (Office 365 / Brevo / Gmail)."""
from __future__ import annotations
import re
import smtplib
from datetime import datetime
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from src.db import Absence, Apprenti
def parse_sender(sender: str) -> tuple[str, str]:
"""Extrait (nom, email) depuis 'Nom Affiché <email>' ou 'email'."""
m = re.match(r'^(.+?)\s*<([^>]+)>\s*$', sender.strip())
if m:
return m.group(1).strip(), m.group(2).strip()
return "", sender.strip()
def render_template(template: str, variables: dict) -> str:
"""Substitue les variables {prenom}, {nom}, etc. dans le template."""
try:
return template.format_map(variables)
except KeyError:
return template
def build_template_vars(
apprenti: "Apprenti",
absences: list["Absence"],
semestre_label: str = "",
) -> dict:
"""Construit le dictionnaire de variables pour les templates email."""
nb_total = len(absences)
nb_excusees = sum(1 for a in absences if a.statut == "excusee")
nb_non_exc = sum(1 for a in absences if a.statut == "non_excusee")
nb_a_traiter = sum(1 for a in absences if a.statut == "a_traiter")
return {
"prenom": apprenti.prenom,
"nom": apprenti.nom,
"nom_complet": f"{apprenti.nom} {apprenti.prenom}",
"classe": apprenti.classe,
"nb_absences": nb_total,
"nb_excusees": nb_excusees,
"nb_non_excusees": nb_non_exc,
"nb_a_traiter": nb_a_traiter,
"semestre": semestre_label or "",
"date_du_jour": datetime.now().strftime("%d.%m.%Y"),
}
def send_email(
smtp_host: str,
smtp_port: int,
smtp_login: str,
smtp_password: str,
smtp_sender: str,
to_email: str,
subject: str,
body: str,
attachments: "list[tuple[bytes, str]] | None" = None,
) -> None:
"""Envoie un email avec pièces jointes PDF optionnelles.
smtp_login : identifiant d'authentification SMTP (peut différer de l'expéditeur).
smtp_sender : adresse expéditeur, format 'Nom <email>' ou 'email'.
attachments : liste de (pdf_bytes, filename).
Lève une exception en cas d'échec (SMTPException, OSError).
"""
_from_name, _from_email = parse_sender(smtp_sender)
msg = MIMEMultipart()
msg["From"] = f"{_from_name} <{_from_email}>" if _from_name else _from_email
msg["To"] = to_email
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain", "utf-8"))
for pdf_bytes, pdf_filename in (attachments or []):
part = MIMEApplication(pdf_bytes, _subtype="pdf")
part.add_header("Content-Disposition", "attachment", filename=pdf_filename)
msg.attach(part)
with smtplib.SMTP(smtp_host, smtp_port, timeout=20) as smtp:
smtp.ehlo()
smtp.starttls()
smtp.ehlo()
smtp.login(smtp_login, smtp_password)
smtp.sendmail(_from_email, [to_email], msg.as_string())