Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[1.0.X] Fixed #9367 - EmailMultiAlternatives does not properly handle…

… attachments.

Thanks to Loek Engels for the bulk of the patch.

Backport of r10983 from trunk


git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.0.X@10984 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit b9952794c387430e7c03510ad1a0db2497f1baf2 1 parent bbe034a
@spookylukey spookylukey authored
Showing with 109 additions and 24 deletions.
  1. +64 −23 django/core/mail.py
  2. +45 −1 tests/regressiontests/mail/tests.py
View
87 django/core/mail.py
@@ -195,7 +195,7 @@ class EmailMessage(object):
A container for email information.
"""
content_subtype = 'plain'
- multipart_subtype = 'mixed'
+ mixed_subtype = 'mixed'
encoding = None # None => use settings default
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
@@ -234,16 +234,7 @@ def message(self):
encoding = self.encoding or settings.DEFAULT_CHARSET
msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
self.content_subtype, encoding)
- if self.attachments:
- body_msg = msg
- msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
- if self.body:
- msg.attach(body_msg)
- for attachment in self.attachments:
- if isinstance(attachment, MIMEBase):
- msg.attach(attachment)
- else:
- msg.attach(self._create_attachment(*attachment))
+ msg = self._create_message(msg)
msg['Subject'] = self.subject
msg['From'] = self.extra_headers.pop('From', self.from_email)
msg['To'] = ', '.join(self.to)
@@ -273,8 +264,7 @@ def send(self, fail_silently=False):
def attach(self, filename=None, content=None, mimetype=None):
"""
Attaches a file with the given filename and content. The filename can
- be omitted (useful for multipart/alternative messages) and the mimetype
- is guessed, if not provided.
+ be omitted and the mimetype is guessed, if not provided.
If the first parameter is a MIMEBase subclass it is inserted directly
into the resulting message attachments.
@@ -292,15 +282,26 @@ def attach_file(self, path, mimetype=None):
content = open(path, 'rb').read()
self.attach(filename, content, mimetype)
- def _create_attachment(self, filename, content, mimetype=None):
+ def _create_message(self, msg):
+ return self._create_attachments(msg)
+
+ def _create_attachments(self, msg):
+ if self.attachments:
+ body_msg = msg
+ msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
+ if self.body:
+ msg.attach(body_msg)
+ for attachment in self.attachments:
+ if isinstance(attachment, MIMEBase):
+ msg.attach(attachment)
+ else:
+ msg.attach(self._create_attachment(*attachment))
+ return msg
+
+ def _create_mime_attachment(self, content, mimetype):
"""
- Converts the filename, content, mimetype triple into a MIME attachment
- object.
+ Converts the content, mimetype pair into a MIME attachment object.
"""
- if mimetype is None:
- mimetype, _ = mimetypes.guess_type(filename)
- if mimetype is None:
- mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
basetype, subtype = mimetype.split('/', 1)
if basetype == 'text':
attachment = SafeMIMEText(smart_str(content,
@@ -310,6 +311,18 @@ def _create_attachment(self, filename, content, mimetype=None):
attachment = MIMEBase(basetype, subtype)
attachment.set_payload(content)
Encoders.encode_base64(attachment)
+ return attachment
+
+ def _create_attachment(self, filename, content, mimetype=None):
+ """
+ Converts the filename, content, mimetype triple into a MIME attachment
+ object.
+ """
+ if mimetype is None:
+ mimetype, _ = mimetypes.guess_type(filename)
+ if mimetype is None:
+ mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
+ attachment = self._create_mime_attachment(content, mimetype)
if filename:
attachment.add_header('Content-Disposition', 'attachment',
filename=filename)
@@ -321,11 +334,39 @@ class EmailMultiAlternatives(EmailMessage):
messages. For example, including text and HTML versions of the text is
made easier.
"""
- multipart_subtype = 'alternative'
+ alternative_subtype = 'alternative'
- def attach_alternative(self, content, mimetype=None):
+ def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
+ connection=None, attachments=None, headers=None, alternatives=None):
+ """
+ Initialize a single email message (which can be sent to multiple
+ recipients).
+
+ All strings used to create the message can be unicode strings (or UTF-8
+ bytestrings). The SafeMIMEText class will handle any necessary encoding
+ conversions.
+ """
+ super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
+ self.alternatives=alternatives or []
+
+ def attach_alternative(self, content, mimetype):
"""Attach an alternative content representation."""
- self.attach(content=content, mimetype=mimetype)
+ assert content is not None
+ assert mimetype is not None
+ self.alternatives.append((content, mimetype))
+
+ def _create_message(self, msg):
+ return self._create_attachments(self._create_alternatives(msg))
+
+ def _create_alternatives(self, msg):
+ if self.alternatives:
+ body_msg = msg
+ msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
+ if self.body:
+ msg.attach(body_msg)
+ for alternative in self.alternatives:
+ msg.attach(self._create_mime_attachment(*alternative))
+ return msg
def send_mail(subject, message, from_email, recipient_list,
fail_silently=False, auth_user=None, auth_password=None):
View
46 tests/regressiontests/mail/tests.py
@@ -2,7 +2,7 @@
r"""
# Tests for the django.core.mail.
->>> from django.core.mail import EmailMessage
+>>> from django.core.mail import EmailMessage, EmailMultiAlternatives
>>> from django.utils.translation import ugettext_lazy
# Test normal ascii character case:
@@ -67,4 +67,48 @@
>>> message['From']
'from@example.com'
+# Handle attachments within an multipart/alternative mail correctly (#9367)
+# (test is not as precise/clear as it could be w.r.t. email tree structure,
+# but it's good enough.)
+
+>>> headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
+>>> subject, from_email, to = 'hello', 'from@example.com', 'to@example.com'
+>>> text_content = 'This is an important message.'
+>>> html_content = '<p>This is an <strong>important</strong> message.</p>'
+>>> msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers=headers)
+>>> msg.attach_alternative(html_content, "text/html")
+>>> msg.attach("an attachment.pdf", "%PDF-1.4.%...", mimetype="application/pdf")
+>>> print msg.message().as_string()
+Content-Type: multipart/mixed; boundary="..."
+MIME-Version: 1.0
+Subject: hello
+From: from@example.com
+To: to@example.com
+Date: Fri, 09 Nov 2001 01:08:47 -0000
+Message-ID: foo
+...
+Content-Type: multipart/alternative; boundary="..."
+MIME-Version: 1.0
+...
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+...
+This is an important message.
+...
+Content-Type: text/html; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+...
+<p>This is an <strong>important</strong> message.</p>
+...
+...
+Content-Type: application/pdf
+MIME-Version: 1.0
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="an attachment.pdf"
+...
+JVBERi0xLjQuJS4uLg==
+...
+
"""
Please sign in to comment.
Something went wrong with that request. Please try again.