Code

Ticket #1541: mail.diff

File mail.diff, 5.2 KB (added by nick.lane.au@…, 7 years ago)

Patch to add attachments to django.core.mail

Line 
1--- django/core/mail.py Mon Jun 18 10:45:40 2007
2+++ django/core/mail.py Mon Jun 18 11:11:08 2007
3@@ -4,6 +4,10 @@
4 
5 from django.conf import settings
6 from email.MIMEText import MIMEText
7+from email.mime.multipart import MIMEMultipart
8+from email.mime.base import MIMEBase
9+from email import Encoders
10+import mimetypes
11 from email.Header import Header
12 from email.Utils import formatdate
13 from email import Charset
14@@ -13,6 +17,9 @@
15 import time
16 import random
17 
18+# Default MIME type to use for attachments if it cannot be guessed.
19+DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
20+
21 # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
22 # some spam filters.
23 Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
24@@ -64,6 +71,15 @@
25             val = Header(val, settings.DEFAULT_CHARSET)
26         MIMEText.__setitem__(self, name, val)
27 
28+class SafeMIMEMultipart(MIMEMultipart):
29+    def __setitem__(self, name, val):
30+        "Forbids multi-line headers, to prevent header injection."
31+        if '\n' in val or '\r' in val:
32+            raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
33+        if name == 'Subject':
34+            val = Header(val, settings.DEFAULT_CHARSET)
35+        MIMEMultipart.__setitem__(self, name, val)
36+
37 class SMTPConnection(object):
38     """
39     A wrapper that manages the SMTP network connection.
40@@ -154,13 +170,14 @@
41     """
42     A container for email information.
43     """
44-    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, connection=None):
45+    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, attachments=None, connection=None):
46         self.to = to or []
47         self.bcc = bcc or []
48         self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
49         self.subject = subject
50         self.body = body
51         self.connection = connection
52+        self.attachments = attachments or []
53 
54     def get_connection(self, fail_silently=False):
55         if not self.connection:
56@@ -169,6 +186,12 @@
57 
58     def message(self):
59         msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
60+        if self.attachments:
61+            body_msg = msg
62+            msg = SafeMIMEMultipart()
63+            msg.attach(body_msg)
64+            for params in self.attachments:
65+                msg.attach(self._compile_attachment(*params))
66         msg['Subject'] = self.subject
67         msg['From'] = self.from_email
68         msg['To'] = ', '.join(self.to)
69@@ -189,7 +212,40 @@
70         """Send the email message."""
71         return self.get_connection(fail_silently).send_messages([self])
72 
73-def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None):
74+    def attach(self, filename, content, mimetype=None):
75+        """Attaches a file with the given filename and content."""
76+        self.attachments.append((filename, content, mimetype))
77+
78+    def attach_file(self, path, mimetype=None):
79+        """Attaches a file from the filesystem."""
80+        filename = os.path.basename(path)
81+        f = open(path, 'rb')
82+        content = f.read()
83+        f.close()
84+        self.attach(filename, content)
85+
86+    def _compile_attachment(self, filename, content, mimetype):
87+        """
88+        Compiles the given attachment to a MIME object and returns the new
89+        attachment.
90+        """
91+        if mimetype is None:
92+            # Guess the mimetype based on the filename if possible.
93+            mimetype, xx = mimetypes.guess_type(filename)
94+            if mimetype is None:
95+                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
96+        basetype, subtype = mimetype.split('/', 1)
97+        if basetype == 'text':
98+            attachment = SafeMIMEText(content, subtype, settings.DEFAULT_CHARSET)
99+        else:
100+            # Encode non-text attachments with base64.
101+            attachment = MIMEBase(basetype, subtype)
102+            attachment.set_payload(content)
103+            Encoders.encode_base64(attachment)
104+        attachment.add_header('Content-Disposition', 'attachment', filename=filename)
105+        return attachment
106+
107+def send_mail(subject, message, from_email, recipient_list, attachments=None, fail_silently=False, auth_user=None, auth_password=None):
108     """
109     Easy wrapper for sending a single message to a recipient list. All members
110     of the recipient list will see the other recipients in the 'To' field.
111@@ -202,7 +258,7 @@
112     """
113     connection = SMTPConnection(username=auth_user, password=auth_password,
114                                  fail_silently=fail_silently)
115-    return EmailMessage(subject, message, from_email, recipient_list, connection=connection).send()
116+    return EmailMessage(subject, message, from_email, recipient_list, attachments=attachments, connection=connection).send()
117 
118 def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None):
119     """
120@@ -233,4 +289,3 @@
121     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
122             settings.SERVER_EMAIL, [a[1] for a in
123                 settings.MANAGERS]).send(fail_silently=fail_silently)
124-