Django Templates Stored in a Database
Feb 17, 2018 11:59 · 520 words · 3 minute read
I want to have an email templates stored in my Django database so that admins can manage them without me changing the
code. The requirement is to have HTML-emails support as well as an ability to send files attached. Neither standard
Django send_mail
nor other builtin features don’t support that feature so below you can find my solution to that
problem.
The snippet below relies to the Django’s EmailMultiAlternatives
and basically wraps it into a class.
Model definition
The model provides capabilities to store the email template in the database to allow admins to manage these templates easily.
subject
*- An email subject.template_key
* - A unique identifier of the email template that could be referenced from the code.to_email
- Optional. A default email that the template will be sent to. Can be used to send email to admins.from_email
- Optional. A default email that the template will be sent from. If not specified,settings.DEFAULT_FROM_EMAIL
is used.html_template
andplain_text
- Body of the email in text and html formats.
from django import template
from django.conf import settings
from django.core.mail import send_mail, EmailMultiAlternatives
from django.db import models
from django.template import Context
class EmailTemplate(models.Model):
"""
Email templates get stored in database so that admins can
change emails on the fly
"""
subject = models.CharField(max_length=255, blank=True, null=True)
to_email = models.CharField(max_length=255, blank=True, null=True)
from_email = models.CharField(max_length=255, blank=True, null=True)
html_template = models.TextField(blank=True, null=True)
plain_text = models.TextField(blank=True, null=True)
is_html = models.BooleanField(default=False)
is_text = models.BooleanField(default=False)
# unique identifier of the email template
template_key = models.CharField(max_length=255, unique=True)
def get_rendered_template(self, tpl, context):
return self.get_template(tpl).render(context)
def get_template(self, tpl):
return template.Template(tpl)
def get_subject(self, subject, context):
return subject or self.get_rendered_template(self.subject, context)
def get_body(self, body, context):
return body or self.get_rendered_template(self._get_body(), context)
def get_sender(self):
return self.from_email or settings.DEFAULT_FROM_EMAIL
def get_recipient(self, emails, context):
return emails or [self.get_rendered_template(self.to_email, context)]
@staticmethod
def send(*args, **kwargs):
EmailTemplate._send(*args, **kwargs)
@staticmethod
def _send(template_key, context, subject=None, body=None, sender=None,
emails=None, bcc=None, attachments=None):
mail_template = EmailTemplate.objects.get(template_key=template_key)
context = Context(context)
subject = mail_template.get_subject(subject, context)
body = mail_template.get_body(body, context)
sender = sender or mail_template.get_sender()
emails = mail_template.get_recipient(emails, context)
if mail_template.is_text:
return send_mail(subject, body, sender, emails, fail_silently=not
settings.DEBUG)
msg = EmailMultiAlternatives(subject, body, sender, emails,
alternatives=((body, 'text/html'),),
bcc=bcc
)
if attachments:
for name, content, mimetype in attachments:
msg.attach(name, content, mimetype)
return msg.send(fail_silently=not (settings.DEBUG or settings.TEST))
def _get_body(self):
if self.is_text:
return self.plain_text
return self.html_template
def __str__(self):
return "<{}> {}".format(self.template_key, self.subject)
Example usage
Sending a notification to the admin about new expense request from an employee.
EmailTemplate.send('expense_notification_to_admin', {
# context object that email template will be rendered with
'expense': expense_request,
})
Or sending an invoice to the customer.
invoice_pdf = invoice.pdf_file.pdf_file_data
send_email(
# a string such as 'invoice_to_customer'
template_name,
# context that contains Customer, Invoice and other objects
ctx,
# list of receivers i.e. ['customer1@example.com', 'customer2@example.com]
emails=emails,
# attached PDF file of the invoice
attachments=[(invoice.reference, invoice_pdf, 'application/pdf')]
)
Running as a celery task
Emails can be sent via celery by adding a task to tasks.py
:
from celery import task
from core.models import EmailTemplate
@task
def send_email(*args, **kwargs):
return EmailTemplate.send(*args, **kwargs)
Enabling in an admin panel
admin.py
can look like follows:
from django.contrib import admin
from core.models import EmailTemplate
class EmailTemplateAdmin(admin.ModelAdmin):
list_display = ['template_key', 'subject', 'from_email', 'to_email']
save_as = True