Django

Code

Changeset 5141

Show
Ignore:
Timestamp:
05/03/07 06:35:11 (2 years ago)
Author:
mtredinnick
Message:

Fixed #3366 -- Part 1 of the email code refactoring and feature extension. This
part refactors email sending into a more object-oriented interface in order to
make adding new features possible without making the API unusable. Thanks to
Gary Wilson for doing the design thinking and initial coding on this.

Includes documentation addition, but it probably needs a rewrite/edit, since
I'm not very happy with it at the moment.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/core/mail.py

    r5041 r5141  
    1 # Use this module for e-mailing. 
     1""" 
     2Tools for sending email. 
     3""" 
    24 
    35from django.conf import settings 
     
    57from email.Header import Header 
    68from email.Utils import formatdate 
     9import os 
    710import smtplib 
    811import socket 
     
    2225 
    2326DNS_NAME = CachedDnsName() 
     27 
     28# Copied from Python standard library and modified to used the cached hostname 
     29# for performance. 
     30def make_msgid(idstring=None): 
     31    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g: 
     32 
     33    <20020201195627.33539.96671@nightshade.la.mastaler.com> 
     34 
     35    Optional idstring if given is a string used to strengthen the 
     36    uniqueness of the message id. 
     37    """ 
     38    timeval = time.time() 
     39    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval)) 
     40    pid = os.getpid() 
     41    randint = random.randrange(100000) 
     42    if idstring is None: 
     43        idstring = '' 
     44    else: 
     45        idstring = '.' + idstring 
     46    idhost = DNS_NAME 
     47    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost) 
     48    return msgid 
    2449 
    2550class BadHeaderError(ValueError): 
     
    3560        MIMEText.__setitem__(self, name, val) 
    3661 
     62class SMTPConnection(object): 
     63    """ 
     64    A wrapper that manages the SMTP network connection. 
     65    """ 
     66 
     67    def __init__(self, host=None, port=None, username=None, password=None, 
     68                 fail_silently=False): 
     69        if host is None: 
     70            self.host = settings.EMAIL_HOST 
     71        if port is None: 
     72            self.port = settings.EMAIL_PORT 
     73        if username is None: 
     74                self.username = settings.EMAIL_HOST_USER 
     75        if password is None: 
     76                self.password = settings.EMAIL_HOST_PASSWORD 
     77        self.fail_silently = fail_silently 
     78        self.connection = None 
     79 
     80    def open(self): 
     81        """ 
     82        Ensure we have a connection to the email server. Returns whether or not 
     83        a new connection was required. 
     84        """ 
     85        if self.connection: 
     86            # Nothing to do if the connection is already open. 
     87            return False 
     88        try: 
     89            self.connection = smtplib.SMTP(self.host, self.port) 
     90            if self.username and self.password: 
     91                self.connection.login(self.username, self.password) 
     92            return True 
     93        except: 
     94            if not self.fail_silently: 
     95                raise 
     96 
     97    def close(self): 
     98        """Close the connection to the email server.""" 
     99        try: 
     100            try: 
     101                self.connection.quit() 
     102            except: 
     103                if self.fail_silently: 
     104                    return 
     105                raise 
     106        finally: 
     107            self.connection = None 
     108 
     109    def send_messages(self, email_messages): 
     110        """ 
     111        Send one or more EmailMessage objects and return the number of email 
     112        messages sent. 
     113        """ 
     114        if not email_messages: 
     115            return 
     116        new_conn_created = self.open() 
     117        if not self.connection: 
     118            # We failed silently on open(). Trying to send would be pointless. 
     119            return 
     120        num_sent = 0 
     121        for message in email_messages: 
     122            sent = self._send(message) 
     123            if sent: 
     124                num_sent += 1 
     125        if new_conn_created: 
     126            self.close() 
     127        return num_sent 
     128 
     129    def _send(self, email_message): 
     130        """A helper method that does the actual sending.""" 
     131        if not email_message.to: 
     132            return False 
     133        try: 
     134            self.connection.sendmail(email_message.from_email, 
     135                    email_message.to, email_message.message.as_string()()) 
     136        except: 
     137            if not self.fail_silently: 
     138                raise 
     139            return False 
     140        return True 
     141 
     142class EmailMessage(object): 
     143    """ 
     144    A container for email information. 
     145    """ 
     146    def __init__(self, subject='', body='', from_email=None, to=None, connection=None): 
     147        self.to = to or [] 
     148        if from_email is None: 
     149            self.from_email = settings.DEFAULT_FROM_EMAIL 
     150        else: 
     151            self.from_email = from_email 
     152        self.subject = subject 
     153        self.body = body 
     154        self.connection = connection 
     155 
     156    def get_connection(self, fail_silently=False): 
     157        if not self.connection: 
     158            self.connection = SMTPConnection(fail_silently=fail_silently) 
     159        return self.connection 
     160 
     161    def message(self): 
     162        msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET) 
     163        msg['Subject'] = self.subject 
     164        msg['From'] = self.from_email 
     165        msg['To'] = ', '.join(self.to) 
     166        msg['Date'] = formatdate() 
     167        msg['Message-ID'] = make_msgid() 
     168 
     169    def send(self, fail_silently=False): 
     170        """Send the email message.""" 
     171        return self.get_connection(fail_silently).send_messages([self]) 
     172 
    37173def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None): 
    38174    """ 
     
    42178    If auth_user is None, the EMAIL_HOST_USER setting is used. 
    43179    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. 
    44     """ 
    45     return send_mass_mail([[subject, message, from_email, recipient_list]], fail_silently, auth_user, auth_password) 
     180 
     181    NOTE: This method is deprecated. It exists for backwards compatibility. 
     182    New code should use the EmailMessage class directly. 
     183    """ 
     184    connection = SMTPConnection(username=auth_user, password=auth_password, 
     185                                 fail_silently=fail_silently) 
     186    return EmailMessage(subject, message, from_email, recipient_list, connection=connection).send() 
    46187 
    47188def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None): 
     
    54195    If auth_user is None, the EMAIL_HOST_USER setting is used. 
    55196    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. 
    56     """ 
    57     if auth_user is None: 
    58         auth_user = settings.EMAIL_HOST_USER 
    59     if auth_password is None: 
    60         auth_password = settings.EMAIL_HOST_PASSWORD 
    61     try: 
    62         server = smtplib.SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT) 
    63         if auth_user and auth_password: 
    64             server.login(auth_user, auth_password) 
    65     except: 
    66         if fail_silently: 
    67             return 
    68         raise 
    69     num_sent = 0 
    70     for subject, message, from_email, recipient_list in datatuple: 
    71         if not recipient_list: 
    72             continue 
    73         from_email = from_email or settings.DEFAULT_FROM_EMAIL 
    74         msg = SafeMIMEText(message, 'plain', settings.DEFAULT_CHARSET) 
    75         msg['Subject'] = subject 
    76         msg['From'] = from_email 
    77         msg['To'] = ', '.join(recipient_list) 
    78         msg['Date'] = formatdate() 
    79         try: 
    80             random_bits = str(random.getrandbits(64)) 
    81         except AttributeError: # Python 2.3 doesn't have random.getrandbits(). 
    82             random_bits = ''.join([random.choice('1234567890') for i in range(19)]) 
    83         msg['Message-ID'] = "<%d.%s@%s>" % (time.time(), random_bits, DNS_NAME) 
    84         try: 
    85             server.sendmail(from_email, recipient_list, msg.as_string()) 
    86             num_sent += 1 
    87         except: 
    88             if not fail_silently: 
    89                 raise 
    90     try: 
    91         server.quit() 
    92     except: 
    93         if fail_silently: 
    94             return 
    95         raise 
    96     return num_sent 
     197 
     198    NOTE: This method is deprecated. It exists for backwards compatibility. 
     199    New code should use the EmailMessage class directly. 
     200    """ 
     201    connection = SMTPConnection(username=auth_user, password=auth_password, 
     202                                 fail_silently=fail_silently) 
     203    messages = [EmailMessage(subject, message, sender, recipient) for subject, message, sender, recipient in datatuple] 
     204    return connection.send_messages(messages) 
    97205 
    98206def mail_admins(subject, message, fail_silently=False): 
    99207    "Sends a message to the admins, as defined by the ADMINS setting." 
    100     send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], fail_silently) 
     208    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, 
     209            settings.SERVER_EMAIL, [a[1] for a in 
     210                settings.ADMINS]).send(fail_silently=fail_silently) 
    101211 
    102212def mail_managers(subject, message, fail_silently=False): 
    103213    "Sends a message to the managers, as defined by the MANAGERS setting." 
    104     send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], fail_silently) 
     214    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, 
     215            settings.SERVER_EMAIL, [a[1] for a in 
     216                settings.MANAGERS]).send(fail_silently=fail_silently) 
     217 
  • django/trunk/docs/email.txt

    r5121 r5141  
    184184 
    185185.. _Header injection: http://securephp.damonkohler.com/index.php/Email_Injection 
     186 
     187The EmailMessage and SMTPConnection classes 
     188=========================================== 
     189 
     190Django's `send_mail()` and `send_mass_mail()` functions are actually thin 
     191wrappers that make use of the `EmailMessage` and `SMTPConnection` classes in 
     192`django.mail`.  If you ever need to customize the way Django sends email, you 
     193can subclass these two classes to suit your needs. 
     194 
     195.. note:: 
     196    Not all features of the `EmailMessage` class are available through the 
     197    `send_mail()` and related wrapper functions. If you wish to use advanced 
     198    features such as including BCC recipients or multi-part email, you will 
     199    need to create `EmailMessage` instances directly. 
     200 
     201In general, `EmailMessage` is responsible for creating the email message 
     202itself. `SMTPConnection` is responsible for the network connection side of the 
     203operation. This means you can reuse the same connection (an `SMTPConnection` 
     204instance) for multiple messages. 
     205 
     206The `EmailMessage` class has the following methods that you can use: 
     207 
     208 * `send()` sends the message, using either the connection that is specified 
     209   in the `connection` attribute, or creating a new connection if none already 
     210   exists. 
     211 * `message()` constructs a `django.core.mail.SafeMIMEText` object (a 
     212   sub-class of Python's `email.MIMEText.MIMEText` class) holding the message 
     213   to be sent. If you ever need to extend the `EmailMessage` class, you will 
     214   probably want to override this method to put the content you wish into the 
     215   MIME object. 
     216 
     217The `SMTPConnection` class is initialized with the host, port, username and 
     218password for the SMTP server. If you don't specify one or more of those 
     219options, they are read from your settings file. 
     220 
     221If you are sending lots of messages at once, the `send_messages()` method of 
     222the `SMTPConnection` class will be useful. It takes a list of `EmailMessage` 
     223instances (or sub-classes) and sends them over a single connection. For 
     224example, if you have a function called `get_notification_email()` that returns a 
     225list of `EmailMessage` objects representing some periodic email you wish to 
     226send out, you could send this with:: 
     227 
     228    connection = SMTPConnection()   # Use default settings for connection 
     229    messages = get_notification_email() 
     230    connection.send_messages(messages) 
     231