Loading django/core/mail.py +64 −23 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -234,16 +234,7 @@ class EmailMessage(object): 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) Loading Loading @@ -273,8 +264,7 @@ class EmailMessage(object): 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. Loading @@ -292,15 +282,26 @@ class EmailMessage(object): 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, Loading @@ -310,6 +311,18 @@ class EmailMessage(object): 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) Loading @@ -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 __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=None): 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): Loading tests/regressiontests/mail/tests.py +45 −1 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -67,4 +67,48 @@ BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection T >>> 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== ... """ Loading
django/core/mail.py +64 −23 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -234,16 +234,7 @@ class EmailMessage(object): 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) Loading Loading @@ -273,8 +264,7 @@ class EmailMessage(object): 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. Loading @@ -292,15 +282,26 @@ class EmailMessage(object): 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, Loading @@ -310,6 +311,18 @@ class EmailMessage(object): 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) Loading @@ -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 __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=None): 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): Loading
tests/regressiontests/mail/tests.py +45 −1 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -67,4 +67,48 @@ BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection T >>> 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== ... """