Author: bouldersprinters
Date: 2007-05-03 14:26:47 -0500 (Thu, 03 May 2007)
New Revision: 5148

Modified:
   django/branches/boulder-oracle-sprint/django/conf/global_settings.py
   django/branches/boulder-oracle-sprint/django/core/mail.py
   django/branches/boulder-oracle-sprint/docs/documentation.txt
   django/branches/boulder-oracle-sprint/docs/email.txt
   django/branches/boulder-oracle-sprint/docs/sessions.txt
   django/branches/boulder-oracle-sprint/docs/settings.txt
   django/branches/boulder-oracle-sprint/docs/syndication_feeds.txt
   django/branches/boulder-oracle-sprint/docs/templates_python.txt
Log:
boulder-oracle-sprint: Merged to [5147]

Modified: django/branches/boulder-oracle-sprint/django/conf/global_settings.py
===================================================================
--- django/branches/boulder-oracle-sprint/django/conf/global_settings.py        
2007-05-03 17:52:38 UTC (rev 5147)
+++ django/branches/boulder-oracle-sprint/django/conf/global_settings.py        
2007-05-03 19:26:47 UTC (rev 5148)
@@ -119,6 +119,7 @@
 # Optional SMTP authentication information for EMAIL_HOST.
 EMAIL_HOST_USER = ''
 EMAIL_HOST_PASSWORD = ''
+EMAIL_USE_TLS = False
 
 # List of strings representing installed apps.
 INSTALLED_APPS = ()

Modified: django/branches/boulder-oracle-sprint/django/core/mail.py
===================================================================
--- django/branches/boulder-oracle-sprint/django/core/mail.py   2007-05-03 
17:52:38 UTC (rev 5147)
+++ django/branches/boulder-oracle-sprint/django/core/mail.py   2007-05-03 
19:26:47 UTC (rev 5148)
@@ -1,14 +1,22 @@
-# Use this module for e-mailing.
+"""
+Tools for sending email.
+"""
 
 from django.conf import settings
 from email.MIMEText import MIMEText
 from email.Header import Header
 from email.Utils import formatdate
+from email import Charset
+import os
 import smtplib
 import socket
 import time
 import random
 
+# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
+# some spam filters.
+Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
+
 # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
 # seconds, which slows down the restart of the server.
 class CachedDnsName(object):
@@ -22,6 +30,28 @@
 
 DNS_NAME = CachedDnsName()
 
+# Copied from Python standard library and modified to used the cached hostname
+# for performance.
+def make_msgid(idstring=None):
+    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
+
+    <[EMAIL PROTECTED]>
+
+    Optional idstring if given is a string used to strengthen the
+    uniqueness of the message id.
+    """
+    timeval = time.time()
+    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
+    pid = os.getpid()
+    randint = random.randrange(100000)
+    if idstring is None:
+        idstring = ''
+    else:
+        idstring = '.' + idstring
+    idhost = DNS_NAME
+    msgid = '<[EMAIL PROTECTED]>' % (utcdate, pid, randint, idstring, idhost)
+    return msgid
+
 class BadHeaderError(ValueError):
     pass
 
@@ -34,6 +64,131 @@
             val = Header(val, settings.DEFAULT_CHARSET)
         MIMEText.__setitem__(self, name, val)
 
+class SMTPConnection(object):
+    """
+    A wrapper that manages the SMTP network connection.
+    """
+
+    def __init__(self, host=None, port=None, username=None, password=None,
+            use_tls=None, fail_silently=False):
+        self.host = host or settings.EMAIL_HOST
+        self.port = port or settings.EMAIL_PORT
+        self.username = username or settings.EMAIL_HOST_USER
+        self.password = password or settings.EMAIL_HOST_PASSWORD
+        self.use_tls = (use_tls is not None) and use_tls or 
settings.EMAIL_USE_TLS
+        self.fail_silently = fail_silently
+        self.connection = None
+
+    def open(self):
+        """
+        Ensure we have a connection to the email server. Returns whether or not
+        a new connection was required.
+        """
+        if self.connection:
+            # Nothing to do if the connection is already open.
+            return False
+        try:
+            self.connection = smtplib.SMTP(self.host, self.port)
+            if self.use_tls:
+                self.connection.ehlo()
+                self.connection.starttls()
+                self.connection.ehlo()
+            if self.username and self.password:
+                self.connection.login(self.username, self.password)
+            return True
+        except:
+            if not self.fail_silently:
+                raise
+
+    def close(self):
+        """Close the connection to the email server."""
+        try:
+            try:
+                self.connection.quit()
+            except socket.sslerror:
+                # This happens when calling quit() on a TLS connection
+                # sometimes.
+                self.connection.close()
+            except:
+                if self.fail_silently:
+                    return
+                raise
+        finally:
+            self.connection = None
+
+    def send_messages(self, email_messages):
+        """
+        Send one or more EmailMessage objects and return the number of email
+        messages sent.
+        """
+        if not email_messages:
+            return
+        new_conn_created = self.open()
+        if not self.connection:
+            # We failed silently on open(). Trying to send would be pointless.
+            return
+        num_sent = 0
+        for message in email_messages:
+            sent = self._send(message)
+            if sent:
+                num_sent += 1
+        if new_conn_created:
+            self.close()
+        return num_sent
+
+    def _send(self, email_message):
+        """A helper method that does the actual sending."""
+        if not email_message.to:
+            return False
+        try:
+            self.connection.sendmail(email_message.from_email,
+                    email_message.recipients(),
+                    email_message.message().as_string())
+        except:
+            if not self.fail_silently:
+                raise
+            return False
+        return True
+
+class EmailMessage(object):
+    """
+    A container for email information.
+    """
+    def __init__(self, subject='', body='', from_email=None, to=None, 
bcc=None, connection=None):
+        self.to = to or []
+        self.bcc = bcc or []
+        self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
+        self.subject = subject
+        self.body = body
+        self.connection = connection
+
+    def get_connection(self, fail_silently=False):
+        if not self.connection:
+            self.connection = SMTPConnection(fail_silently=fail_silently)
+        return self.connection
+
+    def message(self):
+        msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
+        msg['Subject'] = self.subject
+        msg['From'] = self.from_email
+        msg['To'] = ', '.join(self.to)
+        msg['Date'] = formatdate()
+        msg['Message-ID'] = make_msgid()
+        if self.bcc:
+            msg['Bcc'] = ', '.join(self.bcc)
+        return msg
+
+    def recipients(self):
+        """
+        Returns a list of all recipients of the email (includes direct
+        addressees as well as Bcc entries).
+        """
+        return self.to + self.bcc
+
+    def send(self, fail_silently=False):
+        """Send the email message."""
+        return self.get_connection(fail_silently).send_messages([self])
+
 def send_mail(subject, message, from_email, recipient_list, 
fail_silently=False, auth_user=None, auth_password=None):
     """
     Easy wrapper for sending a single message to a recipient list. All members
@@ -41,8 +196,13 @@
 
     If auth_user is None, the EMAIL_HOST_USER setting is used.
     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
+
+    NOTE: This method is deprecated. It exists for backwards compatibility.
+    New code should use the EmailMessage class directly.
     """
-    return send_mass_mail([[subject, message, from_email, recipient_list]], 
fail_silently, auth_user, auth_password)
+    connection = SMTPConnection(username=auth_user, password=auth_password,
+                                 fail_silently=fail_silently)
+    return EmailMessage(subject, message, from_email, recipient_list, 
connection=connection).send()
 
 def send_mass_mail(datatuple, fail_silently=False, auth_user=None, 
auth_password=None):
     """
@@ -53,52 +213,24 @@
     If auth_user and auth_password are set, they're used to log in.
     If auth_user is None, the EMAIL_HOST_USER setting is used.
     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
+
+    NOTE: This method is deprecated. It exists for backwards compatibility.
+    New code should use the EmailMessage class directly.
     """
-    if auth_user is None:
-        auth_user = settings.EMAIL_HOST_USER
-    if auth_password is None:
-        auth_password = settings.EMAIL_HOST_PASSWORD
-    try:
-        server = smtplib.SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT)
-        if auth_user and auth_password:
-            server.login(auth_user, auth_password)
-    except:
-        if fail_silently:
-            return
-        raise
-    num_sent = 0
-    for subject, message, from_email, recipient_list in datatuple:
-        if not recipient_list:
-            continue
-        from_email = from_email or settings.DEFAULT_FROM_EMAIL
-        msg = SafeMIMEText(message, 'plain', settings.DEFAULT_CHARSET)
-        msg['Subject'] = subject
-        msg['From'] = from_email
-        msg['To'] = ', '.join(recipient_list)
-        msg['Date'] = formatdate()
-        try:
-            random_bits = str(random.getrandbits(64))
-        except AttributeError: # Python 2.3 doesn't have random.getrandbits().
-            random_bits = ''.join([random.choice('1234567890') for i in 
range(19)])
-        msg['Message-ID'] = "<[EMAIL PROTECTED]>" % (time.time(), random_bits, 
DNS_NAME)
-        try:
-            server.sendmail(from_email, recipient_list, msg.as_string())
-            num_sent += 1
-        except:
-            if not fail_silently:
-                raise
-    try:
-        server.quit()
-    except:
-        if fail_silently:
-            return
-        raise
-    return num_sent
+    connection = SMTPConnection(username=auth_user, password=auth_password,
+                                 fail_silently=fail_silently)
+    messages = [EmailMessage(subject, message, sender, recipient) for subject, 
message, sender, recipient in datatuple]
+    return connection.send_messages(messages)
 
 def mail_admins(subject, message, fail_silently=False):
     "Sends a message to the admins, as defined by the ADMINS setting."
-    send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, 
settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], fail_silently)
+    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
+            settings.SERVER_EMAIL, [a[1] for a in
+                settings.ADMINS]).send(fail_silently=fail_silently)
 
 def mail_managers(subject, message, fail_silently=False):
     "Sends a message to the managers, as defined by the MANAGERS setting."
-    send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, 
settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], fail_silently)
+    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
+            settings.SERVER_EMAIL, [a[1] for a in
+                settings.MANAGERS]).send(fail_silently=fail_silently)
+

Modified: django/branches/boulder-oracle-sprint/docs/documentation.txt
===================================================================
--- django/branches/boulder-oracle-sprint/docs/documentation.txt        
2007-05-03 17:52:38 UTC (rev 5147)
+++ django/branches/boulder-oracle-sprint/docs/documentation.txt        
2007-05-03 19:26:47 UTC (rev 5148)
@@ -94,12 +94,10 @@
 
 The text documentation is written in ReST (ReStructured Text) format. That
 means it's easy to read but is also formatted in a way that makes it easy to
-convert into other formats, such as HTML. If you're interested, the script that
-converts the ReST text docs into djangoproject.com's HTML lives at
-`djangoproject.com/django_website/apps/docs/parts/build_documentation.py`_ in
-the Django Subversion repository.
+convert into other formats, such as HTML. If you have the `reStructuredText`_
+library installed, you can use ``rst2html`` to generate your own HTML files.
 
-.. _djangoproject.com/django_website/apps/docs/parts/build_documentation.py: 
http://code.djangoproject.com/browser/djangoproject.com/django_website/apps/docs/parts/build_documentation.py
+.. _reStructuredText: http://docutils.sourceforge.net/rst.html
 
 Differences between versions
 ============================

Modified: django/branches/boulder-oracle-sprint/docs/email.txt
===================================================================
--- django/branches/boulder-oracle-sprint/docs/email.txt        2007-05-03 
17:52:38 UTC (rev 5147)
+++ django/branches/boulder-oracle-sprint/docs/email.txt        2007-05-03 
19:26:47 UTC (rev 5148)
@@ -22,7 +22,8 @@
 
 Mail will be sent using the SMTP host and port specified in the `EMAIL_HOST`_
 and `EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_
-settings, if set, will be used to authenticate to the SMTP server.
+settings, if set, will be used to authenticate to the SMTP server and the
+`EMAIL_USE_TLS`_ settings will control whether a secure connection is used.
 
 .. note::
 
@@ -34,6 +35,7 @@
 .. _EMAIL_PORT: ../settings/#email-port
 .. _EMAIL_HOST_USER: ../settings/#email-host-user
 .. _EMAIL_HOST_PASSWORD: ../settings/#email-host-password
+.. _EMAIL_USE_TLS: ../settings/#email-use-tls
 
 
 send_mail()
@@ -183,3 +185,65 @@
             return HttpResponse('Make sure all fields are entered and valid.')
 
 .. _Header injection: 
http://securephp.damonkohler.com/index.php/Email_Injection
+
+The EmailMessage and SMTPConnection classes
+===========================================
+
+**New in Django development version**
+
+Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
+wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
+in ``django.mail``.  If you ever need to customize the way Django sends email,
+you can subclass these two classes to suit your needs.
+
+.. note::
+    Not all features of the ``EmailMessage`` class are available through the
+    ``send_mail()`` and related wrapper functions. If you wish to use advanced
+    features such as including BCC recipients or multi-part email, you will
+    need to create ``EmailMessage`` instances directly.
+
+In general, ``EmailMessage`` is responsible for creating the email message
+itself. ``SMTPConnection`` is responsible for the network connection side of
+the operation. This means you can reuse the same connection (an
+``SMTPConnection`` instance) for multiple messages.
+
+The ``EmailMessage`` class is initialised as follows::
+
+    email = EmailMessage(subject, body, from_email, to, bcc, connection)
+
+All of these parameters are optional. If ``from_email`` is omitted, the value
+from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc``
+parameters are lists of addresses.
+
+The class has the following methods that you can use:
+
+ * ``send()`` sends the message, using either the connection that is specified
+   in the ``connection`` attribute, or creating a new connection if none 
already
+   exists.
+ * ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
+   sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the
+   message to be sent. If you ever need to extend the `EmailMessage` class,
+   you will probably want to override this method to put the content you wish
+   into the MIME object.
+ * ``recipients()`` returns a lists of all the recipients of the message,
+   whether they are recorded in the ``to`` or ``bcc`` attributes. This is
+   another method you need to possibly override when sub-classing, since the
+   SMTP server needs to be told the full list of recipients when the message
+   is sent. If you add another way to specify recipients in your class, they
+   need to be returned from this method as well.
+
+The ``SMTPConnection`` class is initialized with the host, port, username and
+password for the SMTP server. If you don't specify one or more of those
+options, they are read from your settings file.
+
+If you are sending lots of messages at once, the ``send_messages()`` method of
+the ``SMTPConnection`` class will be useful. It takes a list of 
``EmailMessage``
+instances (or sub-classes) and sends them over a single connection. For
+example, if you have a function called ``get_notification_email()`` that 
returns a
+list of ``EmailMessage`` objects representing some periodic email you wish to
+send out, you could send this with::
+
+    connection = SMTPConnection()   # Use default settings for connection
+    messages = get_notification_email()
+    connection.send_messages(messages)
+

Modified: django/branches/boulder-oracle-sprint/docs/sessions.txt
===================================================================
--- django/branches/boulder-oracle-sprint/docs/sessions.txt     2007-05-03 
17:52:38 UTC (rev 5147)
+++ django/branches/boulder-oracle-sprint/docs/sessions.txt     2007-05-03 
19:26:47 UTC (rev 5148)
@@ -107,7 +107,7 @@
 This simplistic view logs in a "member" of the site::
 
     def login(request):
-        m = members.get_object(username__exact=request.POST['username'])
+        m = Member.objects.get(username=request.POST['username'])
         if m.password == request.POST['password']:
             request.session['member_id'] = m.id
             return HttpResponse("You're logged in.")

Modified: django/branches/boulder-oracle-sprint/docs/settings.txt
===================================================================
--- django/branches/boulder-oracle-sprint/docs/settings.txt     2007-05-03 
17:52:38 UTC (rev 5147)
+++ django/branches/boulder-oracle-sprint/docs/settings.txt     2007-05-03 
19:26:47 UTC (rev 5148)
@@ -428,6 +428,15 @@
 or ``django.core.mail.mail_managers``. You'll probably want to include the
 trailing space.
 
+EMAIL_USE_TLS
+-------------
+
+**New in Django development version**
+
+Default: ``False``
+
+Whether to use a TLS (secure) connection when talking to the SMTP server.
+
 FIXTURE_DIRS
 -------------
 

Modified: django/branches/boulder-oracle-sprint/docs/syndication_feeds.txt
===================================================================
--- django/branches/boulder-oracle-sprint/docs/syndication_feeds.txt    
2007-05-03 17:52:38 UTC (rev 5147)
+++ django/branches/boulder-oracle-sprint/docs/syndication_feeds.txt    
2007-05-03 19:26:47 UTC (rev 5148)
@@ -646,15 +646,15 @@
         def item_enclosure_mime_type(self, item):
             """
             Takes an item, as returned by items(), and returns the item's
-            enclosure mime type.
+            enclosure MIME type.
             """
 
         def item_enclosure_mime_type(self):
             """
-            Returns the enclosure length, in bytes, for every item in the feed.
+            Returns the enclosure MIME type for every item in the feed.
             """
 
-        item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure 
mime-type.
+        item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure MIME 
type.
 
         # ITEM PUBDATE -- It's optional to use one of these three. This is a
         # hook that specifies how to get the pubdate for a given item.

Modified: django/branches/boulder-oracle-sprint/docs/templates_python.txt
===================================================================
--- django/branches/boulder-oracle-sprint/docs/templates_python.txt     
2007-05-03 17:52:38 UTC (rev 5147)
+++ django/branches/boulder-oracle-sprint/docs/templates_python.txt     
2007-05-03 19:26:47 UTC (rev 5148)
@@ -345,7 +345,7 @@
       ``request.user.get_and_delete_messages()`` for every request. That method
       collects the user's messages and deletes them from the database.
 
-      Note that messages are set with ``user.add_message()``. See the
+      Note that messages are set with ``user.message_set.create``. See the
       `message docs`_ for more.
 
     * ``perms`` -- An instance of


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to