Code

Ticket #17193: send_templated_mail.diff

File send_templated_mail.diff, 5.2 KB (added by tomchristie, 2 years ago)
Line 
1diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py
2index 9f97cae..db31ee4 100644
3--- a/django/shortcuts/__init__.py
4+++ b/django/shortcuts/__init__.py
5@@ -4,12 +4,14 @@ of MVC. In other words, these functions/classes introduce controlled coupling
6 for convenience's sake.
7 """
8 
9-from django.template import loader, RequestContext
10+from django.template import loader, Context, RequestContext
11+from django.template.loader_tags import BlockNode, ExtendsNode
12 from django.http import HttpResponse, Http404
13 from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
14 from django.db.models.manager import Manager
15 from django.db.models.query import QuerySet
16-from django.core import urlresolvers
17+from django.core import urlresolvers, mail
18+from django.utils.html import strip_tags
19 
20 def render_to_response(*args, **kwargs):
21     """
22@@ -128,3 +130,124 @@ def get_list_or_404(klass, *args, **kwargs):
23         raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
24     return obj_list
25 
26+
27+def _get_node(template, name):
28+    """
29+    Get a named node from a template.
30+    Returns `None` if a node with the given name does not exist.
31+    """
32+    for node in template:
33+        if isinstance(node, BlockNode) and node.name == name:
34+            return node
35+        elif isinstance(node, ExtendsNode):
36+            return _get_node(node.nodelist, name)
37+    return None
38+
39+
40+def _render_node(template, node_name, context):
41+    """
42+    Shortcut to render a named node from a template, using the given context.
43+    Returns `None` if a node with the given name does not exist.
44+
45+    Note that leading and trailing whitespace is stripped from the output.
46+    """
47+    node = _get_node(template, node_name)
48+    if node is None:
49+        return None
50+    return node.render(context).strip()
51+
52+
53+def _create_message(subject, plain, html,
54+                    from_email, recipient_list, connection):
55+    """
56+    Return an EmailMessage instance, containing either a plaintext
57+    representation, or a multipart html/plaintext representation.
58+    """
59+    if html:
60+        message = mail.EmailMultiAlternatives(
61+            subject or '',
62+            plain or '',
63+            from_email,
64+            recipient_list,
65+            connection=connection,
66+        )
67+        message.attach_alternative(html, 'text/html')
68+
69+    else:
70+        message = mail.EmailMessage(
71+            subject or '',
72+            plain or '',
73+            from_email,
74+            recipient_list,
75+            connection=connection,
76+        )
77+
78+    return message
79+
80+
81+def _render_mail(template_name, from_email, recipient_list,
82+                 dictionary=None, context_instance=None,
83+                 fail_silently=False,
84+                 auth_user=None, auth_password=None,
85+                 connection=None):
86+    """
87+    Returns an EmailMessage instance, rendering the subject and body of the
88+    email from a template.
89+
90+    The template should contain a block named 'subject',
91+    and either/both of a 'plain' and/or 'html' block.
92+
93+    If only the 'plain' block exists, a plaintext email will be sent.
94+
95+    If only the 'html' block exists, the plaintext component will be
96+      automatically generated from the html, and a multipart email will be sent.
97+
98+    If both the 'plain' and 'html' blocks exist, a multipart email will be sent.
99+
100+    Required arguments:
101+    `template_name` - The template that should be used to render the email.
102+    `from_email` - The sender's email address.
103+    `recipient_list` - A list of reciepient's email addresses.
104+
105+    Optional arguments:
106+    `dictionary` - The context dictionary used to render the template.
107+                   By default, this is an empty dictionary.
108+    `context_instance` - The Context instance used to render the template.
109+                         By default, the template will be rendered with a
110+                         Context instance (filled with values from dictionary).
111+    `fail_silently` - As in Django's send_mail.
112+    `auth_user`     - As in Django's send_mail.
113+    `auth_password` - As in Django's send_mail.
114+    `connection`    - As in Django's send_mail.
115+    """
116+    if dictionary is None:
117+        dictionary = {}
118+    if context_instance is None:
119+        context_instance = Context()
120+    if connection is None:
121+        connection = mail.get_connection(username=auth_user,
122+                                         password=auth_password,
123+                                         fail_silently=fail_silently)
124+
125+    context_instance.update(dictionary)
126+    template = loader.get_template(template_name)
127+
128+    subject = _render_node(template, 'subject', context_instance)
129+    plain = _render_node(template, 'plain', context_instance)
130+    html = _render_node(template, 'html', context_instance)
131+
132+    # Always strip newlines from subject
133+    if subject is not None:
134+        subject = ' '.join(subject.splitlines())
135+
136+    # Auto-generate plaintext if no 'plain' block exists
137+    if plain is None and not html is None:
138+        plain = strip_tags(html)
139+
140+    message = _create_message(subject, plain, html,
141+                              from_email, recipient_list, connection)
142+    return message
143+
144+
145+def send_templated_mail(*args, **kwargs):
146+    return _render_mail(*args, **kwargs).send()