import logging from typing import Any from dataclasses import dataclass from django.db import models from django.db import transaction from django.template import ( Template, Context ) from django.core.mail import EmailMessage from django.conf import settings logger = logging.getLogger(__name__) @dataclass class Attachment: name: str content: Any contenttype: str def send_mail( to: list[str], attachments: list[Attachment], subject: str, content: str, sender_email: str = settings.DEFAULT_FROM_EMAIL ): message = EmailMessage( subject=subject, body=content, from_email=sender_email, to=to ) message.content_subtype = 'html' for attachment in attachments: message.attach(attachment.name, attachment.content, attachment.contenttype) sent = bool(message.send()) if not sent: logger.exception(f"Sending email to {to} with subject {subject} caused an exception") return sent class MailTemplate(models.Model): template_name = models.CharField(max_length=255, unique=True) template = models.FileField( upload_to="mail_templates" ) def delete(self, *args, **kwargs): # delete file super().delete(*args, **kwargs) @transaction.on_commit def remove_template_file(self): self.template.delete() def load_and_process_template(self, context: dict|Context): if not self.template: logger.exception( f"Template file is missing for template with "+ f"pk={self.pk}, template_name={self.template_name}" ) raise FileNotFoundError("Template file is missing") if isinstance(context, dict): context = Context(context) with open(self.template.path, "r", encoding="utf-8") as f: content = f.read() template = Template(content) return template.render(context) class OutgoingEmailManager(models.Manager): def send( self, template_name: str, subject: str, recipient: str, context: dict | Context, sender:str, attachments: list[Attachment] = None ): template = MailTemplate.objects.get(template_name=template_name) outgoing_email = self.create( template=template, recipient=recipient, subject=subject, sender=sender ) attachments = attachments or [] # send email sent = send_mail( to=[recipient], sender_email=sender, subject=subject, content=template.load_and_process_template(context), attachments=attachments ) outgoing_email.sent = sent outgoing_email.save() return outgoing_email class OutgoingEmail(models.Model): subject = models.CharField(max_length=255) template = models.ForeignKey(MailTemplate, on_delete=models.CASCADE) sender = models.EmailField() recipient = models.EmailField() sent = models.BooleanField(default=False) objects = OutgoingEmailManager()