Fixed #1541 -- Added ability to create multipart email messages. Thanks, Nick
Lane. git-svn-id: http://code.djangoproject.com/svn/django/trunk@5547 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
551a36131e
commit
2d082a34dc
|
@ -3,10 +3,13 @@ Tools for sending email.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from email import Charset, Encoders
|
||||||
from email.MIMEText import MIMEText
|
from email.MIMEText import MIMEText
|
||||||
|
from email.MIMEMultipart import MIMEMultipart
|
||||||
|
from email.MIMEBase import MIMEBase
|
||||||
from email.Header import Header
|
from email.Header import Header
|
||||||
from email.Utils import formatdate
|
from email.Utils import formatdate
|
||||||
from email import Charset
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import smtplib
|
import smtplib
|
||||||
import socket
|
import socket
|
||||||
|
@ -17,6 +20,10 @@ import random
|
||||||
# some spam filters.
|
# some spam filters.
|
||||||
Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
|
Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
|
||||||
|
|
||||||
|
# Default MIME type to use on attachments (if it is not explicitly given
|
||||||
|
# and cannot be guessed).
|
||||||
|
DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
|
||||||
|
|
||||||
# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
|
# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
|
||||||
# seconds, which slows down the restart of the server.
|
# seconds, which slows down the restart of the server.
|
||||||
class CachedDnsName(object):
|
class CachedDnsName(object):
|
||||||
|
@ -55,14 +62,22 @@ def make_msgid(idstring=None):
|
||||||
class BadHeaderError(ValueError):
|
class BadHeaderError(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class SafeMIMEText(MIMEText):
|
class SafeHeaderMixin(object):
|
||||||
def __setitem__(self, name, val):
|
def __setitem__(self, name, val):
|
||||||
"Forbids multi-line headers, to prevent header injection."
|
"Forbids multi-line headers, to prevent header injection."
|
||||||
if '\n' in val or '\r' in val:
|
if '\n' in val or '\r' in val:
|
||||||
raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
|
raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
|
||||||
if name == "Subject":
|
if name == "Subject":
|
||||||
val = Header(val, settings.DEFAULT_CHARSET)
|
val = Header(val, settings.DEFAULT_CHARSET)
|
||||||
MIMEText.__setitem__(self, name, val)
|
# Note: using super() here is safe; any __setitem__ overrides must use
|
||||||
|
# the same argument signature.
|
||||||
|
super(SafeHeaderMixin, self).__setitem__(name, val)
|
||||||
|
|
||||||
|
class SafeMIMEText(MIMEText, SafeHeaderMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SafeMIMEMultipart(MIMEMultipart, SafeHeaderMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
class SMTPConnection(object):
|
class SMTPConnection(object):
|
||||||
"""
|
"""
|
||||||
|
@ -154,12 +169,14 @@ class EmailMessage(object):
|
||||||
"""
|
"""
|
||||||
A container for email information.
|
A container for email information.
|
||||||
"""
|
"""
|
||||||
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, connection=None):
|
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
|
||||||
|
connection=None, attachments=None):
|
||||||
self.to = to or []
|
self.to = to or []
|
||||||
self.bcc = bcc or []
|
self.bcc = bcc or []
|
||||||
self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
|
self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.body = body
|
self.body = body
|
||||||
|
self.attachments = attachments or []
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
def get_connection(self, fail_silently=False):
|
def get_connection(self, fail_silently=False):
|
||||||
|
@ -169,6 +186,16 @@ class EmailMessage(object):
|
||||||
|
|
||||||
def message(self):
|
def message(self):
|
||||||
msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
|
msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
|
||||||
|
if self.attachments:
|
||||||
|
body_msg = msg
|
||||||
|
msg = SafeMIMEMultipart()
|
||||||
|
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['Subject'] = self.subject
|
msg['Subject'] = self.subject
|
||||||
msg['From'] = self.from_email
|
msg['From'] = self.from_email
|
||||||
msg['To'] = ', '.join(self.to)
|
msg['To'] = ', '.join(self.to)
|
||||||
|
@ -189,6 +216,45 @@ class EmailMessage(object):
|
||||||
"""Send the email message."""
|
"""Send the email message."""
|
||||||
return self.get_connection(fail_silently).send_messages([self])
|
return self.get_connection(fail_silently).send_messages([self])
|
||||||
|
|
||||||
|
def attach(self, filename, content=None, mimetype=None):
|
||||||
|
"""
|
||||||
|
Attaches a file with the given filename and content.
|
||||||
|
|
||||||
|
Alternatively, the first parameter can be a MIMEBase subclass, which
|
||||||
|
is inserted directly into the resulting message attachments.
|
||||||
|
"""
|
||||||
|
if isinstance(filename, MIMEBase):
|
||||||
|
self.attachements.append(filename)
|
||||||
|
else:
|
||||||
|
assert content is not None
|
||||||
|
self.attachments.append((filename, content, mimetype))
|
||||||
|
|
||||||
|
def attach_file(self, path, mimetype=None):
|
||||||
|
"""Attaches a file from the filesystem."""
|
||||||
|
filename = os.path.basename(path)
|
||||||
|
content = open(path, 'rb').read()
|
||||||
|
self.attach(filename, content, mimetype)
|
||||||
|
|
||||||
|
def _create_attachment(self, filename, content, mimetype=None):
|
||||||
|
"""
|
||||||
|
Convert 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
|
||||||
|
basetype, subtype = mimetype.split('/', 1)
|
||||||
|
if basetype == 'text':
|
||||||
|
attachment = SafeMIMEText(content, subtype, settings.DEFAULT_CHARSET)
|
||||||
|
else:
|
||||||
|
# Encode non-text attachments with base64.
|
||||||
|
attachment = MIMEBase(basetype, subtype)
|
||||||
|
attachment.set_payload(content)
|
||||||
|
Encoders.encode_base64(attachment)
|
||||||
|
attachment.add_header('Content-Disposition', 'attachment', filename=filename)
|
||||||
|
return attachment
|
||||||
|
|
||||||
def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None):
|
def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None):
|
||||||
"""
|
"""
|
||||||
Easy wrapper for sending a single message to a recipient list. All members
|
Easy wrapper for sending a single message to a recipient list. All members
|
||||||
|
|
|
@ -28,9 +28,9 @@ settings, if set, are used to authenticate to the SMTP server, and the
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The character set of e-mail sent with ``django.core.mail`` will be set to
|
The character set of e-mail sent with ``django.core.mail`` will be set to
|
||||||
the value of your `DEFAULT_CHARSET setting`_.
|
the value of your `DEFAULT_CHARSET`_ setting.
|
||||||
|
|
||||||
.. _DEFAULT_CHARSET setting: ../settings/#default-charset
|
.. _DEFAULT_CHARSET: ../settings/#default-charset
|
||||||
.. _EMAIL_HOST: ../settings/#email-host
|
.. _EMAIL_HOST: ../settings/#email-host
|
||||||
.. _EMAIL_PORT: ../settings/#email-port
|
.. _EMAIL_PORT: ../settings/#email-port
|
||||||
.. _EMAIL_HOST_USER: ../settings/#email-host-user
|
.. _EMAIL_HOST_USER: ../settings/#email-host-user
|
||||||
|
@ -198,21 +198,36 @@ e-mail, you can subclass these two classes to suit your needs.
|
||||||
.. note::
|
.. note::
|
||||||
Not all features of the ``EmailMessage`` class are available through the
|
Not all features of the ``EmailMessage`` class are available through the
|
||||||
``send_mail()`` and related wrapper functions. If you wish to use advanced
|
``send_mail()`` and related wrapper functions. If you wish to use advanced
|
||||||
features, such as BCC'ed recipients or multi-part e-mail, you'll need to
|
features, such as BCC'ed recipients, file attachments, or multi-part
|
||||||
create ``EmailMessage`` instances directly.
|
e-mail, you'll need to create ``EmailMessage`` instances directly.
|
||||||
|
|
||||||
|
This is a design feature. ``send_mail()`` and related functions were
|
||||||
|
originally the only interface Django provided. However, the list of
|
||||||
|
parameters they accepted was slowly growing over time. It made sense to
|
||||||
|
move to a more object-oriented design for e-mail messages and retain the
|
||||||
|
original functions only for backwards compatibility.
|
||||||
|
|
||||||
|
If you need to add new functionality to the e-mail infrastrcture,
|
||||||
|
sub-classing the ``EmailMessage`` class should make this a simple task.
|
||||||
|
|
||||||
In general, ``EmailMessage`` is responsible for creating the e-mail message
|
In general, ``EmailMessage`` is responsible for creating the e-mail message
|
||||||
itself. ``SMTPConnection`` is responsible for the network connection side of
|
itself. ``SMTPConnection`` is responsible for the network connection side of
|
||||||
the operation. This means you can reuse the same connection (an
|
the operation. This means you can reuse the same connection (an
|
||||||
``SMTPConnection`` instance) for multiple messages.
|
``SMTPConnection`` instance) for multiple messages.
|
||||||
|
|
||||||
|
E-mail messages
|
||||||
|
----------------
|
||||||
|
|
||||||
The ``EmailMessage`` class is initialized as follows::
|
The ``EmailMessage`` class is initialized as follows::
|
||||||
|
|
||||||
email = EmailMessage(subject, body, from_email, to, bcc, connection)
|
email = EmailMessage(subject, body, from_email, to,
|
||||||
|
bcc, connection, attachments)
|
||||||
|
|
||||||
All of these parameters are optional. If ``from_email`` is omitted, the value
|
All of these parameters are optional. If ``from_email`` is omitted, the value
|
||||||
from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc``
|
from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc``
|
||||||
parameters are lists of addresses, as strings.
|
parameters are lists of addresses, as strings. The ``attachments`` parameter is
|
||||||
|
a list containing either ``(filename, content, mimetype)`` triples of
|
||||||
|
``email.MIMEBase.MIMEBase`` instances.
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
|
@ -227,7 +242,8 @@ The class has the following methods:
|
||||||
if none already exists.
|
if none already exists.
|
||||||
|
|
||||||
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
|
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
|
||||||
sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the
|
sub-class of Python's ``email.MIMEText.MIMEText`` class) or a
|
||||||
|
``django.core.mail.SafeMIMEMultipart`` object holding the
|
||||||
message to be sent. If you ever need to extend the `EmailMessage` class,
|
message to be sent. If you ever need to extend the `EmailMessage` class,
|
||||||
you'll probably want to override this method to put the content you wish
|
you'll probably want to override this method to put the content you wish
|
||||||
into the MIME object.
|
into the MIME object.
|
||||||
|
@ -239,6 +255,35 @@ The class has the following methods:
|
||||||
is sent. If you add another way to specify recipients in your class, they
|
is sent. If you add another way to specify recipients in your class, they
|
||||||
need to be returned from this method as well.
|
need to be returned from this method as well.
|
||||||
|
|
||||||
|
* ``attach()`` creates a new file attachment and adds it to the message.
|
||||||
|
There are two ways to call ``attach()``:
|
||||||
|
|
||||||
|
* You can pass it a single argument which is an
|
||||||
|
``email.MIMBase.MIMEBase`` instance. This will be inserted directly
|
||||||
|
into the resulting message.
|
||||||
|
|
||||||
|
* Alternatively, you can pass ``attach()`` three arguments:
|
||||||
|
``filename``, ``content`` and ``mimetype``. ``filename`` is the name
|
||||||
|
of the file attachment as it will appear in the email, ``content`` is
|
||||||
|
the data that will be contained inside the attachment and
|
||||||
|
``mimetype`` is the optional MIME type for the attachment. If you
|
||||||
|
omit ``mimetype``, the MIME content type will be guessed from the
|
||||||
|
filename of the attachment.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
message.attach('design.png', img_data, 'image/png')
|
||||||
|
|
||||||
|
* ``attach_file()`` creates a new attachment using a file from your
|
||||||
|
filesystem. Call it with the path of the file to attach and, optionally,
|
||||||
|
the MIME type to use for the attachment. If the MIME type is omitted, it
|
||||||
|
will be guessed from the filename. The simplest use would be::
|
||||||
|
|
||||||
|
message.attach_file('/images/weather_map.png')
|
||||||
|
|
||||||
|
SMTP network connections
|
||||||
|
-------------------------
|
||||||
|
|
||||||
The ``SMTPConnection`` class is initialized with the host, port, username and
|
The ``SMTPConnection`` class is initialized with the host, port, username and
|
||||||
password for the SMTP server. If you don't specify one or more of those
|
password for the SMTP server. If you don't specify one or more of those
|
||||||
options, they are read from your settings file.
|
options, they are read from your settings file.
|
||||||
|
|
Loading…
Reference in New Issue