Django

Code

Ticket #10355: email_backends.diff

File email_backends.diff, 59.2 kB (added by andialbrecht, 7 months ago)

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

  • a/django/conf/global_settings.py

    old new  
    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 
  • /dev/null

    old new  
    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) 
  • /dev/null

    old new  
     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) 
  • /dev/null

    old new  
     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 
  • /dev/null

    old new  
     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) 
  • /dev/null

    old new  
     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) 
  • /dev/null

    old new  
     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 
  • /dev/null

    old new  
     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) 
  • /dev/null

    old new  
     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 
  • /dev/null

    old new  
     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() 
  • a/django/test/utils.py

    old new  
    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 
  • a/tests/modeltests/test_client/views.py

    old new  
    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") 
  • a/tests/regressiontests/mail/tests.py

    old new  
    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""" 
  • a/docs/ref/settings.txt

    old new  
    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 
  • a/django/core/mail/backends/log.py

    old new  
    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) 
  • a/django/conf/global_settings.py

    old new  
    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' 
  • a/django/core/mail/__init__.py

    old new  
    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): 
  • /dev/null

    old new  
     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) 
  • /dev/null

    old new  
    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) 
  • a/docs/ref/settings.txt

    old new  
    11171117 
    11181118.. versionadded:: 1.2 
    11191119 
    1120 Default: ``'testenv'`` 
     1120Default: ``'test'`` 
    11211121 
    11221122The email backend to use when running the test suite. 
    11231123 
  • a/docs/topics/email.txt

    old new  
    178178 
    179179.. _emailmessage-and-smtpconnection: 
    180180 
    181 The EmailMessage and SMTPConnection classe
    182 =========================================== 
     181The EmailMessage clas
     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 
  • a/tests/regressiontests/mail/tests.py

    old new  
    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. 
  • a/django/core/mail/backends/base.py

    old new  
    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. 
  • a/django/core/mail/backends/test.py

    old new  
    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): 
  • a/tests/modeltests/test_client/views.py

    old new  
    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") 
  • a/tests/regressiontests/mail/tests.py

    old new  
    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.