mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	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:
		| @@ -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. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user