mirror of
https://github.com/django/django.git
synced 2025-06-03 18:49:12 +00:00
Refs #35537 -- Improved documentation and test coverage for email attachments and alternatives.
This commit is contained in:
parent
5424151f96
commit
d5bebc1c26
@ -11,6 +11,8 @@ from django.conf import settings
|
|||||||
from django.core.mail.message import (
|
from django.core.mail.message import (
|
||||||
DEFAULT_ATTACHMENT_MIME_TYPE,
|
DEFAULT_ATTACHMENT_MIME_TYPE,
|
||||||
BadHeaderError,
|
BadHeaderError,
|
||||||
|
EmailAlternative,
|
||||||
|
EmailAttachment,
|
||||||
EmailMessage,
|
EmailMessage,
|
||||||
EmailMultiAlternatives,
|
EmailMultiAlternatives,
|
||||||
SafeMIMEMultipart,
|
SafeMIMEMultipart,
|
||||||
@ -37,6 +39,8 @@ __all__ = [
|
|||||||
"send_mass_mail",
|
"send_mass_mail",
|
||||||
"mail_admins",
|
"mail_admins",
|
||||||
"mail_managers",
|
"mail_managers",
|
||||||
|
"EmailAlternative",
|
||||||
|
"EmailAttachment",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ class SafeMIMEMultipart(MIMEMixin, MIMEMultipart):
|
|||||||
MIMEMultipart.__setitem__(self, name, val)
|
MIMEMultipart.__setitem__(self, name, val)
|
||||||
|
|
||||||
|
|
||||||
Alternative = namedtuple("Alternative", ["content", "mimetype"])
|
EmailAlternative = namedtuple("Alternative", ["content", "mimetype"])
|
||||||
EmailAttachment = namedtuple("Attachment", ["filename", "content", "mimetype"])
|
EmailAttachment = namedtuple("Attachment", ["filename", "content", "mimetype"])
|
||||||
|
|
||||||
|
|
||||||
@ -477,14 +477,14 @@ class EmailMultiAlternatives(EmailMessage):
|
|||||||
reply_to,
|
reply_to,
|
||||||
)
|
)
|
||||||
self.alternatives = [
|
self.alternatives = [
|
||||||
Alternative(*alternative) for alternative in (alternatives or [])
|
EmailAlternative(*alternative) for alternative in (alternatives or [])
|
||||||
]
|
]
|
||||||
|
|
||||||
def attach_alternative(self, content, mimetype):
|
def attach_alternative(self, content, mimetype):
|
||||||
"""Attach an alternative content representation."""
|
"""Attach an alternative content representation."""
|
||||||
if content is None or mimetype is None:
|
if content is None or mimetype is None:
|
||||||
raise ValueError("Both content and mimetype must be provided.")
|
raise ValueError("Both content and mimetype must be provided.")
|
||||||
self.alternatives.append(Alternative(content, mimetype))
|
self.alternatives.append(EmailAlternative(content, mimetype))
|
||||||
|
|
||||||
def _create_message(self, msg):
|
def _create_message(self, msg):
|
||||||
return self._create_attachments(self._create_alternatives(msg))
|
return self._create_attachments(self._create_alternatives(msg))
|
||||||
|
@ -284,7 +284,9 @@ PostgreSQL 14 and higher.
|
|||||||
Miscellaneous
|
Miscellaneous
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
* ...
|
* :attr:`EmailMultiAlternatives.alternatives
|
||||||
|
<django.core.mail.EmailMultiAlternatives.alternatives>` should only be added
|
||||||
|
to using :meth:`~django.core.mail.EmailMultiAlternatives.attach_alternative`.
|
||||||
|
|
||||||
.. _deprecated-features-5.2:
|
.. _deprecated-features-5.2:
|
||||||
|
|
||||||
|
@ -282,13 +282,14 @@ All parameters are optional and can be set at any time prior to calling the
|
|||||||
new connection is created when ``send()`` is called.
|
new connection is created when ``send()`` is called.
|
||||||
|
|
||||||
* ``attachments``: A list of attachments to put on the message. These can
|
* ``attachments``: A list of attachments to put on the message. These can
|
||||||
be either :class:`~email.mime.base.MIMEBase` instances, or a named tuple
|
be instances of :class:`~email.mime.base.MIMEBase` or
|
||||||
with attributes ``(filename, content, mimetype)``.
|
:class:`~django.core.mail.EmailAttachment`, or a tuple with attributes
|
||||||
|
``(filename, content, mimetype)``.
|
||||||
|
|
||||||
.. versionchanged:: 5.2
|
.. versionchanged:: 5.2
|
||||||
|
|
||||||
In older versions, tuple items of ``attachments`` were regular tuples,
|
Support for :class:`~django.core.mail.EmailAttachment` items of
|
||||||
as opposed to named tuples.
|
``attachments`` were added.
|
||||||
|
|
||||||
* ``headers``: A dictionary of extra headers to put on the message. The
|
* ``headers``: A dictionary of extra headers to put on the message. The
|
||||||
keys are the header name, values are the header values. It's up to the
|
keys are the header name, values are the header values. It's up to the
|
||||||
@ -384,6 +385,18 @@ The class has the following methods:
|
|||||||
For MIME types starting with :mimetype:`text/`, binary data is handled as in
|
For MIME types starting with :mimetype:`text/`, binary data is handled as in
|
||||||
``attach()``.
|
``attach()``.
|
||||||
|
|
||||||
|
.. class:: EmailAttachment
|
||||||
|
|
||||||
|
.. versionadded:: 5.2
|
||||||
|
|
||||||
|
A named tuple to store attachments to an email.
|
||||||
|
|
||||||
|
The named tuple has the following indexes:
|
||||||
|
|
||||||
|
* ``filename``
|
||||||
|
* ``content``
|
||||||
|
* ``mimetype``
|
||||||
|
|
||||||
Sending alternative content types
|
Sending alternative content types
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
@ -404,20 +417,21 @@ Django's email library, you can do this using the
|
|||||||
|
|
||||||
.. attribute:: alternatives
|
.. attribute:: alternatives
|
||||||
|
|
||||||
A list of named tuples with attributes ``(content, mimetype)``. This is
|
A list of :class:`~django.core.mail.EmailAlternative` named tuples. This
|
||||||
particularly useful in tests::
|
is particularly useful in tests::
|
||||||
|
|
||||||
self.assertEqual(len(msg.alternatives), 1)
|
self.assertEqual(len(msg.alternatives), 1)
|
||||||
self.assertEqual(msg.alternatives[0].content, html_content)
|
self.assertEqual(msg.alternatives[0].content, html_content)
|
||||||
self.assertEqual(msg.alternatives[0].mimetype, "text/html")
|
self.assertEqual(msg.alternatives[0].mimetype, "text/html")
|
||||||
|
|
||||||
Alternatives should only be added using the :meth:`attach_alternative`
|
Alternatives should only be added using the :meth:`attach_alternative`
|
||||||
method.
|
method, or passed to the constructor.
|
||||||
|
|
||||||
.. versionchanged:: 5.2
|
.. versionchanged:: 5.2
|
||||||
|
|
||||||
In older versions, ``alternatives`` was a list of regular tuples,
|
In older versions, ``alternatives`` was a list of regular tuples,
|
||||||
as opposed to named tuples.
|
as opposed to :class:`~django.core.mail.EmailAlternative` named
|
||||||
|
tuples.
|
||||||
|
|
||||||
.. method:: attach_alternative(content, mimetype)
|
.. method:: attach_alternative(content, mimetype)
|
||||||
|
|
||||||
@ -456,6 +470,17 @@ Django's email library, you can do this using the
|
|||||||
self.assertIs(msg.body_contains("I am content"), True)
|
self.assertIs(msg.body_contains("I am content"), True)
|
||||||
self.assertIs(msg.body_contains("<p>I am content.</p>"), False)
|
self.assertIs(msg.body_contains("<p>I am content.</p>"), False)
|
||||||
|
|
||||||
|
.. class:: EmailAlternative
|
||||||
|
|
||||||
|
.. versionadded:: 5.2
|
||||||
|
|
||||||
|
A named tuple to store alternative versions of email content.
|
||||||
|
|
||||||
|
The named tuple has the following indexes:
|
||||||
|
|
||||||
|
* ``content``
|
||||||
|
* ``mimetype``
|
||||||
|
|
||||||
Updating the default content type
|
Updating the default content type
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ from unittest import mock, skipUnless
|
|||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.mail import (
|
from django.core.mail import (
|
||||||
DNS_NAME,
|
DNS_NAME,
|
||||||
|
EmailAlternative,
|
||||||
|
EmailAttachment,
|
||||||
EmailMessage,
|
EmailMessage,
|
||||||
EmailMultiAlternatives,
|
EmailMultiAlternatives,
|
||||||
mail_admins,
|
mail_admins,
|
||||||
@ -557,12 +559,50 @@ class MailTests(HeadersCheckMixin, SimpleTestCase):
|
|||||||
mime_type = "text/html"
|
mime_type = "text/html"
|
||||||
msg.attach_alternative(html_content, mime_type)
|
msg.attach_alternative(html_content, mime_type)
|
||||||
|
|
||||||
|
self.assertIsInstance(msg.alternatives[0], EmailAlternative)
|
||||||
|
|
||||||
self.assertEqual(msg.alternatives[0][0], html_content)
|
self.assertEqual(msg.alternatives[0][0], html_content)
|
||||||
self.assertEqual(msg.alternatives[0].content, html_content)
|
self.assertEqual(msg.alternatives[0].content, html_content)
|
||||||
|
|
||||||
self.assertEqual(msg.alternatives[0][1], mime_type)
|
self.assertEqual(msg.alternatives[0][1], mime_type)
|
||||||
self.assertEqual(msg.alternatives[0].mimetype, mime_type)
|
self.assertEqual(msg.alternatives[0].mimetype, mime_type)
|
||||||
|
|
||||||
|
self.assertIn(html_content, msg.message().as_string())
|
||||||
|
|
||||||
|
def test_alternatives_constructor(self):
|
||||||
|
html_content = "<p>This is <strong>html</strong></p>"
|
||||||
|
mime_type = "text/html"
|
||||||
|
|
||||||
|
msg = EmailMultiAlternatives(
|
||||||
|
alternatives=[EmailAlternative(html_content, mime_type)]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsInstance(msg.alternatives[0], EmailAlternative)
|
||||||
|
|
||||||
|
self.assertEqual(msg.alternatives[0][0], html_content)
|
||||||
|
self.assertEqual(msg.alternatives[0].content, html_content)
|
||||||
|
|
||||||
|
self.assertEqual(msg.alternatives[0][1], mime_type)
|
||||||
|
self.assertEqual(msg.alternatives[0].mimetype, mime_type)
|
||||||
|
|
||||||
|
self.assertIn(html_content, msg.message().as_string())
|
||||||
|
|
||||||
|
def test_alternatives_constructor_from_tuple(self):
|
||||||
|
html_content = "<p>This is <strong>html</strong></p>"
|
||||||
|
mime_type = "text/html"
|
||||||
|
|
||||||
|
msg = EmailMultiAlternatives(alternatives=[(html_content, mime_type)])
|
||||||
|
|
||||||
|
self.assertIsInstance(msg.alternatives[0], EmailAlternative)
|
||||||
|
|
||||||
|
self.assertEqual(msg.alternatives[0][0], html_content)
|
||||||
|
self.assertEqual(msg.alternatives[0].content, html_content)
|
||||||
|
|
||||||
|
self.assertEqual(msg.alternatives[0][1], mime_type)
|
||||||
|
self.assertEqual(msg.alternatives[0].mimetype, mime_type)
|
||||||
|
|
||||||
|
self.assertIn(html_content, msg.message().as_string())
|
||||||
|
|
||||||
def test_none_body(self):
|
def test_none_body(self):
|
||||||
msg = EmailMessage("subject", None, "from@example.com", ["to@example.com"])
|
msg = EmailMessage("subject", None, "from@example.com", ["to@example.com"])
|
||||||
self.assertEqual(msg.body, "")
|
self.assertEqual(msg.body, "")
|
||||||
@ -654,6 +694,51 @@ class MailTests(HeadersCheckMixin, SimpleTestCase):
|
|||||||
self.assertEqual(msg.attachments[0][2], mime_type)
|
self.assertEqual(msg.attachments[0][2], mime_type)
|
||||||
self.assertEqual(msg.attachments[0].mimetype, mime_type)
|
self.assertEqual(msg.attachments[0].mimetype, mime_type)
|
||||||
|
|
||||||
|
attachments = self.get_decoded_attachments(msg)
|
||||||
|
self.assertEqual(attachments[0], (file_name, file_content.encode(), mime_type))
|
||||||
|
|
||||||
|
def test_attachments_constructor(self):
|
||||||
|
file_name = "example.txt"
|
||||||
|
file_content = "Text file content"
|
||||||
|
mime_type = "text/plain"
|
||||||
|
msg = EmailMessage(
|
||||||
|
attachments=[EmailAttachment(file_name, file_content, mime_type)]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsInstance(msg.attachments[0], EmailAttachment)
|
||||||
|
|
||||||
|
self.assertEqual(msg.attachments[0][0], file_name)
|
||||||
|
self.assertEqual(msg.attachments[0].filename, file_name)
|
||||||
|
|
||||||
|
self.assertEqual(msg.attachments[0][1], file_content)
|
||||||
|
self.assertEqual(msg.attachments[0].content, file_content)
|
||||||
|
|
||||||
|
self.assertEqual(msg.attachments[0][2], mime_type)
|
||||||
|
self.assertEqual(msg.attachments[0].mimetype, mime_type)
|
||||||
|
|
||||||
|
attachments = self.get_decoded_attachments(msg)
|
||||||
|
self.assertEqual(attachments[0], (file_name, file_content.encode(), mime_type))
|
||||||
|
|
||||||
|
def test_attachments_constructor_from_tuple(self):
|
||||||
|
file_name = "example.txt"
|
||||||
|
file_content = "Text file content"
|
||||||
|
mime_type = "text/plain"
|
||||||
|
msg = EmailMessage(attachments=[(file_name, file_content, mime_type)])
|
||||||
|
|
||||||
|
self.assertIsInstance(msg.attachments[0], EmailAttachment)
|
||||||
|
|
||||||
|
self.assertEqual(msg.attachments[0][0], file_name)
|
||||||
|
self.assertEqual(msg.attachments[0].filename, file_name)
|
||||||
|
|
||||||
|
self.assertEqual(msg.attachments[0][1], file_content)
|
||||||
|
self.assertEqual(msg.attachments[0].content, file_content)
|
||||||
|
|
||||||
|
self.assertEqual(msg.attachments[0][2], mime_type)
|
||||||
|
self.assertEqual(msg.attachments[0].mimetype, mime_type)
|
||||||
|
|
||||||
|
attachments = self.get_decoded_attachments(msg)
|
||||||
|
self.assertEqual(attachments[0], (file_name, file_content.encode(), mime_type))
|
||||||
|
|
||||||
def test_decoded_attachments(self):
|
def test_decoded_attachments(self):
|
||||||
"""Regression test for #9367"""
|
"""Regression test for #9367"""
|
||||||
headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
|
headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user