92 lines
3.1 KiB
Python
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())
|