"""
Templated Email
===============
Class-based, multi-part emails with template support.
TODO(nick): pynliner allows css to be written in a separate file, then
automatically inlined into the html (essential for email html messages).
There may be a significant performance penalty if doing this on the fly
(according to some random package's docs), so an alternative would be to
have a build/pre-process step that inlines css into the templates
themselves, instead of into the generated html.
"""
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.template import Context, Template
from django.template.loader import get_template
class Email(object):
"""Class-based, multi-part emails with template support.
Class attributes (to, from_email, etc.) provide a simple way to define static
default values.
default_* methods provide an easy way for subclasses to provide runtime
computed values.
Defaults are override-able by providing appropriate arguments to __init__.
Precedence: parameters > default_* methods > class attributes
Args
----
to : str or list
Recipient of the email.
from_email : str
Sender's address.
subject : str
The subject of the email. The subject is processed by the template
engine, using the same context as the email body, and therefore may
contain template tags.
template_name : str
The app path to the template, without a suffix. Email will check for
both `template_name.txt` and `template_name.html` at that location.
text_template : str
The app path to the text template to use as the body of the email. If
provided, this will override the `template_name` argument.
html_template : str
The app path to the html template to use as the body of the email. If
provided, this will override the `template_name` argument.
context : dict
The context to use when rendering templates. This will be merged with
the default context, overwriting any existing values in the default
context.
Example
-------
A simple example:
from unb.utils import email
class NewSignupEmail(email.Email):
subject = "{{email}} has signed up!"
template_name = 'signups/emails/new_signup'
def default_context(self):
return {
'email': self.to,
}
# Usage
# -----
e = NewSignupEmail(to='nick@unb.services')
e.send()
A more complicated example:
from unb.utils import email
class NewSignupEmail(email.Email):
text_template = 'signups/emails/new_signup.txt'
html_template = 'signups/emails/new_signup.html'
@property
def user(self):
return self.kwargs['user']
def default_to(self):
return self.user.email
def default_subject(self):
if self.user.name:
return self.user.name + " has signed up!"
if self.user.username:
return self.user.username + " has signed up!"
return self.subject
def default_context(self):
return {
'username': self.user.username,
'email': self.user.email,
}
# Usage
# -----
from unb.users.models import User
u = User.objects.get('username'='nick')
e = NewSignupEmail(user=u)
e.send()
"""
to = None
from_email = None
subject = ''
template_name = None
text_template = None
html_template = None
def __init__(self,
to=None,
from_email=None,
subject=None,
template_name=None,
text_template=None,
html_template=None,
context=None,
**kwargs):
self.kwargs = kwargs
self.to = to or self.default_to()
if not isinstance(self.to, list) or not isinstance(self.to, tuple):
self.to = [self.to]
self.from_email = (
from_email or self.default_from_email() or settings.DEFAULT_FROM_EMAIL)
self.subject = subject or self.default_subject()
self.template_name = template_name or self.default_template_name()
self.text_template = text_template or self.default_text_template()
self.html_template = html_template or self.default_html_template()
ctx = self.default_context()
if context:
ctx.update(context)
self.context = Context(ctx)
def _update_context(self, ctx):
if ctx:
self.context.update(ctx)
return self.context
def default_to(self):
return self.to
def default_from_email(self):
return self.from_email
def default_subject(self):
return self.subject
def default_template_name(self):
return self.template_name
def default_text_template(self):
return self.text_template
def default_html_template(self):
return self.html_template
def default_context(self):
return {}
def render_string(self, string, ctx):
if string:
return Template(string).render(ctx)
else:
return ''
def render_template(self, template, ctx):
if template:
template = get_template(template)
return template.render(ctx)
else:
return ''
def message(self, context=None):
"""Get a compiled message without sending it."""
ctx = self._update_context(context)
# Render templates
subject = self.render_string(self.subject, ctx)
text = self.render_template(self.text_template, ctx)
html = self.render_template(self.html_template, ctx)
# Construct the message
msg = EmailMultiAlternatives(
to=self.to,
from_email=self.from_email,
subject=subject,
body=text)
if html:
msg.attach_alternative(html, 'text/html')
return msg
def send(self, context=None):
"""Compile and send the message."""
msg = self.message(context=context)
msg.send()