Created
August 7, 2013 12:11
-
-
Save danielrichman/6173537 to your computer and use it in GitHub Desktop.
Sending emails, bodies generated by jinja2, with a utility to wrap text nicely.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def send_email(template, recipient, | |
sender=("Jinja email Gist", "[email protected]"), | |
**kwargs): | |
""" | |
Send an email, acquiring its payload by rendering a jinja2 template | |
:type template: :class:`str` | |
:param template: name of the template file in ``templates/emails`` to use | |
:type recipient: :class:`tuple` (:class:`str`, :class:`str`) | |
:param recipient: 'To' (name, email) | |
:type sender: :class:`tuple` (:class:`str`, :class:`str`) | |
:param sender: 'From' (name, email) | |
* `recipient` and `sender` are made available to the template as variables | |
* In any email tuple, name may be ``None`` | |
* The subject is retrieved from a sufficiently-global template variable; | |
typically set by placing something like | |
``{% set subject = "My Subject" %}`` | |
at the top of the template used (it may be inside some blocks | |
(if, elif, ...) but not others (rewrap, block, ...). | |
If it's not present, it defaults to "My Subject". | |
* With regards to line lengths: :class:`email.mime.text.MIMEText` will | |
(at least, in 2.7) encode the body of the text in base64 before sending | |
it, text-wrapping the base64 data. You will therefore not have any | |
problems with SMTP line length restrictions, and any concern to line | |
lengths is purely aesthetic or to be nice to the MUA. | |
:class:`RewrapExtension` may be used to wrap blocks of text nicely. | |
Note that ``{{ variables }}`` in manually wrapped text can cause | |
problems! | |
""" | |
if flask.current_app: | |
# use the app's env if it's available, so that url_for may be used | |
jinja_env = flask.jinja_env | |
else: | |
path = os.path.join(os.path.dirname(__file__), '..', 'templates') | |
loader = jinja2.FileSystemLoader(path) | |
jinja_env = jinja2.Environment(loader=loader, | |
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']) | |
jinja_env = jinja_env.overlay(autoescape=False, | |
extensions=[RewrapExtension]) | |
def _jinja2_email(name, email): | |
if name is None: | |
hint = 'name was not set for email {0}'.format(email) | |
name = jinja_env.undefined(name='name', hint=hint) | |
return {"name": name, "email": email} | |
template = jinja_env.get_template(os.path.join('emails', template)) | |
kwargs["sender"] = _jinja2_email(*sender) | |
kwargs["recipient"] = _jinja2_email(*recipient) | |
rendered = template.make_module(vars=kwargs) | |
message = email.mime.text.MIMEText(unicode(rendered), _charset='utf-8') | |
message["from"] = email.utils.formataddr(sender) | |
message["to"] = email.utils.formataddr(recipient) | |
message["subject"] = getattr(rendered, "subject", "My Subject") | |
s = smtplib.SMTP('localhost') | |
s.sendmail(sender[1], [recipient[1]], message.as_string()) | |
s.quit() | |
class RewrapExtension(jinja2.ext.Extension): | |
""" | |
The :mod:`jinja2` extension adds a ``{% rewrap %}...{% endrewrap %}`` block | |
The contents in the rewrap block are modified as follows | |
* whitespace at the start and end of lines is discarded | |
* the contents are split into 'paragraphs' separated by blank lines | |
* empty paragraphs are discarded - so two blank lines is equivalent to | |
one blank line, and blank lines at the start and end of the block | |
are effectively discarded | |
* lines in each paragraph are joined to one line, and then text wrapped | |
to lines `width` characters wide | |
* paragraphs are re-joined into text, with blank lines insert in between | |
them | |
It does not insert a newline at the end of the block, which means that:: | |
Something, then a blank line | |
{% block rewrap %} | |
some text | |
{% endblock %} | |
After another blank line | |
will do what you expect. | |
You may optionally specify the width like so:: | |
{% rewrap 72 %} | |
It defaults to 78. | |
""" | |
tags = set(['rewrap']) | |
def parse(self, parser): | |
# first token is 'rewrap' | |
lineno = parser.stream.next().lineno | |
if parser.stream.current.type != 'block_end': | |
width = parser.parse_expression() | |
else: | |
width = jinja2.nodes.Const(78) | |
body = parser.parse_statements(['name:endrewrap'], drop_needle=True) | |
call = self.call_method('_rewrap', [width]) | |
return jinja2.nodes.CallBlock(call, [], [], body).set_lineno(lineno) | |
def _rewrap(self, width, caller): | |
contents = caller() | |
lines = [line.strip() for line in contents.splitlines()] | |
lines.append('') | |
paragraphs = [] | |
start = 0 | |
while start != len(lines): | |
end = lines.index('', start) | |
if start != end: | |
paragraph = ' '.join(lines[start:end]) | |
paragraphs.append(paragraph) | |
start = end + 1 | |
new_lines = [] | |
for paragraph in paragraphs: | |
if new_lines: | |
new_lines.append('') | |
new_lines += textwrap.wrap(paragraph, width) | |
# under the assumption that there will be a newline immediately after | |
# the endrewrap block, don't put a newline on the end. | |
return '\n'.join(new_lines) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment