Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #1541 -- Added ability to create multipart email messages. Than…

…ks, Nick

Lane.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@5547 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 2d082a34dc61a832710d98a933858fd2c0059644 1 parent 551a361
Malcolm Tredinnick authored June 27, 2007
74  django/core/mail.py
@@ -3,10 +3,13 @@
3 3
 """
4 4
 
5 5
 from django.conf import settings
  6
+from email import Charset, Encoders
6 7
 from email.MIMEText import MIMEText
  8
+from email.MIMEMultipart import MIMEMultipart
  9
+from email.MIMEBase import MIMEBase
7 10
 from email.Header import Header
8 11
 from email.Utils import formatdate
9  
-from email import Charset
  12
+import mimetypes
10 13
 import os
11 14
 import smtplib
12 15
 import socket
@@ -17,6 +20,10 @@
17 20
 # some spam filters.
18 21
 Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
19 22
 
  23
+# Default MIME type to use on attachments (if it is not explicitly given
  24
+# and cannot be guessed).
  25
+DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
  26
+
20 27
 # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
21 28
 # seconds, which slows down the restart of the server.
22 29
 class CachedDnsName(object):
@@ -55,14 +62,22 @@ def make_msgid(idstring=None):
55 62
 class BadHeaderError(ValueError):
56 63
     pass
57 64
 
58  
-class SafeMIMEText(MIMEText):
  65
+class SafeHeaderMixin(object):
59 66
     def __setitem__(self, name, val):
60 67
         "Forbids multi-line headers, to prevent header injection."
61 68
         if '\n' in val or '\r' in val:
62 69
             raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
63 70
         if name == "Subject":
64 71
             val = Header(val, settings.DEFAULT_CHARSET)
65  
-        MIMEText.__setitem__(self, name, val)
  72
+        # Note: using super() here is safe; any __setitem__ overrides must use
  73
+        # the same argument signature.
  74
+        super(SafeHeaderMixin, self).__setitem__(name, val)
  75
+
  76
+class SafeMIMEText(MIMEText, SafeHeaderMixin):
  77
+    pass
  78
+
  79
+class SafeMIMEMultipart(MIMEMultipart, SafeHeaderMixin):
  80
+    pass
66 81
 
67 82
 class SMTPConnection(object):
68 83
     """
@@ -154,12 +169,14 @@ class EmailMessage(object):
154 169
     """
155 170
     A container for email information.
156 171
     """
157  
-    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, connection=None):
  172
+    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
  173
+            connection=None, attachments=None):
158 174
         self.to = to or []
159 175
         self.bcc = bcc or []
160 176
         self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
161 177
         self.subject = subject
162 178
         self.body = body
  179
+        self.attachments = attachments or []
163 180
         self.connection = connection
164 181
 
165 182
     def get_connection(self, fail_silently=False):
@@ -169,6 +186,16 @@ def get_connection(self, fail_silently=False):
169 186
 
170 187
     def message(self):
171 188
         msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
  189
+        if self.attachments:
  190
+            body_msg = msg
  191
+            msg = SafeMIMEMultipart()
  192
+            if self.body:
  193
+                msg.attach(body_msg)
  194
+            for attachment in self.attachments:
  195
+                if isinstance(attachment, MIMEBase):
  196
+                    msg.attach(attachment)
  197
+                else:
  198
+                    msg.attach(self._create_attachment(*attachment))
172 199
         msg['Subject'] = self.subject
173 200
         msg['From'] = self.from_email
174 201
         msg['To'] = ', '.join(self.to)
@@ -189,6 +216,45 @@ def send(self, fail_silently=False):
189 216
         """Send the email message."""
190 217
         return self.get_connection(fail_silently).send_messages([self])
191 218
 
  219
+    def attach(self, filename, content=None, mimetype=None):
  220
+        """
  221
+        Attaches a file with the given filename and content.
  222
+
  223
+        Alternatively, the first parameter can be a MIMEBase subclass, which
  224
+        is inserted directly into the resulting message attachments.
  225
+        """
  226
+        if isinstance(filename, MIMEBase):
  227
+            self.attachements.append(filename)
  228
+        else:
  229
+            assert content is not None
  230
+            self.attachments.append((filename, content, mimetype))
  231
+
  232
+    def attach_file(self, path, mimetype=None):
  233
+        """Attaches a file from the filesystem."""
  234
+        filename = os.path.basename(path)
  235
+        content = open(path, 'rb').read()
  236
+        self.attach(filename, content, mimetype)
  237
+
  238
+    def _create_attachment(self, filename, content, mimetype=None):
  239
+        """
  240
+        Convert the filename, content, mimetype triple into a MIME attachment
  241
+        object.
  242
+        """
  243
+        if mimetype is None:
  244
+            mimetype, _ = mimetypes.guess_type(filename)
  245
+            if mimetype is None:
  246
+                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
  247
+        basetype, subtype = mimetype.split('/', 1)
  248
+        if basetype == 'text':
  249
+            attachment = SafeMIMEText(content, subtype, settings.DEFAULT_CHARSET)
  250
+        else:
  251
+            # Encode non-text attachments with base64.
  252
+            attachment = MIMEBase(basetype, subtype)
  253
+            attachment.set_payload(content)
  254
+            Encoders.encode_base64(attachment)
  255
+        attachment.add_header('Content-Disposition', 'attachment', filename=filename)
  256
+        return attachment
  257
+
192 258
 def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None):
193 259
     """
194 260
     Easy wrapper for sending a single message to a recipient list. All members
59  docs/email.txt
@@ -28,9 +28,9 @@ settings, if set, are used to authenticate to the SMTP server, and the
28 28
 .. note::
29 29
 
30 30
     The character set of e-mail sent with ``django.core.mail`` will be set to
31  
-    the value of your `DEFAULT_CHARSET setting`_.
  31
+    the value of your `DEFAULT_CHARSET`_ setting.
32 32
 
33  
-.. _DEFAULT_CHARSET setting: ../settings/#default-charset
  33
+.. _DEFAULT_CHARSET: ../settings/#default-charset
34 34
 .. _EMAIL_HOST: ../settings/#email-host
35 35
 .. _EMAIL_PORT: ../settings/#email-port
36 36
 .. _EMAIL_HOST_USER: ../settings/#email-host-user
@@ -198,21 +198,36 @@ e-mail, you can subclass these two classes to suit your needs.
198 198
 .. note::
199 199
     Not all features of the ``EmailMessage`` class are available through the
200 200
     ``send_mail()`` and related wrapper functions. If you wish to use advanced
201  
-    features, such as BCC'ed recipients or multi-part e-mail, you'll need to
202  
-    create ``EmailMessage`` instances directly.
  201
+    features, such as BCC'ed recipients, file attachments, or multi-part
  202
+    e-mail, you'll need to create ``EmailMessage`` instances directly.
  203
+
  204
+    This is a design feature. ``send_mail()`` and related functions were
  205
+    originally the only interface Django provided. However, the list of
  206
+    parameters they accepted was slowly growing over time.  It made sense to
  207
+    move to a more object-oriented design for e-mail messages and retain the
  208
+    original functions only for backwards compatibility.
  209
+
  210
+    If you need to add new functionality to the e-mail infrastrcture,
  211
+    sub-classing the ``EmailMessage`` class should make this a simple task.
203 212
 
204 213
 In general, ``EmailMessage`` is responsible for creating the e-mail message
205 214
 itself. ``SMTPConnection`` is responsible for the network connection side of
206 215
 the operation. This means you can reuse the same connection (an
207 216
 ``SMTPConnection`` instance) for multiple messages.
208 217
 
  218
+E-mail messages
  219
+----------------
  220
+
209 221
 The ``EmailMessage`` class is initialized as follows::
210 222
 
211  
-    email = EmailMessage(subject, body, from_email, to, bcc, connection)
  223
+    email = EmailMessage(subject, body, from_email, to,
  224
+                         bcc, connection, attachments)
212 225
 
213 226
 All of these parameters are optional. If ``from_email`` is omitted, the value
214 227
 from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc``
215  
-parameters are lists of addresses, as strings.
  228
+parameters are lists of addresses, as strings. The ``attachments`` parameter is
  229
+a list containing either ``(filename, content, mimetype)`` triples of
  230
+``email.MIMEBase.MIMEBase`` instances.
216 231
 
217 232
 For example::
218 233
 
@@ -227,7 +242,8 @@ The class has the following methods:
227 242
       if none already exists.
228 243
 
229 244
     * ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
230  
-      sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the
  245
+      sub-class of Python's ``email.MIMEText.MIMEText`` class) or a
  246
+      ``django.core.mail.SafeMIMEMultipart`` object holding the
231 247
       message to be sent. If you ever need to extend the `EmailMessage` class,
232 248
       you'll probably want to override this method to put the content you wish
233 249
       into the MIME object.
@@ -239,6 +255,35 @@ The class has the following methods:
239 255
       is sent. If you add another way to specify recipients in your class, they
240 256
       need to be returned from this method as well.
241 257
 
  258
+    * ``attach()`` creates a new file attachment and adds it to the message.
  259
+      There are two ways to call ``attach()``:
  260
+
  261
+       * You can pass it a single argument which is an
  262
+         ``email.MIMBase.MIMEBase`` instance. This will be inserted directly
  263
+         into the resulting message.
  264
+
  265
+       * Alternatively, you can pass ``attach()`` three arguments:
  266
+         ``filename``, ``content`` and ``mimetype``. ``filename`` is the name
  267
+         of the file attachment as it will appear in the email, ``content`` is
  268
+         the data that will be contained inside the attachment and
  269
+         ``mimetype`` is the optional MIME type for the attachment. If you
  270
+         omit ``mimetype``, the MIME content type will be guessed from the
  271
+         filename of the attachment.
  272
+
  273
+         For example::
  274
+
  275
+            message.attach('design.png', img_data, 'image/png')
  276
+
  277
+    * ``attach_file()`` creates a new attachment using a file from your
  278
+      filesystem. Call it with the path of the file to attach and, optionally,
  279
+      the MIME type to use for the attachment. If the MIME type is omitted, it
  280
+      will be guessed from the filename. The simplest use would be::
  281
+
  282
+        message.attach_file('/images/weather_map.png')
  283
+
  284
+SMTP network connections
  285
+-------------------------
  286
+
242 287
 The ``SMTPConnection`` class is initialized with the host, port, username and
243 288
 password for the SMTP server. If you don't specify one or more of those
244 289
 options, they are read from your settings file.

0 notes on commit 2d082a3

Please sign in to comment.
Something went wrong with that request. Please try again.