feat: add HTML email body for --send (#48)
Add email.html config flag (default false). When enabled, --send includes an HTML version of the report body generated via HTMLFormatter alongside the plain-text part in a multipart/alternative message. - EmailConfig gains html: bool field - mailer.build_message/send_report accept rows for HTML generation - CLI passes rows to send_report - --init-config generates email.html: false - README.md and docs/CONFIG.md updated Bump version to 1.10.0. Closes #48
This commit is contained in:
@@ -6,10 +6,11 @@ import smtplib
|
||||
from email.mime.application import MIMEApplication
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
|
||||
from .client import RedmineAPIError
|
||||
from .config import EmailConfig
|
||||
from .types import ReportRow
|
||||
|
||||
# Use 8bit transfer encoding for UTF-8 so non-ASCII text (e.g. Russian)
|
||||
# appears literally in MIME output instead of base64.
|
||||
@@ -33,13 +34,22 @@ def _resolve_mime_type(file_path: str) -> str:
|
||||
return MIME_TYPES.get(ext, "application/octet-stream")
|
||||
|
||||
|
||||
def _build_html_body(rows: List[ReportRow]) -> str:
|
||||
"""Генерирует HTML-версию тела письма через HTMLFormatter."""
|
||||
from .formatters.html import HTMLFormatter
|
||||
|
||||
formatter = HTMLFormatter()
|
||||
return formatter.format(rows)
|
||||
|
||||
|
||||
def build_message(
|
||||
email_config: EmailConfig,
|
||||
file_path: str,
|
||||
author: str,
|
||||
period: str,
|
||||
rows: List[ReportRow],
|
||||
) -> MIMEMultipart:
|
||||
"""Формирует MIME-письмо с подстановками и вложением."""
|
||||
"""Формирует MIME-письмо с подстановками, телами и вложением."""
|
||||
subject = email_config.subject.replace("{author}", author).replace(
|
||||
"{period}", period
|
||||
)
|
||||
@@ -54,7 +64,15 @@ def build_message(
|
||||
if email_config.cc:
|
||||
msg["Cc"] = ", ".join(email_config.cc)
|
||||
|
||||
msg.attach(MIMEText(body, "plain", "utf-8"))
|
||||
# Тела письма: plain-text всегда, HTML по флагу
|
||||
body_container = MIMEMultipart("alternative")
|
||||
body_container.attach(MIMEText(body, "plain", "utf-8"))
|
||||
|
||||
if email_config.html:
|
||||
html_body = _build_html_body(rows)
|
||||
body_container.attach(MIMEText(html_body, "html", "utf-8"))
|
||||
|
||||
msg.attach(body_container)
|
||||
|
||||
if email_config.attach:
|
||||
try:
|
||||
@@ -81,6 +99,7 @@ def send_report(
|
||||
file_path: str,
|
||||
author: str,
|
||||
period: str,
|
||||
rows: List[ReportRow],
|
||||
) -> None:
|
||||
"""Отправляет сгенерированный отчёт по email через SMTP.
|
||||
|
||||
@@ -89,12 +108,13 @@ def send_report(
|
||||
file_path: Путь к файлу отчёта для вложения.
|
||||
author: Имя автора (для подстановки в тему/тело).
|
||||
period: Строка периода (для подстановки в тему/тело).
|
||||
rows: Строки отчёта (для HTML-версии тела письма).
|
||||
|
||||
Raises:
|
||||
RedmineAPIError: При любой ошибке соединения или отправки.
|
||||
"""
|
||||
smtp_cfg = email_config.smtp
|
||||
msg = build_message(email_config, file_path, author, period)
|
||||
msg = build_message(email_config, file_path, author, period, rows)
|
||||
all_recipients = (
|
||||
list(email_config.to) + list(email_config.cc) + list(email_config.bcc)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user