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 email import Charset, Encoders
|
||||
from email.MIMEText import MIMEText
|
||||
from email.MIMEMultipart import MIMEMultipart
|
||||
from email.MIMEBase import MIMEBase
|
||||
from email.Header import Header
|
||||
from email.Utils import formatdate
|
||||
from email import Charset
|
||||
import mimetypes
|
||||
import os
|
||||
import smtplib
|
||||
import socket
|
||||
|
@ -17,6 +20,10 @@ import random
|
|||
# some spam filters.
|
||||
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
|
||||
# seconds, which slows down the restart of the server.
|
||||
class CachedDnsName(object):
|
||||
|
@ -55,14 +62,22 @@ def make_msgid(idstring=None):
|
|||
class BadHeaderError(ValueError):
|
||||
pass
|
||||
|
||||
class SafeMIMEText(MIMEText):
|
||||
class SafeHeaderMixin(object):
|
||||
def __setitem__(self, name, val):
|
||||
"Forbids multi-line headers, to prevent header injection."
|
||||
if '\n' in val or '\r' in val:
|
||||
raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
|
||||
if name == "Subject":
|
||||
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):
|
||||
"""
|
||||
|
@ -154,12 +169,14 @@ class EmailMessage(object):
|
|||
"""
|
||||
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.bcc = bcc or []
|
||||
self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
|
||||
self.subject = subject
|
||||
self.body = body
|
||||
self.attachments = attachments or []
|
||||
self.connection = connection
|
||||
|
||||
def get_connection(self, fail_silently=False):
|
||||
|
@ -169,6 +186,16 @@ class EmailMessage(object):
|
|||
|
||||
def message(self):
|
||||
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['From'] = self.from_email
|
||||
msg['To'] = ', '.join(self.to)
|
||||
|
@ -189,6 +216,45 @@ class EmailMessage(object):
|
|||
"""Send the email message."""
|
||||
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):
|
||||
"""
|
||||
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::
|
||||
|
||||
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_PORT: ../settings/#email-port
|
||||
.. _EMAIL_HOST_USER: ../settings/#email-host-user
|
||||
|
@ -198,21 +198,36 @@ e-mail, you can subclass these two classes to suit your needs.
|
|||
.. note::
|
||||
Not all features of the ``EmailMessage`` class are available through the
|
||||
``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
|
||||
create ``EmailMessage`` instances directly.
|
||||
features, such as BCC'ed recipients, file attachments, or multi-part
|
||||
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
|
||||
itself. ``SMTPConnection`` is responsible for the network connection side of
|
||||
the operation. This means you can reuse the same connection (an
|
||||
``SMTPConnection`` instance) for multiple messages.
|
||||
|
||||
E-mail messages
|
||||
----------------
|
||||
|
||||
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
|
||||
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::
|
||||
|
||||
|
@ -227,7 +242,8 @@ The class has the following methods:
|
|||
if none already exists.
|
||||
|
||||
* ``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,
|
||||
you'll probably want to override this method to put the content you wish
|
||||
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
|
||||
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
|
||||
password for the SMTP server. If you don't specify one or more of those
|
||||
options, they are read from your settings file.
|
||||
|
|
Loading…
Reference in New Issue