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 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. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user