Ticket #10355: email_backends.diff

File email_backends.diff, 59.2 KB (added by Andi Albrecht, 15 years ago)

Make django.core.mail a package and add backend functionality

  • django/conf/global_settings.py

    Vergleiche mit /storage/devel/django/django-trunk
    Suche nach Änderungen
    Änderung:        7296:c00b6982431e
    Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
    Datum:           Mon Aug 24 06:34:37 2009 +0200
    Zusammenfassung: Refactored django.core.email
    
    diff -r 4d79b1a3175f -r c00b6982431e django/conf/global_settings.py
    a b  
    131131DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
    132132DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
    133133
     134# The email backend to use. Possible values are 'smtp' (default), 'dummy',
     135# 'log', 'test' or any Python import path pointing to a BaseEmailBackend
     136# implementation.
     137EMAIL_BACKEND = 'smtp'
     138
     139# The email backend to use for testing.
     140TEST_EMAIL_BACKEND = 'testenv'
     141
    134142# Host for sending e-mail.
    135143EMAIL_HOST = 'localhost'
    136144
  • deleted file django/core/mail.py

    diff -r 4d79b1a3175f -r c00b6982431e django/core/mail.py
    + -  
    1 """
    2 Tools for sending email.
    3 """
    4 
    5 import mimetypes
    6 import os
    7 import smtplib
    8 import socket
    9 import time
    10 import random
    11 from email import Charset, Encoders
    12 from email.MIMEText import MIMEText
    13 from email.MIMEMultipart import MIMEMultipart
    14 from email.MIMEBase import MIMEBase
    15 from email.Header import Header
    16 from email.Utils import formatdate, parseaddr, formataddr
    17 
    18 from django.conf import settings
    19 from django.utils.encoding import smart_str, force_unicode
    20 
    21 # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
    22 # some spam filters.
    23 Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
    24 
    25 # Default MIME type to use on attachments (if it is not explicitly given
    26 # and cannot be guessed).
    27 DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
    28 
    29 # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
    30 # seconds, which slows down the restart of the server.
    31 class CachedDnsName(object):
    32     def __str__(self):
    33         return self.get_fqdn()
    34 
    35     def get_fqdn(self):
    36         if not hasattr(self, '_fqdn'):
    37             self._fqdn = socket.getfqdn()
    38         return self._fqdn
    39 
    40 DNS_NAME = CachedDnsName()
    41 
    42 # Copied from Python standard library, with the following modifications:
    43 # * Used cached hostname for performance.
    44 # * Added try/except to support lack of getpid() in Jython (#5496).
    45 def make_msgid(idstring=None):
    46     """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
    47 
    48     <20020201195627.33539.96671@nightshade.la.mastaler.com>
    49 
    50     Optional idstring if given is a string used to strengthen the
    51     uniqueness of the message id.
    52     """
    53     timeval = time.time()
    54     utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
    55     try:
    56         pid = os.getpid()
    57     except AttributeError:
    58         # No getpid() in Jython, for example.
    59         pid = 1
    60     randint = random.randrange(100000)
    61     if idstring is None:
    62         idstring = ''
    63     else:
    64         idstring = '.' + idstring
    65     idhost = DNS_NAME
    66     msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
    67     return msgid
    68 
    69 class BadHeaderError(ValueError):
    70     pass
    71 
    72 def forbid_multi_line_headers(name, val):
    73     """Forbids multi-line headers, to prevent header injection."""
    74     val = force_unicode(val)
    75     if '\n' in val or '\r' in val:
    76         raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
    77     try:
    78         val = val.encode('ascii')
    79     except UnicodeEncodeError:
    80         if name.lower() in ('to', 'from', 'cc'):
    81             result = []
    82             for item in val.split(', '):
    83                 nm, addr = parseaddr(item)
    84                 nm = str(Header(nm, settings.DEFAULT_CHARSET))
    85                 result.append(formataddr((nm, str(addr))))
    86             val = ', '.join(result)
    87         else:
    88             val = Header(val, settings.DEFAULT_CHARSET)
    89     else:
    90         if name.lower() == 'subject':
    91             val = Header(val)
    92     return name, val
    93 
    94 class SafeMIMEText(MIMEText):
    95     def __setitem__(self, name, val):
    96         name, val = forbid_multi_line_headers(name, val)
    97         MIMEText.__setitem__(self, name, val)
    98 
    99 class SafeMIMEMultipart(MIMEMultipart):
    100     def __setitem__(self, name, val):
    101         name, val = forbid_multi_line_headers(name, val)
    102         MIMEMultipart.__setitem__(self, name, val)
    103 
    104 class SMTPConnection(object):
    105     """
    106     A wrapper that manages the SMTP network connection.
    107     """
    108 
    109     def __init__(self, host=None, port=None, username=None, password=None,
    110                  use_tls=None, fail_silently=False):
    111         self.host = host or settings.EMAIL_HOST
    112         self.port = port or settings.EMAIL_PORT
    113         self.username = username or settings.EMAIL_HOST_USER
    114         self.password = password or settings.EMAIL_HOST_PASSWORD
    115         self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
    116         self.fail_silently = fail_silently
    117         self.connection = None
    118 
    119     def open(self):
    120         """
    121         Ensures we have a connection to the email server. Returns whether or
    122         not a new connection was required (True or False).
    123         """
    124         if self.connection:
    125             # Nothing to do if the connection is already open.
    126             return False
    127         try:
    128             # If local_hostname is not specified, socket.getfqdn() gets used.
    129             # For performance, we use the cached FQDN for local_hostname.
    130             self.connection = smtplib.SMTP(self.host, self.port,
    131                                            local_hostname=DNS_NAME.get_fqdn())
    132             if self.use_tls:
    133                 self.connection.ehlo()
    134                 self.connection.starttls()
    135                 self.connection.ehlo()
    136             if self.username and self.password:
    137                 self.connection.login(self.username, self.password)
    138             return True
    139         except:
    140             if not self.fail_silently:
    141                 raise
    142 
    143     def close(self):
    144         """Closes the connection to the email server."""
    145         try:
    146             try:
    147                 self.connection.quit()
    148             except socket.sslerror:
    149                 # This happens when calling quit() on a TLS connection
    150                 # sometimes.
    151                 self.connection.close()
    152             except:
    153                 if self.fail_silently:
    154                     return
    155                 raise
    156         finally:
    157             self.connection = None
    158 
    159     def send_messages(self, email_messages):
    160         """
    161         Sends one or more EmailMessage objects and returns the number of email
    162         messages sent.
    163         """
    164         if not email_messages:
    165             return
    166         new_conn_created = self.open()
    167         if not self.connection:
    168             # We failed silently on open(). Trying to send would be pointless.
    169             return
    170         num_sent = 0
    171         for message in email_messages:
    172             sent = self._send(message)
    173             if sent:
    174                 num_sent += 1
    175         if new_conn_created:
    176             self.close()
    177         return num_sent
    178 
    179     def _send(self, email_message):
    180         """A helper method that does the actual sending."""
    181         if not email_message.recipients():
    182             return False
    183         try:
    184             self.connection.sendmail(email_message.from_email,
    185                     email_message.recipients(),
    186                     email_message.message().as_string())
    187         except:
    188             if not self.fail_silently:
    189                 raise
    190             return False
    191         return True
    192 
    193 class EmailMessage(object):
    194     """
    195     A container for email information.
    196     """
    197     content_subtype = 'plain'
    198     mixed_subtype = 'mixed'
    199     encoding = None     # None => use settings default
    200 
    201     def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
    202             connection=None, attachments=None, headers=None):
    203         """
    204         Initialize a single email message (which can be sent to multiple
    205         recipients).
    206 
    207         All strings used to create the message can be unicode strings (or UTF-8
    208         bytestrings). The SafeMIMEText class will handle any necessary encoding
    209         conversions.
    210         """
    211         if to:
    212             assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
    213             self.to = list(to)
    214         else:
    215             self.to = []
    216         if bcc:
    217             assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
    218             self.bcc = list(bcc)
    219         else:
    220             self.bcc = []
    221         self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
    222         self.subject = subject
    223         self.body = body
    224         self.attachments = attachments or []
    225         self.extra_headers = headers or {}
    226         self.connection = connection
    227 
    228     def get_connection(self, fail_silently=False):
    229         if not self.connection:
    230             self.connection = SMTPConnection(fail_silently=fail_silently)
    231         return self.connection
    232 
    233     def message(self):
    234         encoding = self.encoding or settings.DEFAULT_CHARSET
    235         msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
    236                            self.content_subtype, encoding)
    237         msg = self._create_message(msg)
    238         msg['Subject'] = self.subject
    239         msg['From'] = self.extra_headers.pop('From', self.from_email)
    240         msg['To'] = ', '.join(self.to)
    241 
    242         # Email header names are case-insensitive (RFC 2045), so we have to
    243         # accommodate that when doing comparisons.
    244         header_names = [key.lower() for key in self.extra_headers]
    245         if 'date' not in header_names:
    246             msg['Date'] = formatdate()
    247         if 'message-id' not in header_names:
    248             msg['Message-ID'] = make_msgid()
    249         for name, value in self.extra_headers.items():
    250             msg[name] = value
    251         return msg
    252 
    253     def recipients(self):
    254         """
    255         Returns a list of all recipients of the email (includes direct
    256         addressees as well as Bcc entries).
    257         """
    258         return self.to + self.bcc
    259 
    260     def send(self, fail_silently=False):
    261         """Sends the email message."""
    262         if not self.recipients():
    263             # Don't bother creating the network connection if there's nobody to
    264             # send to.
    265             return 0
    266         return self.get_connection(fail_silently).send_messages([self])
    267 
    268     def attach(self, filename=None, content=None, mimetype=None):
    269         """
    270         Attaches a file with the given filename and content. The filename can
    271         be omitted and the mimetype is guessed, if not provided.
    272 
    273         If the first parameter is a MIMEBase subclass it is inserted directly
    274         into the resulting message attachments.
    275         """
    276         if isinstance(filename, MIMEBase):
    277             assert content == mimetype == None
    278             self.attachments.append(filename)
    279         else:
    280             assert content is not None
    281             self.attachments.append((filename, content, mimetype))
    282 
    283     def attach_file(self, path, mimetype=None):
    284         """Attaches a file from the filesystem."""
    285         filename = os.path.basename(path)
    286         content = open(path, 'rb').read()
    287         self.attach(filename, content, mimetype)
    288 
    289     def _create_message(self, msg):
    290         return self._create_attachments(msg)
    291 
    292     def _create_attachments(self, msg):
    293         if self.attachments:
    294             body_msg = msg
    295             msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
    296             if self.body:
    297                 msg.attach(body_msg)
    298             for attachment in self.attachments:
    299                 if isinstance(attachment, MIMEBase):
    300                     msg.attach(attachment)
    301                 else:
    302                     msg.attach(self._create_attachment(*attachment))
    303         return msg
    304 
    305     def _create_mime_attachment(self, content, mimetype):
    306         """
    307         Converts the content, mimetype pair into a MIME attachment object.
    308         """
    309         basetype, subtype = mimetype.split('/', 1)
    310         if basetype == 'text':
    311             attachment = SafeMIMEText(smart_str(content,
    312                 settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
    313         else:
    314             # Encode non-text attachments with base64.
    315             attachment = MIMEBase(basetype, subtype)
    316             attachment.set_payload(content)
    317             Encoders.encode_base64(attachment)
    318         return attachment
    319 
    320     def _create_attachment(self, filename, content, mimetype=None):
    321         """
    322         Converts the filename, content, mimetype triple into a MIME attachment
    323         object.
    324         """
    325         if mimetype is None:
    326             mimetype, _ = mimetypes.guess_type(filename)
    327             if mimetype is None:
    328                 mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
    329         attachment = self._create_mime_attachment(content, mimetype)
    330         if filename:
    331             attachment.add_header('Content-Disposition', 'attachment',
    332                                   filename=filename)
    333         return attachment
    334 
    335 class EmailMultiAlternatives(EmailMessage):
    336     """
    337     A version of EmailMessage that makes it easy to send multipart/alternative
    338     messages. For example, including text and HTML versions of the text is
    339     made easier.
    340     """
    341     alternative_subtype = 'alternative'
    342 
    343     def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
    344             connection=None, attachments=None, headers=None, alternatives=None):
    345         """
    346         Initialize a single email message (which can be sent to multiple
    347         recipients).
    348 
    349         All strings used to create the message can be unicode strings (or UTF-8
    350         bytestrings). The SafeMIMEText class will handle any necessary encoding
    351         conversions.
    352         """
    353         super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
    354         self.alternatives=alternatives or []
    355 
    356     def attach_alternative(self, content, mimetype):
    357         """Attach an alternative content representation."""
    358         assert content is not None
    359         assert mimetype is not None
    360         self.alternatives.append((content, mimetype))
    361 
    362     def _create_message(self, msg):
    363         return self._create_attachments(self._create_alternatives(msg))
    364 
    365     def _create_alternatives(self, msg):
    366         if self.alternatives:
    367             body_msg = msg
    368             msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
    369             if self.body:
    370                 msg.attach(body_msg)
    371             for alternative in self.alternatives:
    372                 msg.attach(self._create_mime_attachment(*alternative))
    373         return msg
    374 
    375 def send_mail(subject, message, from_email, recipient_list,
    376               fail_silently=False, auth_user=None, auth_password=None):
    377     """
    378     Easy wrapper for sending a single message to a recipient list. All members
    379     of the recipient list will see the other recipients in the 'To' field.
    380 
    381     If auth_user is None, the EMAIL_HOST_USER setting is used.
    382     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
    383 
    384     Note: The API for this method is frozen. New code wanting to extend the
    385     functionality should use the EmailMessage class directly.
    386     """
    387     connection = SMTPConnection(username=auth_user, password=auth_password,
    388                                 fail_silently=fail_silently)
    389     return EmailMessage(subject, message, from_email, recipient_list,
    390                         connection=connection).send()
    391 
    392 def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
    393                    auth_password=None):
    394     """
    395     Given a datatuple of (subject, message, from_email, recipient_list), sends
    396     each message to each recipient list. Returns the number of e-mails sent.
    397 
    398     If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
    399     If auth_user and auth_password are set, they're used to log in.
    400     If auth_user is None, the EMAIL_HOST_USER setting is used.
    401     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
    402 
    403     Note: The API for this method is frozen. New code wanting to extend the
    404     functionality should use the EmailMessage class directly.
    405     """
    406     connection = SMTPConnection(username=auth_user, password=auth_password,
    407                                 fail_silently=fail_silently)
    408     messages = [EmailMessage(subject, message, sender, recipient)
    409                 for subject, message, sender, recipient in datatuple]
    410     return connection.send_messages(messages)
    411 
    412 def mail_admins(subject, message, fail_silently=False):
    413     """Sends a message to the admins, as defined by the ADMINS setting."""
    414     if not settings.ADMINS:
    415         return
    416     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
    417                  settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
    418                  ).send(fail_silently=fail_silently)
    419 
    420 def mail_managers(subject, message, fail_silently=False):
    421     """Sends a message to the managers, as defined by the MANAGERS setting."""
    422     if not settings.MANAGERS:
    423         return
    424     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
    425                  settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
    426                  ).send(fail_silently=fail_silently)
  • new file django/core/mail/__init__.py

    diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/__init__.py
    - +  
     1"""
     2Tools for sending email.
     3"""
     4
     5from django.conf import settings
     6from django.core.exceptions import ImproperlyConfigured
     7from django.core.mail.message import EmailMessage, EmailMultiAlternatives
     8from django.utils.importlib import import_module
     9
     10# Imported for backwards compatibility
     11from django.core.mail.backends.smtp import EmailBackend as SMTPConnection
     12
     13
     14# Shortcuts for builtin email backends. If settings.EMAIL_BACKEND isn't in
     15# this list it's treated as a Python import path.
     16BACKENDS = ('smtp', 'dummy', 'log', 'testenv')
     17
     18
     19def get_connection(fail_silently=False, **kwds):
     20    """Load configured email backend and return an instance of it.
     21
     22    Both fail_silently and other keyword arguments are used in the
     23    constructor of the backend.
     24    """
     25    path = settings.EMAIL_BACKEND
     26    if path in BACKENDS:
     27        path = 'django.core.mail.backends.%s.EmailBackend' % path
     28    i = path.rfind('.')
     29    module, attr = path[:i], path[i+1:]
     30    try:
     31        mod = import_module(module)
     32    except ImportError, e:
     33        raise ImproperlyConfigured(('Error importing email backend %s: "%s"'
     34                                    % (module, e)))
     35    try:
     36        cls = getattr(mod, attr)
     37    except AttributeError:
     38        raise ImproperlyConfigured(('Module "%s" does not define a "%s" '
     39                                    'email backend' % (module, attr)))
     40    return cls(fail_silently=fail_silently, **kwds)
     41
     42
     43def send_mail(subject, message, from_email, recipient_list,
     44              fail_silently=False, auth_user=None, auth_password=None):
     45    """
     46    Easy wrapper for sending a single message to a recipient list. All members
     47    of the recipient list will see the other recipients in the 'To' field.
     48
     49    If auth_user is None, the EMAIL_HOST_USER setting is used.
     50    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
     51
     52    Note: The API for this method is frozen. New code wanting to extend the
     53    functionality should use the EmailMessage class directly.
     54    """
     55    connection = get_connection(username=auth_user, password=auth_password,
     56                                fail_silently=fail_silently)
     57    return EmailMessage(subject, message, from_email, recipient_list,
     58                        connection=connection).send()
     59
     60
     61def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
     62                   auth_password=None):
     63    """
     64    Given a datatuple of (subject, message, from_email, recipient_list), sends
     65    each message to each recipient list. Returns the number of e-mails sent.
     66
     67    If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
     68    If auth_user and auth_password are set, they're used to log in.
     69    If auth_user is None, the EMAIL_HOST_USER setting is used.
     70    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
     71
     72    Note: The API for this method is frozen. New code wanting to extend the
     73    functionality should use the EmailMessage class directly.
     74    """
     75    connection = get_connection(username=auth_user, password=auth_password,
     76                                fail_silently=fail_silently)
     77    messages = [EmailMessage(subject, message, sender, recipient)
     78                for subject, message, sender, recipient in datatuple]
     79    return connection.send_messages(messages)
     80
     81
     82def mail_admins(subject, message, fail_silently=False):
     83    """Sends a message to the admins, as defined by the ADMINS setting."""
     84    if not settings.ADMINS:
     85        return
     86    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
     87                 settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
     88                 ).send(fail_silently=fail_silently)
     89
     90
     91def mail_managers(subject, message, fail_silently=False):
     92    """Sends a message to the managers, as defined by the MANAGERS setting."""
     93    if not settings.MANAGERS:
     94        return
     95    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
     96                 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
     97                 ).send(fail_silently=fail_silently)
  • new file django/core/mail/backends/base.py

    diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/base.py
    - +  
     1"""Base email backend class."""
     2
     3
     4class BaseEmailBackend(object):
     5    """
     6    Base class for email backend implementations.
     7
     8    Subclasses must at least overwrite send_messages().
     9    """
     10
     11    def __init__(self, fail_silently=False, **kwds):
     12        self.fail_silently = fail_silently
     13
     14    def open(self):
     15        """Open a network connection.
     16
     17        This method can be overwritten by backend implementations to
     18        open a network connection. There's no guarantee that this method
     19        is called before an attempt to send one or more emails is made.
     20        But this method can be called by applications to force a single
     21        network connection to be used when sending mails. See the
     22        send_messages() method of the SMTP backend for a reference
     23        implementation.
     24
     25        The default implementation does nothing.
     26        """
     27        pass
     28
     29    def close(self):
     30        """Close a network connection."""
     31        pass
     32
     33    def send_messages(self, email_messages):
     34        """
     35        Sends one or more EmailMessage objects and returns the number of email
     36        messages sent.
     37        """
     38        raise NotImplementedError
  • new file django/core/mail/backends/dummy.py

    diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/dummy.py
    - +  
     1"""
     2Dummy email backend that does nothing.
     3"""
     4
     5
     6from django.core.mail.backends.base import BaseEmailBackend
     7
     8
     9class EmailBackend(BaseEmailBackend):
     10
     11    def send_messages(self, email_messages):
     12        return len(email_messages)
  • new file django/core/mail/backends/log.py

    diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/log.py
    - +  
     1"""
     2Email backend that logs messages instead of sending them.
     3"""
     4
     5import logging
     6
     7from django.core.mail.backends.base import BaseEmailBackend
     8
     9
     10class EmailBackend(BaseEmailBackend):
     11
     12    def send_messages(self, email_messages):
     13        for message in email_messages:
     14            logging.info('Sending email:\n%s' % message.message().as_string())
     15        return len(email_messages)
  • new file django/core/mail/backends/smtp.py

    diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/smtp.py
    - +  
     1"""SMTP email backend class."""
     2
     3import smtplib
     4import socket
     5
     6from django.conf import settings
     7from django.core.mail.backends.base import BaseEmailBackend
     8from django.core.mail.utils import DNS_NAME
     9
     10
     11class EmailBackend(BaseEmailBackend):
     12    """
     13    A wrapper that manages the SMTP network connection.
     14    """
     15
     16    def __init__(self, host=None, port=None, username=None, password=None,
     17                 use_tls=None, fail_silently=False, **kwds):
     18        super(EmailBackend, self).__init__(fail_silently=fail_silently)
     19        self.host = host or settings.EMAIL_HOST
     20        self.port = port or settings.EMAIL_PORT
     21        self.username = username or settings.EMAIL_HOST_USER
     22        self.password = password or settings.EMAIL_HOST_PASSWORD
     23        self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
     24        self.connection = None
     25
     26    def open(self):
     27        """
     28        Ensures we have a connection to the email server. Returns whether or
     29        not a new connection was required (True or False).
     30        """
     31        if self.connection:
     32            # Nothing to do if the connection is already open.
     33            return False
     34        try:
     35            # If local_hostname is not specified, socket.getfqdn() gets used.
     36            # For performance, we use the cached FQDN for local_hostname.
     37            self.connection = smtplib.SMTP(self.host, self.port,
     38                                           local_hostname=DNS_NAME.get_fqdn())
     39            if self.use_tls:
     40                self.connection.ehlo()
     41                self.connection.starttls()
     42                self.connection.ehlo()
     43            if self.username and self.password:
     44                self.connection.login(self.username, self.password)
     45            return True
     46        except:
     47            if not self.fail_silently:
     48                raise
     49
     50    def close(self):
     51        """Closes the connection to the email server."""
     52        try:
     53            try:
     54                self.connection.quit()
     55            except socket.sslerror:
     56                # This happens when calling quit() on a TLS connection
     57                # sometimes.
     58                self.connection.close()
     59            except:
     60                if self.fail_silently:
     61                    return
     62                raise
     63        finally:
     64            self.connection = None
     65
     66    def send_messages(self, email_messages):
     67        """
     68        Sends one or more EmailMessage objects and returns the number of email
     69        messages sent.
     70        """
     71        if not email_messages:
     72            return
     73        new_conn_created = self.open()
     74        if not self.connection:
     75            # We failed silently on open(). Trying to send would be pointless.
     76            return
     77        num_sent = 0
     78        for message in email_messages:
     79            sent = self._send(message)
     80            if sent:
     81                num_sent += 1
     82        if new_conn_created:
     83            self.close()
     84        return num_sent
     85
     86    def _send(self, email_message):
     87        """A helper method that does the actual sending."""
     88        if not email_message.recipients():
     89            return False
     90        try:
     91            self.connection.sendmail(email_message.from_email,
     92                    email_message.recipients(),
     93                    email_message.message().as_string())
     94        except:
     95            if not self.fail_silently:
     96                raise
     97            return False
     98        return True
  • new file django/core/mail/backends/testenv.py

    diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/testenv.py
    - +  
     1"""
     2Backend for test environment.
     3"""
     4
     5from django.core import mail
     6from django.core.mail.backends.base import BaseEmailBackend
     7
     8
     9class EmailBackend(BaseEmailBackend):
     10    """A substitute SMTP connection for use during test sessions.
     11    The test connection stores email messages in a dummy outbox,
     12    rather than sending them out on the wire.
     13
     14    """
     15
     16    def send_messages(self, messages):
     17        "Redirect messages to the dummy outbox"
     18        mail.outbox.extend(messages)
     19        return len(messages)
  • new file django/core/mail/message.py

    diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/message.py
    - +  
     1import mimetypes
     2import os
     3import random
     4import time
     5from email import Charset, Encoders
     6from email.MIMEText import MIMEText
     7from email.MIMEMultipart import MIMEMultipart
     8from email.MIMEBase import MIMEBase
     9from email.Header import Header
     10from email.Utils import formatdate, parseaddr, formataddr
     11
     12from django.conf import settings
     13from django.core.mail.utils import DNS_NAME
     14from django.utils.encoding import smart_str, force_unicode
     15
     16# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
     17# some spam filters.
     18Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
     19
     20# Default MIME type to use on attachments (if it is not explicitly given
     21# and cannot be guessed).
     22DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
     23
     24
     25class BadHeaderError(ValueError):
     26    pass
     27
     28
     29# Copied from Python standard library, with the following modifications:
     30# * Used cached hostname for performance.
     31# * Added try/except to support lack of getpid() in Jython (#5496).
     32def make_msgid(idstring=None):
     33    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
     34
     35    <20020201195627.33539.96671@nightshade.la.mastaler.com>
     36
     37    Optional idstring if given is a string used to strengthen the
     38    uniqueness of the message id.
     39    """
     40    timeval = time.time()
     41    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
     42    try:
     43        pid = os.getpid()
     44    except AttributeError:
     45        # No getpid() in Jython, for example.
     46        pid = 1
     47    randint = random.randrange(100000)
     48    if idstring is None:
     49        idstring = ''
     50    else:
     51        idstring = '.' + idstring
     52    idhost = DNS_NAME
     53    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
     54    return msgid
     55
     56
     57def forbid_multi_line_headers(name, val):
     58    """Forbids multi-line headers, to prevent header injection."""
     59    val = force_unicode(val)
     60    if '\n' in val or '\r' in val:
     61        raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
     62    try:
     63        val = val.encode('ascii')
     64    except UnicodeEncodeError:
     65        if name.lower() in ('to', 'from', 'cc'):
     66            result = []
     67            for item in val.split(', '):
     68                nm, addr = parseaddr(item)
     69                nm = str(Header(nm, settings.DEFAULT_CHARSET))
     70                result.append(formataddr((nm, str(addr))))
     71            val = ', '.join(result)
     72        else:
     73            val = Header(val, settings.DEFAULT_CHARSET)
     74    else:
     75        if name.lower() == 'subject':
     76            val = Header(val)
     77    return name, val
     78
     79
     80class SafeMIMEText(MIMEText):
     81    def __setitem__(self, name, val):
     82        name, val = forbid_multi_line_headers(name, val)
     83        MIMEText.__setitem__(self, name, val)
     84
     85
     86class SafeMIMEMultipart(MIMEMultipart):
     87    def __setitem__(self, name, val):
     88        name, val = forbid_multi_line_headers(name, val)
     89        MIMEMultipart.__setitem__(self, name, val)
     90
     91
     92class EmailMessage(object):
     93    """
     94    A container for email information.
     95    """
     96    content_subtype = 'plain'
     97    mixed_subtype = 'mixed'
     98    encoding = None     # None => use settings default
     99
     100    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
     101                 connection=None, attachments=None, headers=None):
     102        """
     103        Initialize a single email message (which can be sent to multiple
     104        recipients).
     105
     106        All strings used to create the message can be unicode strings
     107        (or UTF-8 bytestrings). The SafeMIMEText class will handle any
     108        necessary encoding conversions.
     109        """
     110        if to:
     111            assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
     112            self.to = list(to)
     113        else:
     114            self.to = []
     115        if bcc:
     116            assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
     117            self.bcc = list(bcc)
     118        else:
     119            self.bcc = []
     120        self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
     121        self.subject = subject
     122        self.body = body
     123        self.attachments = attachments or []
     124        self.extra_headers = headers or {}
     125        self.connection = connection
     126
     127    def get_connection(self, fail_silently=False):
     128        from django.core.mail import get_connection
     129        if not self.connection:
     130            self.connection = get_connection(fail_silently=fail_silently)
     131        return self.connection
     132
     133    def message(self):
     134        encoding = self.encoding or settings.DEFAULT_CHARSET
     135        msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
     136                           self.content_subtype, encoding)
     137        msg = self._create_message(msg)
     138        msg['Subject'] = self.subject
     139        msg['From'] = self.extra_headers.pop('From', self.from_email)
     140        msg['To'] = ', '.join(self.to)
     141
     142        # Email header names are case-insensitive (RFC 2045), so we have to
     143        # accommodate that when doing comparisons.
     144        header_names = [key.lower() for key in self.extra_headers]
     145        if 'date' not in header_names:
     146            msg['Date'] = formatdate()
     147        if 'message-id' not in header_names:
     148            msg['Message-ID'] = make_msgid()
     149        for name, value in self.extra_headers.items():
     150            msg[name] = value
     151        return msg
     152
     153    def recipients(self):
     154        """
     155        Returns a list of all recipients of the email (includes direct
     156        addressees as well as Bcc entries).
     157        """
     158        return self.to + self.bcc
     159
     160    def send(self, fail_silently=False):
     161        """Sends the email message."""
     162        if not self.recipients():
     163            # Don't bother creating the network connection if there's nobody to
     164            # send to.
     165            return 0
     166        return self.get_connection(fail_silently).send_messages([self])
     167
     168    def attach(self, filename=None, content=None, mimetype=None):
     169        """
     170        Attaches a file with the given filename and content. The filename can
     171        be omitted and the mimetype is guessed, if not provided.
     172
     173        If the first parameter is a MIMEBase subclass it is inserted directly
     174        into the resulting message attachments.
     175        """
     176        if isinstance(filename, MIMEBase):
     177            assert content == mimetype == None
     178            self.attachments.append(filename)
     179        else:
     180            assert content is not None
     181            self.attachments.append((filename, content, mimetype))
     182
     183    def attach_file(self, path, mimetype=None):
     184        """Attaches a file from the filesystem."""
     185        filename = os.path.basename(path)
     186        content = open(path, 'rb').read()
     187        self.attach(filename, content, mimetype)
     188
     189    def _create_message(self, msg):
     190        return self._create_attachments(msg)
     191
     192    def _create_attachments(self, msg):
     193        if self.attachments:
     194            body_msg = msg
     195            msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
     196            if self.body:
     197                msg.attach(body_msg)
     198            for attachment in self.attachments:
     199                if isinstance(attachment, MIMEBase):
     200                    msg.attach(attachment)
     201                else:
     202                    msg.attach(self._create_attachment(*attachment))
     203        return msg
     204
     205    def _create_mime_attachment(self, content, mimetype):
     206        """
     207        Converts the content, mimetype pair into a MIME attachment object.
     208        """
     209        basetype, subtype = mimetype.split('/', 1)
     210        if basetype == 'text':
     211            attachment = SafeMIMEText(smart_str(content,
     212                settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
     213        else:
     214            # Encode non-text attachments with base64.
     215            attachment = MIMEBase(basetype, subtype)
     216            attachment.set_payload(content)
     217            Encoders.encode_base64(attachment)
     218        return attachment
     219
     220    def _create_attachment(self, filename, content, mimetype=None):
     221        """
     222        Converts the filename, content, mimetype triple into a MIME attachment
     223        object.
     224        """
     225        if mimetype is None:
     226            mimetype, _ = mimetypes.guess_type(filename)
     227            if mimetype is None:
     228                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
     229        attachment = self._create_mime_attachment(content, mimetype)
     230        if filename:
     231            attachment.add_header('Content-Disposition', 'attachment',
     232                                  filename=filename)
     233        return attachment
     234
     235
     236class EmailMultiAlternatives(EmailMessage):
     237    """
     238    A version of EmailMessage that makes it easy to send multipart/alternative
     239    messages. For example, including text and HTML versions of the text is
     240    made easier.
     241    """
     242    alternative_subtype = 'alternative'
     243
     244    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
     245            connection=None, attachments=None, headers=None, alternatives=None):
     246        """
     247        Initialize a single email message (which can be sent to multiple
     248        recipients).
     249
     250        All strings used to create the message can be unicode strings (or UTF-8
     251        bytestrings). The SafeMIMEText class will handle any necessary encoding
     252        conversions.
     253        """
     254        super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
     255        self.alternatives=alternatives or []
     256
     257    def attach_alternative(self, content, mimetype):
     258        """Attach an alternative content representation."""
     259        assert content is not None
     260        assert mimetype is not None
     261        self.alternatives.append((content, mimetype))
     262
     263    def _create_message(self, msg):
     264        return self._create_attachments(self._create_alternatives(msg))
     265
     266    def _create_alternatives(self, msg):
     267        if self.alternatives:
     268            body_msg = msg
     269            msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
     270            if self.body:
     271                msg.attach(body_msg)
     272            for alternative in self.alternatives:
     273                msg.attach(self._create_mime_attachment(*alternative))
     274        return msg
  • new file django/core/mail/utils.py

    diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/utils.py
    - +  
     1"""
     2Email message and email sending related helper functions.
     3"""
     4
     5import socket
     6
     7
     8# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
     9# seconds, which slows down the restart of the server.
     10class CachedDnsName(object):
     11    def __str__(self):
     12        return self.get_fqdn()
     13
     14    def get_fqdn(self):
     15        if not hasattr(self, '_fqdn'):
     16            self._fqdn = socket.getfqdn()
     17        return self._fqdn
     18
     19DNS_NAME = CachedDnsName()
  • django/test/utils.py

    diff -r 4d79b1a3175f -r c00b6982431e django/test/utils.py
    a b  
    2828    signals.template_rendered.send(sender=self, template=self, context=context)
    2929    return self.nodelist.render(context)
    3030
    31 class TestSMTPConnection(object):
    32     """A substitute SMTP connection for use during test sessions.
    33     The test connection stores email messages in a dummy outbox,
    34     rather than sending them out on the wire.
    35 
    36     """
    37     def __init__(*args, **kwargs):
    38         pass
    39     def open(self):
    40         "Mock the SMTPConnection open() interface"
    41         pass
    42     def close(self):
    43         "Mock the SMTPConnection close() interface"
    44         pass
    45     def send_messages(self, messages):
    46         "Redirect messages to the dummy outbox"
    47         mail.outbox.extend(messages)
    48         return len(messages)
    4931
    5032def setup_test_environment():
    5133    """Perform any global pre-test setup. This involves:
    5234
    5335        - Installing the instrumented test renderer
    54         - Diverting the email sending functions to a test buffer
     36        - Replacing the email backend with the email backend used for testing.
    5537        - Setting the active locale to match the LANGUAGE_CODE setting.
    5638    """
    5739    Template.original_render = Template.render
    5840    Template.render = instrumented_test_render
    5941
    60     mail.original_SMTPConnection = mail.SMTPConnection
    61     mail.SMTPConnection = TestSMTPConnection
     42    mail.original_email_backend = settings.EMAIL_BACKEND
     43    settings.EMAIL_BACKEND = settings.TEST_EMAIL_BACKEND
    6244
    6345    mail.outbox = []
    6446
     
    7456    Template.render = Template.original_render
    7557    del Template.original_render
    7658
    77     mail.SMTPConnection = mail.original_SMTPConnection
    78     del mail.original_SMTPConnection
     59    settings.EMAIL_BACKEND = mail.original_email_backend
     60    del mail.original_email_backend
    7961
    8062    del mail.outbox
    8163
  • tests/modeltests/test_client/views.py

    diff -r 4d79b1a3175f -r c00b6982431e tests/modeltests/test_client/views.py
    a b  
    11from xml.dom.minidom import parseString
    22
     3from django.core import mail
    34from django.core.mail import EmailMessage, SMTPConnection
    45from django.template import Context, Template
    56from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
     
    201202        'from@example.com',
    202203        ['second@example.com', 'third@example.com'])
    203204
    204     c = SMTPConnection()
     205    c = mail.get_connection()
     206    c.open()
    205207    c.send_messages([m1,m2])
     208    c.close()
    206209
    207210    return HttpResponse("Mail sent")
  • tests/regressiontests/mail/tests.py

    diff -r 4d79b1a3175f -r c00b6982431e tests/regressiontests/mail/tests.py
    a b  
    55>>> from django.conf import settings
    66>>> from django.core import mail
    77>>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
     8>>> from django.core.mail.backends.base import BaseEmailBackend
    89>>> from django.utils.translation import ugettext_lazy
    910
    1011# Test normal ascii character case:
     
    138139JVBERi0xLjQuJS4uLg==
    139140...
    140141
     142# Make sure all builtin email backends load correctly.
     143>>> failed = []
     144>>> for name in mail.BACKENDS:
     145...   settings.EMAIL_BACKEND = name
     146...   conn = mail.get_connection(foo='bar')
     147...   if not isinstance(conn, BaseEmailBackend):
     148...     failed.append(name)
     149>>> failed
     150[]
     151>>> settings.EMAIL_BACKEND = 'testenv' # reset test backend
     152
     153# Make sure that get_connection() accepts arbitrary keyword that might be
     154# used with custom backends.
     155>>> c = mail.get_connection(fail_silently=True, foo='bar')
     156>>> c.fail_silently
     157True
    141158"""
  • docs/ref/settings.txt

    Änderung:        7297:54962e5dce18
    Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
    Datum:           Mon Aug 24 06:46:46 2009 +0200
    Zusammenfassung: Document email backend settings.
    
    diff -r c00b6982431e -r 54962e5dce18 docs/ref/settings.txt
    a b  
    379379This is only used if ``CommonMiddleware`` is installed (see
    380380:ref:`topics-http-middleware`).
    381381
     382.. setting:: EMAIL_BACKEND
     383
     384EMAIL_BACKEND
     385-------------
     386
     387.. versionadded:: 1.2
     388
     389Default: ``'smtp'``
     390
     391The backend to use for sending emails. For available backend see
     392:ref:`topics-email`.
     393
    382394.. setting:: EMAIL_HOST
    383395
    384396EMAIL_HOST
     
    10981110
    10991111See :ref:`topics-testing`.
    11001112
     1113.. setting:: TEST_EMAIL_BACKEND
     1114
     1115TEST_EMAIL_BACKEND
     1116------------------
     1117
     1118.. versionadded:: 1.2
     1119
     1120Default: ``'testenv'``
     1121
     1122The email backend to use when running the test suite.
     1123
     1124See :ref:`topics-email`.
     1125
    11011126.. setting:: TEST_RUNNER
    11021127
    11031128TEST_RUNNER
  • django/core/mail/backends/log.py

    Änderung:        7298:fab52dcbb802
    Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
    Datum:           Mon Aug 24 09:53:09 2009 +0200
    Zusammenfassung: Change log message.
    
    diff -r 54962e5dce18 -r fab52dcbb802 django/core/mail/backends/log.py
    a b  
    1111
    1212    def send_messages(self, email_messages):
    1313        for message in email_messages:
    14             logging.info('Sending email:\n%s' % message.message().as_string())
     14            logging.info(message.message().as_string())
     15            logging.info('This email is just logged. No real email was sent.')
    1516        return len(email_messages)
  • django/conf/global_settings.py

    Änderung:        7299:864d5ad60910
    Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
    Datum:           Mon Aug 24 13:39:11 2009 +0200
    Zusammenfassung: Docs and cleanup.
    
    diff -r fab52dcbb802 -r 864d5ad60910 django/conf/global_settings.py
    a b  
    137137EMAIL_BACKEND = 'smtp'
    138138
    139139# The email backend to use for testing.
    140 TEST_EMAIL_BACKEND = 'testenv'
     140TEST_EMAIL_BACKEND = 'test'
    141141
    142142# Host for sending e-mail.
    143143EMAIL_HOST = 'localhost'
  • django/core/mail/__init__.py

    diff -r fab52dcbb802 -r 864d5ad60910 django/core/mail/__init__.py
    a b  
    1313
    1414# Shortcuts for builtin email backends. If settings.EMAIL_BACKEND isn't in
    1515# this list it's treated as a Python import path.
    16 BACKENDS = ('smtp', 'dummy', 'log', 'testenv')
     16BACKENDS = ('smtp', 'dummy', 'log', 'test')
    1717
    1818
    1919def get_connection(fail_silently=False, **kwds):
  • new file django/core/mail/backends/test.py

    diff -r fab52dcbb802 -r 864d5ad60910 django/core/mail/backends/test.py
    - +  
     1"""
     2Backend for test environment.
     3"""
     4
     5from django.core import mail
     6from django.core.mail.backends.base import BaseEmailBackend
     7
     8
     9class EmailBackend(BaseEmailBackend):
     10    """A substitute SMTP connection for use during test sessions.
     11    The test connection stores email messages in a dummy outbox,
     12    rather than sending them out on the wire.
     13
     14    """
     15
     16    def send_messages(self, messages):
     17        "Redirect messages to the dummy outbox"
     18        mail.outbox.extend(messages)
     19        return len(messages)
  • deleted file django/core/mail/backends/testenv.py

    diff -r fab52dcbb802 -r 864d5ad60910 django/core/mail/backends/testenv.py
    + -  
    1 """
    2 Backend for test environment.
    3 """
    4 
    5 from django.core import mail
    6 from django.core.mail.backends.base import BaseEmailBackend
    7 
    8 
    9 class EmailBackend(BaseEmailBackend):
    10     """A substitute SMTP connection for use during test sessions.
    11     The test connection stores email messages in a dummy outbox,
    12     rather than sending them out on the wire.
    13 
    14     """
    15 
    16     def send_messages(self, messages):
    17         "Redirect messages to the dummy outbox"
    18         mail.outbox.extend(messages)
    19         return len(messages)
  • docs/ref/settings.txt

    diff -r fab52dcbb802 -r 864d5ad60910 docs/ref/settings.txt
    a b  
    11171117
    11181118.. versionadded:: 1.2
    11191119
    1120 Default: ``'testenv'``
     1120Default: ``'test'``
    11211121
    11221122The email backend to use when running the test suite.
    11231123
  • docs/topics/email.txt

    diff -r fab52dcbb802 -r 864d5ad60910 docs/topics/email.txt
    a b  
    178178
    179179.. _emailmessage-and-smtpconnection:
    180180
    181 The EmailMessage and SMTPConnection classes
    182 ===========================================
     181The EmailMessage class
     182======================
    183183
    184184.. versionadded:: 1.0
    185185
    186186Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
    187 wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
    188 in ``django.core.mail``.  If you ever need to customize the way Django sends
    189 e-mail, you can subclass these two classes to suit your needs.
     187wrappers that make use of the ``EmailMessage`` class in ``django.core.mail``.
     188
     189Not all features of the ``EmailMessage`` class are available through the
     190``send_mail()`` and related wrapper functions. If you wish to use advanced
     191features, such as BCC'ed recipients, file attachments, or multi-part
     192e-mail, you'll need to create ``EmailMessage`` instances directly.
    190193
    191194.. note::
    192     Not all features of the ``EmailMessage`` class are available through the
    193     ``send_mail()`` and related wrapper functions. If you wish to use advanced
    194     features, such as BCC'ed recipients, file attachments, or multi-part
    195     e-mail, you'll need to create ``EmailMessage`` instances directly.
    196195
    197     This is a design feature. ``send_mail()`` and related functions were
    198     originally the only interface Django provided. However, the list of
    199     parameters they accepted was slowly growing over time. It made sense to
    200     move to a more object-oriented design for e-mail messages and retain the
    201     original functions only for backwards compatibility.
     196    This is a design feature. ``send_mail()`` and related functions
     197    were originally the only interface Django provided. However, the
     198    list of parameters they accepted was slowly growing over time. It
     199    made sense to move to a more object-oriented design for e-mail
     200    messages and retain the original functions only for backwards
     201    compatibility.
    202202
    203203In general, ``EmailMessage`` is responsible for creating the e-mail message
    204 itself. ``SMTPConnection`` is responsible for the network connection side of
    205 the operation. This means you can reuse the same connection (an
    206 ``SMTPConnection`` instance) for multiple messages.
     204itself. The email backend is responsible for sending the email. By default
     205the SMTP backend is used to send emails using a SMTP server.
    207206
    208207EmailMessage Objects
    209208--------------------
     
    227226    * ``bcc``: A list or tuple of addresses used in the "Bcc" header when
    228227      sending the e-mail.
    229228
    230     * ``connection``: An ``SMTPConnection`` instance. Use this parameter if
     229    * ``connection``: An email backend instance. Use this parameter if
    231230      you want to use the same connection for multiple messages. If omitted, a
    232231      new connection is created when ``send()`` is called.
    233232
     
    331330    msg.content_subtype = "html"  # Main content is now text/html
    332331    msg.send()
    333332
    334 SMTPConnection Objects
    335 ----------------------
     333
     334Email Backends
     335==============
     336
     337.. versionadded:: 1.2
     338
     339The actual sending of an email is handled by the email backend.
     340By default the SMTP backend will be used.
     341
     342Your email backend preferences goes in the ``EMAIL_BACKEND`` setting in
     343your settings file. Here's an explanation of all available values for
     344``EMAIL_BACKEND``.
     345
     346SMTP backend
     347------------
     348
     349This is the default backend. Email will be sent through a SMTP server.
     350The server address and authentication credentials are set in the
     351:setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`,
     352:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
     353settings file.
     354
     355The SMTP is configured in the default settings::
     356
     357    EMAIL_BACKEND = 'smtp'
     358
     359
     360SMTPConnection objects
     361~~~~~~~~~~~~~~~~~~~~~~
    336362
    337363.. class:: SMTPConnection
    338364
     365For backwards compatibility the SMTP backend is available in
     366``django.core.mail`` as the ``SMTPConnection`` class.
     367
    339368The ``SMTPConnection`` class is initialized with the host, port, username and
    340369password for the SMTP server. If you don't specify one or more of those
    341370options, they are read from your settings file.
     
    351380    messages = get_notification_email()
    352381    connection.send_messages(messages)
    353382
    354 Testing e-mail sending
    355 ----------------------
     383If you want to resuse the same connection for multiple messages, but can't
     384use ``send_messages()``, you'll have to use the ``open()`` and ``close()``
     385methods of the email backend::
     386
     387    from django.core import mail
     388
     389    connection = mail.get_connection()
     390    connection.open()
     391    email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
     392                               ['to1@example.com'], connection=connection)
     393    email1.send()
     394    email2 = mail.EmailMessage('Hello again', 'Body goes here',
     395                               'from@example.com', ['to1@example.com'],
     396                               connection=connection)
     397    email2.send()
     398    connection.close()
     399
     400
     401Logging backend (for development)
     402---------------------------------
     403
     404Instead of sending out real emails the logging backend just logs the emails
     405that would be send out to the standard output. To use this backend, use
     406in your settings::
     407
     408    EMAIL_BACKEND = 'log'
     409
     410Dummy backend (for development)
     411-------------------------------
     412
     413As the name suggests the dummy backend does nothing with your emails.
     414To enable this backend, use in your settings file::
     415
     416   EMAIL_BACKEND = 'dummy'
     417
     418This backend can be useful during development, if you even want to suppress
     419the log entries written by the logging backend.
     420
     421Using a custom email backend
     422----------------------------
     423
     424If you need to change how emails are send you can write your own email
     425backend. The ``EMAIL_BACKEND`` setting in your settings file is then the
     426Python import path pointing to your custom backend class.
     427
     428Custom email backends should subclass ``BaseEmailBackend`` that is located
     429in the ``django.core.mail.backends.base`` module. A custom email
     430backend must implement the ``send_messages(email_messages)`` method. This
     431method receives a list of ``EMailMessage`` instances and returns the number
     432of successfully delivered messages. If your backend should be capable of
     433network connection handling, you should implement both the ``open()``
     434and ``close()`` methods too. Refer to ``SMTPEmailBackend`` for a reference
     435implementation of those methods.
     436
     437.. note::
     438
     439   If you write test suites for your email backend you should use the
     440   Python import path pointing to your email backend both for the
     441   ``EMAIL_BACKEND`` and ``TEST_EMAIL_BACKEND`` in your settings file.
     442   This will change the default email backend when running test suites.
     443
     444
     445Testing Email Sending
     446=====================
    356447
    357448The are times when you do not want Django to send e-mails at all. For example,
    358449while developing a website, you probably don't want to send out thousands of
     
    360451people under the right conditions, and that those e-mails will contain the
    361452correct content.
    362453
    363 The easiest way to test your project's use of e-mail is to use a "dumb" e-mail
    364 server that receives the e-mails locally and displays them to the terminal,
    365 but does not actually send anything. Python has a built-in way to accomplish
    366 this with a single command::
     454The easiest way to test your project's use of e-mail is to change the
     455``EMAIL_BACKEND`` setting in your settings file to ``'log'`` to use the
     456logging backend that doesn't send any email but writes them to the
     457terminal.
     458
     459Alternatively use a "dumb" e-mail server that receives the e-mails locally
     460and displays them to the terminal, but does not actually send anything.
     461Python has a built-in way to accomplish this with a single command::
    367462
    368463    python -m smtpd -n -c DebuggingServer localhost:1025
    369464
  • tests/regressiontests/mail/tests.py

    diff -r fab52dcbb802 -r 864d5ad60910 tests/regressiontests/mail/tests.py
    a b  
    142142# Make sure all builtin email backends load correctly.
    143143>>> failed = []
    144144>>> for name in mail.BACKENDS:
    145 ...   settings.EMAIL_BACKEND = name
     145...   settings.TEST_EMAIL_BACKEND = name
    146146...   conn = mail.get_connection(foo='bar')
    147147...   if not isinstance(conn, BaseEmailBackend):
    148148...     failed.append(name)
    149149>>> failed
    150150[]
    151 >>> settings.EMAIL_BACKEND = 'testenv' # reset test backend
     151>>> settings.TEST_EMAIL_BACKEND = 'test' # reset test backend
    152152
    153153# Make sure that get_connection() accepts arbitrary keyword that might be
    154154# used with custom backends.
  • django/core/mail/backends/base.py

    Änderung:        7300:f480711a4050
    Marke:           tip
    Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
    Datum:           Mon Aug 24 14:39:16 2009 +0200
    Zusammenfassung: Minor fixes after personal review.
    
    diff -r 864d5ad60910 -r f480711a4050 django/core/mail/backends/base.py
    a b  
    1515        """Open a network connection.
    1616
    1717        This method can be overwritten by backend implementations to
    18         open a network connection. There's no guarantee that this method
    19         is called before an attempt to send one or more emails is made.
    20         But this method can be called by applications to force a single
     18        open a network connection.
     19
     20        It's up to the backend implementation to track the status of
     21        a network connection if it's needed by the backend.
     22
     23        This method can be called by applications to force a single
    2124        network connection to be used when sending mails. See the
    2225        send_messages() method of the SMTP backend for a reference
    2326        implementation.
  • django/core/mail/backends/test.py

    diff -r 864d5ad60910 -r f480711a4050 django/core/mail/backends/test.py
    a b  
    77
    88
    99class EmailBackend(BaseEmailBackend):
    10     """A substitute SMTP connection for use during test sessions.
     10    """A email backend for use during test sessions.
     11
    1112    The test connection stores email messages in a dummy outbox,
    1213    rather than sending them out on the wire.
    1314
     15    The backend requires the 'django.core.mail' module to provide
     16    a 'outbox' attribute (a list). This attribute is set during the setup
     17    of the test environment and removed when the test environment is
     18    teared down.
    1419    """
    1520
    1621    def send_messages(self, messages):
  • tests/modeltests/test_client/views.py

    diff -r 864d5ad60910 -r f480711a4050 tests/modeltests/test_client/views.py
    a b  
    203203        ['second@example.com', 'third@example.com'])
    204204
    205205    c = mail.get_connection()
    206     c.open()
    207206    c.send_messages([m1,m2])
    208     c.close()
    209207
    210208    return HttpResponse("Mail sent")
  • tests/regressiontests/mail/tests.py

    diff -r 864d5ad60910 -r f480711a4050 tests/regressiontests/mail/tests.py
    a b  
    142142# Make sure all builtin email backends load correctly.
    143143>>> failed = []
    144144>>> for name in mail.BACKENDS:
    145 ...   settings.TEST_EMAIL_BACKEND = name
     145...   settings.EMAIL_BACKEND = name
    146146...   conn = mail.get_connection(foo='bar')
    147147...   if not isinstance(conn, BaseEmailBackend):
    148148...     failed.append(name)
    149149>>> failed
    150150[]
    151 >>> settings.TEST_EMAIL_BACKEND = 'test' # reset test backend
     151>>> settings.EMAIL_BACKEND = 'test' # reset test backend
    152152
    153153# Make sure that get_connection() accepts arbitrary keyword that might be
    154154# used with custom backends.
Back to Top