Vergleiche mit /storage/devel/django/django-trunk
Suche nach Änderungen
Änderung:        7296:c00b6982431e
Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
Datum:           Mon Aug 24 06:34:37 2009 +0200
Zusammenfassung: Refactored django.core.email

diff -r 4d79b1a3175f -r c00b6982431e django/conf/global_settings.py
--- a/django/conf/global_settings.py	Thu Aug 20 16:05:25 2009 +0000
+++ b/django/conf/global_settings.py	Mon Aug 24 06:34:37 2009 +0200
@@ -131,6 +131,14 @@
 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
 DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
 
+# The email backend to use. Possible values are 'smtp' (default), 'dummy',
+# 'log', 'test' or any Python import path pointing to a BaseEmailBackend
+# implementation.
+EMAIL_BACKEND = 'smtp'
+
+# The email backend to use for testing.
+TEST_EMAIL_BACKEND = 'testenv'
+
 # Host for sending e-mail.
 EMAIL_HOST = 'localhost'
 
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail.py
--- a/django/core/mail.py	Thu Aug 20 16:05:25 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,426 +0,0 @@
-"""
-Tools for sending email.
-"""
-
-import mimetypes
-import os
-import smtplib
-import socket
-import time
-import random
-from email import Charset, Encoders
-from email.MIMEText import MIMEText
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEBase import MIMEBase
-from email.Header import Header
-from email.Utils import formatdate, parseaddr, formataddr
-
-from django.conf import settings
-from django.utils.encoding import smart_str, force_unicode
-
-# 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')
-
-# Default MIME type to use on attachments (if it is not explicitly given
-# and cannot be guessed).
-DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
-
-# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
-# seconds, which slows down the restart of the server.
-class CachedDnsName(object):
-    def __str__(self):
-        return self.get_fqdn()
-
-    def get_fqdn(self):
-        if not hasattr(self, '_fqdn'):
-            self._fqdn = socket.getfqdn()
-        return self._fqdn
-
-DNS_NAME = CachedDnsName()
-
-# Copied from Python standard library, with the following modifications:
-# * Used cached hostname for performance.
-# * Added try/except to support lack of getpid() in Jython (#5496).
-def make_msgid(idstring=None):
-    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
-
-    <20020201195627.33539.96671@nightshade.la.mastaler.com>
-
-    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))
-    try:
-        pid = os.getpid()
-    except AttributeError:
-        # No getpid() in Jython, for example.
-        pid = 1
-    randint = random.randrange(100000)
-    if idstring is None:
-        idstring = ''
-    else:
-        idstring = '.' + idstring
-    idhost = DNS_NAME
-    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
-    return msgid
-
-class BadHeaderError(ValueError):
-    pass
-
-def forbid_multi_line_headers(name, val):
-    """Forbids multi-line headers, to prevent header injection."""
-    val = force_unicode(val)
-    if '\n' in val or '\r' in val:
-        raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
-    try:
-        val = val.encode('ascii')
-    except UnicodeEncodeError:
-        if name.lower() in ('to', 'from', 'cc'):
-            result = []
-            for item in val.split(', '):
-                nm, addr = parseaddr(item)
-                nm = str(Header(nm, settings.DEFAULT_CHARSET))
-                result.append(formataddr((nm, str(addr))))
-            val = ', '.join(result)
-        else:
-            val = Header(val, settings.DEFAULT_CHARSET)
-    else:
-        if name.lower() == 'subject':
-            val = Header(val)
-    return name, val
-
-class SafeMIMEText(MIMEText):
-    def __setitem__(self, name, val):
-        name, val = forbid_multi_line_headers(name, val)
-        MIMEText.__setitem__(self, name, val)
-
-class SafeMIMEMultipart(MIMEMultipart):
-    def __setitem__(self, name, val):
-        name, val = forbid_multi_line_headers(name, val)
-        MIMEMultipart.__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):
-        """
-        Ensures we have a connection to the email server. Returns whether or
-        not a new connection was required (True or False).
-        """
-        if self.connection:
-            # Nothing to do if the connection is already open.
-            return False
-        try:
-            # If local_hostname is not specified, socket.getfqdn() gets used.
-            # For performance, we use the cached FQDN for local_hostname.
-            self.connection = smtplib.SMTP(self.host, self.port,
-                                           local_hostname=DNS_NAME.get_fqdn())
-            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):
-        """Closes 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):
-        """
-        Sends one or more EmailMessage objects and returns 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.recipients():
-            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.
-    """
-    content_subtype = 'plain'
-    mixed_subtype = 'mixed'
-    encoding = None     # None => use settings default
-
-    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
-            connection=None, attachments=None, headers=None):
-        """
-        Initialize a single email message (which can be sent to multiple
-        recipients).
-
-        All strings used to create the message can be unicode strings (or UTF-8
-        bytestrings). The SafeMIMEText class will handle any necessary encoding
-        conversions.
-        """
-        if to:
-            assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
-            self.to = list(to)
-        else:
-            self.to = []
-        if bcc:
-            assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
-            self.bcc = list(bcc)
-        else:
-            self.bcc = []
-        self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
-        self.subject = subject
-        self.body = body
-        self.attachments = attachments or []
-        self.extra_headers = headers or {}
-        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):
-        encoding = self.encoding or settings.DEFAULT_CHARSET
-        msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
-                           self.content_subtype, encoding)
-        msg = self._create_message(msg)
-        msg['Subject'] = self.subject
-        msg['From'] = self.extra_headers.pop('From', self.from_email)
-        msg['To'] = ', '.join(self.to)
-
-        # Email header names are case-insensitive (RFC 2045), so we have to
-        # accommodate that when doing comparisons.
-        header_names = [key.lower() for key in self.extra_headers]
-        if 'date' not in header_names:
-            msg['Date'] = formatdate()
-        if 'message-id' not in header_names:
-            msg['Message-ID'] = make_msgid()
-        for name, value in self.extra_headers.items():
-            msg[name] = value
-        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):
-        """Sends the email message."""
-        if not self.recipients():
-            # Don't bother creating the network connection if there's nobody to
-            # send to.
-            return 0
-        return self.get_connection(fail_silently).send_messages([self])
-
-    def attach(self, filename=None, content=None, mimetype=None):
-        """
-        Attaches a file with the given filename and content. The filename can
-        be omitted and the mimetype is guessed, if not provided.
-
-        If the first parameter is a MIMEBase subclass it is inserted directly
-        into the resulting message attachments.
-        """
-        if isinstance(filename, MIMEBase):
-            assert content == mimetype == None
-            self.attachments.append(filename)
-        else:
-            assert content is not None
-            self.attachments.append((filename, content, mimetype))
-
-    def attach_file(self, path, mimetype=None):
-        """Attaches a file from the filesystem."""
-        filename = os.path.basename(path)
-        content = open(path, 'rb').read()
-        self.attach(filename, content, mimetype)
-
-    def _create_message(self, msg):
-        return self._create_attachments(msg)
-
-    def _create_attachments(self, msg):
-        if self.attachments:
-            body_msg = msg
-            msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
-            if self.body:
-                msg.attach(body_msg)
-            for attachment in self.attachments:
-                if isinstance(attachment, MIMEBase):
-                    msg.attach(attachment)
-                else:
-                    msg.attach(self._create_attachment(*attachment))
-        return msg
-
-    def _create_mime_attachment(self, content, mimetype):
-        """
-        Converts the content, mimetype pair into a MIME attachment object.
-        """
-        basetype, subtype = mimetype.split('/', 1)
-        if basetype == 'text':
-            attachment = SafeMIMEText(smart_str(content,
-                settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
-        else:
-            # Encode non-text attachments with base64.
-            attachment = MIMEBase(basetype, subtype)
-            attachment.set_payload(content)
-            Encoders.encode_base64(attachment)
-        return attachment
-
-    def _create_attachment(self, filename, content, mimetype=None):
-        """
-        Converts the filename, content, mimetype triple into a MIME attachment
-        object.
-        """
-        if mimetype is None:
-            mimetype, _ = mimetypes.guess_type(filename)
-            if mimetype is None:
-                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
-        attachment = self._create_mime_attachment(content, mimetype)
-        if filename:
-            attachment.add_header('Content-Disposition', 'attachment',
-                                  filename=filename)
-        return attachment
-
-class EmailMultiAlternatives(EmailMessage):
-    """
-    A version of EmailMessage that makes it easy to send multipart/alternative
-    messages. For example, including text and HTML versions of the text is
-    made easier.
-    """
-    alternative_subtype = 'alternative'
-
-    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
-            connection=None, attachments=None, headers=None, alternatives=None):
-        """
-        Initialize a single email message (which can be sent to multiple
-        recipients).
-
-        All strings used to create the message can be unicode strings (or UTF-8
-        bytestrings). The SafeMIMEText class will handle any necessary encoding
-        conversions.
-        """
-        super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
-        self.alternatives=alternatives or []
-
-    def attach_alternative(self, content, mimetype):
-        """Attach an alternative content representation."""
-        assert content is not None
-        assert mimetype is not None
-        self.alternatives.append((content, mimetype))
-
-    def _create_message(self, msg):
-        return self._create_attachments(self._create_alternatives(msg))
-
-    def _create_alternatives(self, msg):
-        if self.alternatives:
-            body_msg = msg
-            msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
-            if self.body:
-                msg.attach(body_msg)
-            for alternative in self.alternatives:
-                msg.attach(self._create_mime_attachment(*alternative))
-        return msg
-
-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
-    of the recipient list will see the other recipients in the 'To' field.
-
-    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: The API for this method is frozen. New code wanting to extend the
-    functionality should use the EmailMessage class directly.
-    """
-    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):
-    """
-    Given a datatuple of (subject, message, from_email, recipient_list), sends
-    each message to each recipient list. Returns the number of e-mails sent.
-
-    If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
-    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: The API for this method is frozen. New code wanting to extend the
-    functionality should use the EmailMessage class directly.
-    """
-    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."""
-    if not settings.ADMINS:
-        return
-    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."""
-    if not settings.MANAGERS:
-        return
-    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
-                 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
-                 ).send(fail_silently=fail_silently)
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/__init__.py	Mon Aug 24 06:34:37 2009 +0200
@@ -0,0 +1,97 @@
+"""
+Tools for sending email.
+"""
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.mail.message import EmailMessage, EmailMultiAlternatives
+from django.utils.importlib import import_module
+
+# Imported for backwards compatibility
+from django.core.mail.backends.smtp import EmailBackend as SMTPConnection
+
+
+# Shortcuts for builtin email backends. If settings.EMAIL_BACKEND isn't in
+# this list it's treated as a Python import path.
+BACKENDS = ('smtp', 'dummy', 'log', 'testenv')
+
+
+def get_connection(fail_silently=False, **kwds):
+    """Load configured email backend and return an instance of it.
+
+    Both fail_silently and other keyword arguments are used in the
+    constructor of the backend.
+    """
+    path = settings.EMAIL_BACKEND
+    if path in BACKENDS:
+        path = 'django.core.mail.backends.%s.EmailBackend' % path
+    i = path.rfind('.')
+    module, attr = path[:i], path[i+1:]
+    try:
+        mod = import_module(module)
+    except ImportError, e:
+        raise ImproperlyConfigured(('Error importing email backend %s: "%s"'
+                                    % (module, e)))
+    try:
+        cls = getattr(mod, attr)
+    except AttributeError:
+        raise ImproperlyConfigured(('Module "%s" does not define a "%s" '
+                                    'email backend' % (module, attr)))
+    return cls(fail_silently=fail_silently, **kwds)
+
+
+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
+    of the recipient list will see the other recipients in the 'To' field.
+
+    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: The API for this method is frozen. New code wanting to extend the
+    functionality should use the EmailMessage class directly.
+    """
+    connection = get_connection(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):
+    """
+    Given a datatuple of (subject, message, from_email, recipient_list), sends
+    each message to each recipient list. Returns the number of e-mails sent.
+
+    If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
+    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: The API for this method is frozen. New code wanting to extend the
+    functionality should use the EmailMessage class directly.
+    """
+    connection = get_connection(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."""
+    if not settings.ADMINS:
+        return
+    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."""
+    if not settings.MANAGERS:
+        return
+    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
+                 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
+                 ).send(fail_silently=fail_silently)
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/base.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/base.py	Mon Aug 24 06:34:37 2009 +0200
@@ -0,0 +1,38 @@
+"""Base email backend class."""
+
+
+class BaseEmailBackend(object):
+    """
+    Base class for email backend implementations.
+
+    Subclasses must at least overwrite send_messages().
+    """
+
+    def __init__(self, fail_silently=False, **kwds):
+        self.fail_silently = fail_silently
+
+    def open(self):
+        """Open a network connection.
+
+        This method can be overwritten by backend implementations to
+        open a network connection. There's no guarantee that this method
+        is called before an attempt to send one or more emails is made.
+        But this method can be called by applications to force a single
+        network connection to be used when sending mails. See the
+        send_messages() method of the SMTP backend for a reference
+        implementation.
+
+        The default implementation does nothing.
+        """
+        pass
+
+    def close(self):
+        """Close a network connection."""
+        pass
+
+    def send_messages(self, email_messages):
+        """
+        Sends one or more EmailMessage objects and returns the number of email
+        messages sent.
+        """
+        raise NotImplementedError
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/dummy.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/dummy.py	Mon Aug 24 06:34:37 2009 +0200
@@ -0,0 +1,12 @@
+"""
+Dummy email backend that does nothing.
+"""
+
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+
+    def send_messages(self, email_messages):
+        return len(email_messages)
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/log.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/log.py	Mon Aug 24 06:34:37 2009 +0200
@@ -0,0 +1,15 @@
+"""
+Email backend that logs messages instead of sending them.
+"""
+
+import logging
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+
+    def send_messages(self, email_messages):
+        for message in email_messages:
+            logging.info('Sending email:\n%s' % message.message().as_string())
+        return len(email_messages)
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/smtp.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/smtp.py	Mon Aug 24 06:34:37 2009 +0200
@@ -0,0 +1,98 @@
+"""SMTP email backend class."""
+
+import smtplib
+import socket
+
+from django.conf import settings
+from django.core.mail.backends.base import BaseEmailBackend
+from django.core.mail.utils import DNS_NAME
+
+
+class EmailBackend(BaseEmailBackend):
+    """
+    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, **kwds):
+        super(EmailBackend, self).__init__(fail_silently=fail_silently)
+        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.connection = None
+
+    def open(self):
+        """
+        Ensures we have a connection to the email server. Returns whether or
+        not a new connection was required (True or False).
+        """
+        if self.connection:
+            # Nothing to do if the connection is already open.
+            return False
+        try:
+            # If local_hostname is not specified, socket.getfqdn() gets used.
+            # For performance, we use the cached FQDN for local_hostname.
+            self.connection = smtplib.SMTP(self.host, self.port,
+                                           local_hostname=DNS_NAME.get_fqdn())
+            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):
+        """Closes 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):
+        """
+        Sends one or more EmailMessage objects and returns 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.recipients():
+            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
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/backends/testenv.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/testenv.py	Mon Aug 24 06:34:37 2009 +0200
@@ -0,0 +1,19 @@
+"""
+Backend for test environment.
+"""
+
+from django.core import mail
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+    """A substitute SMTP connection for use during test sessions.
+    The test connection stores email messages in a dummy outbox,
+    rather than sending them out on the wire.
+
+    """
+
+    def send_messages(self, messages):
+        "Redirect messages to the dummy outbox"
+        mail.outbox.extend(messages)
+        return len(messages)
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/message.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/message.py	Mon Aug 24 06:34:37 2009 +0200
@@ -0,0 +1,274 @@
+import mimetypes
+import os
+import random
+import time
+from email import Charset, Encoders
+from email.MIMEText import MIMEText
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEBase import MIMEBase
+from email.Header import Header
+from email.Utils import formatdate, parseaddr, formataddr
+
+from django.conf import settings
+from django.core.mail.utils import DNS_NAME
+from django.utils.encoding import smart_str, force_unicode
+
+# 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')
+
+# Default MIME type to use on attachments (if it is not explicitly given
+# and cannot be guessed).
+DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
+
+
+class BadHeaderError(ValueError):
+    pass
+
+
+# Copied from Python standard library, with the following modifications:
+# * Used cached hostname for performance.
+# * Added try/except to support lack of getpid() in Jython (#5496).
+def make_msgid(idstring=None):
+    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
+
+    <20020201195627.33539.96671@nightshade.la.mastaler.com>
+
+    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))
+    try:
+        pid = os.getpid()
+    except AttributeError:
+        # No getpid() in Jython, for example.
+        pid = 1
+    randint = random.randrange(100000)
+    if idstring is None:
+        idstring = ''
+    else:
+        idstring = '.' + idstring
+    idhost = DNS_NAME
+    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
+    return msgid
+
+
+def forbid_multi_line_headers(name, val):
+    """Forbids multi-line headers, to prevent header injection."""
+    val = force_unicode(val)
+    if '\n' in val or '\r' in val:
+        raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
+    try:
+        val = val.encode('ascii')
+    except UnicodeEncodeError:
+        if name.lower() in ('to', 'from', 'cc'):
+            result = []
+            for item in val.split(', '):
+                nm, addr = parseaddr(item)
+                nm = str(Header(nm, settings.DEFAULT_CHARSET))
+                result.append(formataddr((nm, str(addr))))
+            val = ', '.join(result)
+        else:
+            val = Header(val, settings.DEFAULT_CHARSET)
+    else:
+        if name.lower() == 'subject':
+            val = Header(val)
+    return name, val
+
+
+class SafeMIMEText(MIMEText):
+    def __setitem__(self, name, val):
+        name, val = forbid_multi_line_headers(name, val)
+        MIMEText.__setitem__(self, name, val)
+
+
+class SafeMIMEMultipart(MIMEMultipart):
+    def __setitem__(self, name, val):
+        name, val = forbid_multi_line_headers(name, val)
+        MIMEMultipart.__setitem__(self, name, val)
+
+
+class EmailMessage(object):
+    """
+    A container for email information.
+    """
+    content_subtype = 'plain'
+    mixed_subtype = 'mixed'
+    encoding = None     # None => use settings default
+
+    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
+                 connection=None, attachments=None, headers=None):
+        """
+        Initialize a single email message (which can be sent to multiple
+        recipients).
+
+        All strings used to create the message can be unicode strings
+        (or UTF-8 bytestrings). The SafeMIMEText class will handle any
+        necessary encoding conversions.
+        """
+        if to:
+            assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
+            self.to = list(to)
+        else:
+            self.to = []
+        if bcc:
+            assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
+            self.bcc = list(bcc)
+        else:
+            self.bcc = []
+        self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
+        self.subject = subject
+        self.body = body
+        self.attachments = attachments or []
+        self.extra_headers = headers or {}
+        self.connection = connection
+
+    def get_connection(self, fail_silently=False):
+        from django.core.mail import get_connection
+        if not self.connection:
+            self.connection = get_connection(fail_silently=fail_silently)
+        return self.connection
+
+    def message(self):
+        encoding = self.encoding or settings.DEFAULT_CHARSET
+        msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
+                           self.content_subtype, encoding)
+        msg = self._create_message(msg)
+        msg['Subject'] = self.subject
+        msg['From'] = self.extra_headers.pop('From', self.from_email)
+        msg['To'] = ', '.join(self.to)
+
+        # Email header names are case-insensitive (RFC 2045), so we have to
+        # accommodate that when doing comparisons.
+        header_names = [key.lower() for key in self.extra_headers]
+        if 'date' not in header_names:
+            msg['Date'] = formatdate()
+        if 'message-id' not in header_names:
+            msg['Message-ID'] = make_msgid()
+        for name, value in self.extra_headers.items():
+            msg[name] = value
+        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):
+        """Sends the email message."""
+        if not self.recipients():
+            # Don't bother creating the network connection if there's nobody to
+            # send to.
+            return 0
+        return self.get_connection(fail_silently).send_messages([self])
+
+    def attach(self, filename=None, content=None, mimetype=None):
+        """
+        Attaches a file with the given filename and content. The filename can
+        be omitted and the mimetype is guessed, if not provided.
+
+        If the first parameter is a MIMEBase subclass it is inserted directly
+        into the resulting message attachments.
+        """
+        if isinstance(filename, MIMEBase):
+            assert content == mimetype == None
+            self.attachments.append(filename)
+        else:
+            assert content is not None
+            self.attachments.append((filename, content, mimetype))
+
+    def attach_file(self, path, mimetype=None):
+        """Attaches a file from the filesystem."""
+        filename = os.path.basename(path)
+        content = open(path, 'rb').read()
+        self.attach(filename, content, mimetype)
+
+    def _create_message(self, msg):
+        return self._create_attachments(msg)
+
+    def _create_attachments(self, msg):
+        if self.attachments:
+            body_msg = msg
+            msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
+            if self.body:
+                msg.attach(body_msg)
+            for attachment in self.attachments:
+                if isinstance(attachment, MIMEBase):
+                    msg.attach(attachment)
+                else:
+                    msg.attach(self._create_attachment(*attachment))
+        return msg
+
+    def _create_mime_attachment(self, content, mimetype):
+        """
+        Converts the content, mimetype pair into a MIME attachment object.
+        """
+        basetype, subtype = mimetype.split('/', 1)
+        if basetype == 'text':
+            attachment = SafeMIMEText(smart_str(content,
+                settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
+        else:
+            # Encode non-text attachments with base64.
+            attachment = MIMEBase(basetype, subtype)
+            attachment.set_payload(content)
+            Encoders.encode_base64(attachment)
+        return attachment
+
+    def _create_attachment(self, filename, content, mimetype=None):
+        """
+        Converts the filename, content, mimetype triple into a MIME attachment
+        object.
+        """
+        if mimetype is None:
+            mimetype, _ = mimetypes.guess_type(filename)
+            if mimetype is None:
+                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
+        attachment = self._create_mime_attachment(content, mimetype)
+        if filename:
+            attachment.add_header('Content-Disposition', 'attachment',
+                                  filename=filename)
+        return attachment
+
+
+class EmailMultiAlternatives(EmailMessage):
+    """
+    A version of EmailMessage that makes it easy to send multipart/alternative
+    messages. For example, including text and HTML versions of the text is
+    made easier.
+    """
+    alternative_subtype = 'alternative'
+
+    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
+            connection=None, attachments=None, headers=None, alternatives=None):
+        """
+        Initialize a single email message (which can be sent to multiple
+        recipients).
+
+        All strings used to create the message can be unicode strings (or UTF-8
+        bytestrings). The SafeMIMEText class will handle any necessary encoding
+        conversions.
+        """
+        super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
+        self.alternatives=alternatives or []
+
+    def attach_alternative(self, content, mimetype):
+        """Attach an alternative content representation."""
+        assert content is not None
+        assert mimetype is not None
+        self.alternatives.append((content, mimetype))
+
+    def _create_message(self, msg):
+        return self._create_attachments(self._create_alternatives(msg))
+
+    def _create_alternatives(self, msg):
+        if self.alternatives:
+            body_msg = msg
+            msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
+            if self.body:
+                msg.attach(body_msg)
+            for alternative in self.alternatives:
+                msg.attach(self._create_mime_attachment(*alternative))
+        return msg
diff -r 4d79b1a3175f -r c00b6982431e django/core/mail/utils.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/utils.py	Mon Aug 24 06:34:37 2009 +0200
@@ -0,0 +1,19 @@
+"""
+Email message and email sending related helper functions.
+"""
+
+import socket
+
+
+# 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):
+    def __str__(self):
+        return self.get_fqdn()
+
+    def get_fqdn(self):
+        if not hasattr(self, '_fqdn'):
+            self._fqdn = socket.getfqdn()
+        return self._fqdn
+
+DNS_NAME = CachedDnsName()
diff -r 4d79b1a3175f -r c00b6982431e django/test/utils.py
--- a/django/test/utils.py	Thu Aug 20 16:05:25 2009 +0000
+++ b/django/test/utils.py	Mon Aug 24 06:34:37 2009 +0200
@@ -28,37 +28,19 @@
     signals.template_rendered.send(sender=self, template=self, context=context)
     return self.nodelist.render(context)
 
-class TestSMTPConnection(object):
-    """A substitute SMTP connection for use during test sessions.
-    The test connection stores email messages in a dummy outbox,
-    rather than sending them out on the wire.
-
-    """
-    def __init__(*args, **kwargs):
-        pass
-    def open(self):
-        "Mock the SMTPConnection open() interface"
-        pass
-    def close(self):
-        "Mock the SMTPConnection close() interface"
-        pass
-    def send_messages(self, messages):
-        "Redirect messages to the dummy outbox"
-        mail.outbox.extend(messages)
-        return len(messages)
 
 def setup_test_environment():
     """Perform any global pre-test setup. This involves:
 
         - Installing the instrumented test renderer
-        - Diverting the email sending functions to a test buffer
+        - Replacing the email backend with the email backend used for testing.
         - Setting the active locale to match the LANGUAGE_CODE setting.
     """
     Template.original_render = Template.render
     Template.render = instrumented_test_render
 
-    mail.original_SMTPConnection = mail.SMTPConnection
-    mail.SMTPConnection = TestSMTPConnection
+    mail.original_email_backend = settings.EMAIL_BACKEND
+    settings.EMAIL_BACKEND = settings.TEST_EMAIL_BACKEND
 
     mail.outbox = []
 
@@ -74,8 +56,8 @@
     Template.render = Template.original_render
     del Template.original_render
 
-    mail.SMTPConnection = mail.original_SMTPConnection
-    del mail.original_SMTPConnection
+    settings.EMAIL_BACKEND = mail.original_email_backend
+    del mail.original_email_backend
 
     del mail.outbox
 
diff -r 4d79b1a3175f -r c00b6982431e tests/modeltests/test_client/views.py
--- a/tests/modeltests/test_client/views.py	Thu Aug 20 16:05:25 2009 +0000
+++ b/tests/modeltests/test_client/views.py	Mon Aug 24 06:34:37 2009 +0200
@@ -1,5 +1,6 @@
 from xml.dom.minidom import parseString
 
+from django.core import mail
 from django.core.mail import EmailMessage, SMTPConnection
 from django.template import Context, Template
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
@@ -201,7 +202,9 @@
         'from@example.com',
         ['second@example.com', 'third@example.com'])
 
-    c = SMTPConnection()
+    c = mail.get_connection()
+    c.open()
     c.send_messages([m1,m2])
+    c.close()
 
     return HttpResponse("Mail sent")
diff -r 4d79b1a3175f -r c00b6982431e tests/regressiontests/mail/tests.py
--- a/tests/regressiontests/mail/tests.py	Thu Aug 20 16:05:25 2009 +0000
+++ b/tests/regressiontests/mail/tests.py	Mon Aug 24 06:34:37 2009 +0200
@@ -5,6 +5,7 @@
 >>> from django.conf import settings
 >>> from django.core import mail
 >>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
+>>> from django.core.mail.backends.base import BaseEmailBackend
 >>> from django.utils.translation import ugettext_lazy
 
 # Test normal ascii character case:
@@ -138,4 +139,20 @@
 JVBERi0xLjQuJS4uLg==
 ...
 
+# Make sure all builtin email backends load correctly.
+>>> failed = []
+>>> for name in mail.BACKENDS:
+...   settings.EMAIL_BACKEND = name
+...   conn = mail.get_connection(foo='bar')
+...   if not isinstance(conn, BaseEmailBackend):
+...     failed.append(name)
+>>> failed
+[]
+>>> settings.EMAIL_BACKEND = 'testenv' # reset test backend
+
+# Make sure that get_connection() accepts arbitrary keyword that might be
+# used with custom backends.
+>>> c = mail.get_connection(fail_silently=True, foo='bar')
+>>> c.fail_silently
+True
 """

Änderung:        7297:54962e5dce18
Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
Datum:           Mon Aug 24 06:46:46 2009 +0200
Zusammenfassung: Document email backend settings.

diff -r c00b6982431e -r 54962e5dce18 docs/ref/settings.txt
--- a/docs/ref/settings.txt	Mon Aug 24 06:34:37 2009 +0200
+++ b/docs/ref/settings.txt	Mon Aug 24 06:46:46 2009 +0200
@@ -379,6 +379,18 @@
 This is only used if ``CommonMiddleware`` is installed (see
 :ref:`topics-http-middleware`).
 
+.. setting:: EMAIL_BACKEND
+
+EMAIL_BACKEND
+-------------
+
+.. versionadded:: 1.2
+
+Default: ``'smtp'``
+
+The backend to use for sending emails. For available backend see
+:ref:`topics-email`.
+
 .. setting:: EMAIL_HOST
 
 EMAIL_HOST
@@ -1098,6 +1110,19 @@
 
 See :ref:`topics-testing`.
 
+.. setting:: TEST_EMAIL_BACKEND
+
+TEST_EMAIL_BACKEND
+------------------
+
+.. versionadded:: 1.2
+
+Default: ``'testenv'``
+
+The email backend to use when running the test suite.
+
+See :ref:`topics-email`.
+
 .. setting:: TEST_RUNNER
 
 TEST_RUNNER

Änderung:        7298:fab52dcbb802
Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
Datum:           Mon Aug 24 09:53:09 2009 +0200
Zusammenfassung: Change log message.

diff -r 54962e5dce18 -r fab52dcbb802 django/core/mail/backends/log.py
--- a/django/core/mail/backends/log.py	Mon Aug 24 06:46:46 2009 +0200
+++ b/django/core/mail/backends/log.py	Mon Aug 24 09:53:09 2009 +0200
@@ -11,5 +11,6 @@
 
     def send_messages(self, email_messages):
         for message in email_messages:
-            logging.info('Sending email:\n%s' % message.message().as_string())
+            logging.info(message.message().as_string())
+            logging.info('This email is just logged. No real email was sent.')
         return len(email_messages)

Änderung:        7299:864d5ad60910
Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
Datum:           Mon Aug 24 13:39:11 2009 +0200
Zusammenfassung: Docs and cleanup.

diff -r fab52dcbb802 -r 864d5ad60910 django/conf/global_settings.py
--- a/django/conf/global_settings.py	Mon Aug 24 09:53:09 2009 +0200
+++ b/django/conf/global_settings.py	Mon Aug 24 13:39:11 2009 +0200
@@ -137,7 +137,7 @@
 EMAIL_BACKEND = 'smtp'
 
 # The email backend to use for testing.
-TEST_EMAIL_BACKEND = 'testenv'
+TEST_EMAIL_BACKEND = 'test'
 
 # Host for sending e-mail.
 EMAIL_HOST = 'localhost'
diff -r fab52dcbb802 -r 864d5ad60910 django/core/mail/__init__.py
--- a/django/core/mail/__init__.py	Mon Aug 24 09:53:09 2009 +0200
+++ b/django/core/mail/__init__.py	Mon Aug 24 13:39:11 2009 +0200
@@ -13,7 +13,7 @@
 
 # Shortcuts for builtin email backends. If settings.EMAIL_BACKEND isn't in
 # this list it's treated as a Python import path.
-BACKENDS = ('smtp', 'dummy', 'log', 'testenv')
+BACKENDS = ('smtp', 'dummy', 'log', 'test')
 
 
 def get_connection(fail_silently=False, **kwds):
diff -r fab52dcbb802 -r 864d5ad60910 django/core/mail/backends/test.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/test.py	Mon Aug 24 13:39:11 2009 +0200
@@ -0,0 +1,19 @@
+"""
+Backend for test environment.
+"""
+
+from django.core import mail
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+    """A substitute SMTP connection for use during test sessions.
+    The test connection stores email messages in a dummy outbox,
+    rather than sending them out on the wire.
+
+    """
+
+    def send_messages(self, messages):
+        "Redirect messages to the dummy outbox"
+        mail.outbox.extend(messages)
+        return len(messages)
diff -r fab52dcbb802 -r 864d5ad60910 django/core/mail/backends/testenv.py
--- a/django/core/mail/backends/testenv.py	Mon Aug 24 09:53:09 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-"""
-Backend for test environment.
-"""
-
-from django.core import mail
-from django.core.mail.backends.base import BaseEmailBackend
-
-
-class EmailBackend(BaseEmailBackend):
-    """A substitute SMTP connection for use during test sessions.
-    The test connection stores email messages in a dummy outbox,
-    rather than sending them out on the wire.
-
-    """
-
-    def send_messages(self, messages):
-        "Redirect messages to the dummy outbox"
-        mail.outbox.extend(messages)
-        return len(messages)
diff -r fab52dcbb802 -r 864d5ad60910 docs/ref/settings.txt
--- a/docs/ref/settings.txt	Mon Aug 24 09:53:09 2009 +0200
+++ b/docs/ref/settings.txt	Mon Aug 24 13:39:11 2009 +0200
@@ -1117,7 +1117,7 @@
 
 .. versionadded:: 1.2
 
-Default: ``'testenv'``
+Default: ``'test'``
 
 The email backend to use when running the test suite.
 
diff -r fab52dcbb802 -r 864d5ad60910 docs/topics/email.txt
--- a/docs/topics/email.txt	Mon Aug 24 09:53:09 2009 +0200
+++ b/docs/topics/email.txt	Mon Aug 24 13:39:11 2009 +0200
@@ -178,32 +178,31 @@
 
 .. _emailmessage-and-smtpconnection:
 
-The EmailMessage and SMTPConnection classes
-===========================================
+The EmailMessage class
+======================
 
 .. versionadded:: 1.0
 
 Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
-wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
-in ``django.core.mail``.  If you ever need to customize the way Django sends
-e-mail, you can subclass these two classes to suit your needs.
+wrappers that make use of the ``EmailMessage`` class in ``django.core.mail``.
+
+Not all features of the ``EmailMessage`` class are available through the
+``send_mail()`` and related wrapper functions. If you wish to use advanced
+features, such as BCC'ed recipients, file attachments, or multi-part
+e-mail, you'll need to create ``EmailMessage`` instances directly.
 
 .. note::
-    Not all features of the ``EmailMessage`` class are available through the
-    ``send_mail()`` and related wrapper functions. If you wish to use advanced
-    features, such as BCC'ed recipients, file attachments, or multi-part
-    e-mail, you'll need to create ``EmailMessage`` instances directly.
 
-    This is a design feature. ``send_mail()`` and related functions were
-    originally the only interface Django provided. However, the list of
-    parameters they accepted was slowly growing over time. It made sense to
-    move to a more object-oriented design for e-mail messages and retain the
-    original functions only for backwards compatibility.
+    This is a design feature. ``send_mail()`` and related functions
+    were originally the only interface Django provided. However, the
+    list of parameters they accepted was slowly growing over time. It
+    made sense to move to a more object-oriented design for e-mail
+    messages and retain the original functions only for backwards
+    compatibility.
 
 In general, ``EmailMessage`` is responsible for creating the e-mail message
-itself. ``SMTPConnection`` is responsible for the network connection side of
-the operation. This means you can reuse the same connection (an
-``SMTPConnection`` instance) for multiple messages.
+itself. The email backend is responsible for sending the email. By default
+the SMTP backend is used to send emails using a SMTP server.
 
 EmailMessage Objects
 --------------------
@@ -227,7 +226,7 @@
     * ``bcc``: A list or tuple of addresses used in the "Bcc" header when
       sending the e-mail.
 
-    * ``connection``: An ``SMTPConnection`` instance. Use this parameter if
+    * ``connection``: An email backend instance. Use this parameter if
       you want to use the same connection for multiple messages. If omitted, a
       new connection is created when ``send()`` is called.
 
@@ -331,11 +330,41 @@
     msg.content_subtype = "html"  # Main content is now text/html
     msg.send()
 
-SMTPConnection Objects
-----------------------
+
+Email Backends
+==============
+
+.. versionadded:: 1.2
+
+The actual sending of an email is handled by the email backend.
+By default the SMTP backend will be used.
+
+Your email backend preferences goes in the ``EMAIL_BACKEND`` setting in
+your settings file. Here's an explanation of all available values for
+``EMAIL_BACKEND``.
+
+SMTP backend
+------------
+
+This is the default backend. Email will be sent through a SMTP server.
+The server address and authentication credentials are set in the
+:setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`,
+:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
+settings file.
+
+The SMTP is configured in the default settings::
+
+    EMAIL_BACKEND = 'smtp'
+
+
+SMTPConnection objects
+~~~~~~~~~~~~~~~~~~~~~~
 
 .. class:: SMTPConnection
 
+For backwards compatibility the SMTP backend is available in
+``django.core.mail`` as the ``SMTPConnection`` class.
+
 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.
@@ -351,8 +380,70 @@
     messages = get_notification_email()
     connection.send_messages(messages)
 
-Testing e-mail sending
-----------------------
+If you want to resuse the same connection for multiple messages, but can't
+use ``send_messages()``, you'll have to use the ``open()`` and ``close()``
+methods of the email backend::
+
+    from django.core import mail
+
+    connection = mail.get_connection()
+    connection.open()
+    email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
+                  	       ['to1@example.com'], connection=connection)
+    email1.send()
+    email2 = mail.EmailMessage('Hello again', 'Body goes here',
+                               'from@example.com', ['to1@example.com'],
+                               connection=connection)
+    email2.send()
+    connection.close()
+
+
+Logging backend (for development)
+---------------------------------
+
+Instead of sending out real emails the logging backend just logs the emails
+that would be send out to the standard output. To use this backend, use
+in your settings::
+
+    EMAIL_BACKEND = 'log'
+
+Dummy backend (for development)
+-------------------------------
+
+As the name suggests the dummy backend does nothing with your emails.
+To enable this backend, use in your settings file::
+
+   EMAIL_BACKEND = 'dummy'
+
+This backend can be useful during development, if you even want to suppress
+the log entries written by the logging backend.
+
+Using a custom email backend
+----------------------------
+
+If you need to change how emails are send you can write your own email
+backend. The ``EMAIL_BACKEND`` setting in your settings file is then the
+Python import path pointing to your custom backend class.
+
+Custom email backends should subclass ``BaseEmailBackend`` that is located
+in the ``django.core.mail.backends.base`` module. A custom email
+backend must implement the ``send_messages(email_messages)`` method. This
+method receives a list of ``EMailMessage`` instances and returns the number
+of successfully delivered messages. If your backend should be capable of
+network connection handling, you should implement both the ``open()``
+and ``close()`` methods too. Refer to ``SMTPEmailBackend`` for a reference
+implementation of those methods.
+
+.. note::
+
+   If you write test suites for your email backend you should use the
+   Python import path pointing to your email backend both for the
+   ``EMAIL_BACKEND`` and ``TEST_EMAIL_BACKEND`` in your settings file.
+   This will change the default email backend when running test suites.
+
+
+Testing Email Sending
+=====================
 
 The are times when you do not want Django to send e-mails at all. For example,
 while developing a website, you probably don't want to send out thousands of
@@ -360,10 +451,14 @@
 people under the right conditions, and that those e-mails will contain the
 correct content.
 
-The easiest way to test your project's use of e-mail is to use a "dumb" e-mail
-server that receives the e-mails locally and displays them to the terminal,
-but does not actually send anything. Python has a built-in way to accomplish
-this with a single command::
+The easiest way to test your project's use of e-mail is to change the
+``EMAIL_BACKEND`` setting in your settings file to ``'log'`` to use the
+logging backend that doesn't send any email but writes them to the
+terminal.
+
+Alternatively use a "dumb" e-mail server that receives the e-mails locally
+and displays them to the terminal, but does not actually send anything.
+Python has a built-in way to accomplish this with a single command::
 
     python -m smtpd -n -c DebuggingServer localhost:1025
 
diff -r fab52dcbb802 -r 864d5ad60910 tests/regressiontests/mail/tests.py
--- a/tests/regressiontests/mail/tests.py	Mon Aug 24 09:53:09 2009 +0200
+++ b/tests/regressiontests/mail/tests.py	Mon Aug 24 13:39:11 2009 +0200
@@ -142,13 +142,13 @@
 # Make sure all builtin email backends load correctly.
 >>> failed = []
 >>> for name in mail.BACKENDS:
-...   settings.EMAIL_BACKEND = name
+...   settings.TEST_EMAIL_BACKEND = name
 ...   conn = mail.get_connection(foo='bar')
 ...   if not isinstance(conn, BaseEmailBackend):
 ...     failed.append(name)
 >>> failed
 []
->>> settings.EMAIL_BACKEND = 'testenv' # reset test backend
+>>> settings.TEST_EMAIL_BACKEND = 'test' # reset test backend
 
 # Make sure that get_connection() accepts arbitrary keyword that might be
 # used with custom backends.

Änderung:        7300:f480711a4050
Marke:           tip
Nutzer:          Andi Albrecht <albrecht.andi@gmail.com>
Datum:           Mon Aug 24 14:39:16 2009 +0200
Zusammenfassung: Minor fixes after personal review.

diff -r 864d5ad60910 -r f480711a4050 django/core/mail/backends/base.py
--- a/django/core/mail/backends/base.py	Mon Aug 24 13:39:11 2009 +0200
+++ b/django/core/mail/backends/base.py	Mon Aug 24 14:39:16 2009 +0200
@@ -15,9 +15,12 @@
         """Open a network connection.
 
         This method can be overwritten by backend implementations to
-        open a network connection. There's no guarantee that this method
-        is called before an attempt to send one or more emails is made.
-        But this method can be called by applications to force a single
+        open a network connection.
+
+        It's up to the backend implementation to track the status of
+        a network connection if it's needed by the backend.
+
+        This method can be called by applications to force a single
         network connection to be used when sending mails. See the
         send_messages() method of the SMTP backend for a reference
         implementation.
diff -r 864d5ad60910 -r f480711a4050 django/core/mail/backends/test.py
--- a/django/core/mail/backends/test.py	Mon Aug 24 13:39:11 2009 +0200
+++ b/django/core/mail/backends/test.py	Mon Aug 24 14:39:16 2009 +0200
@@ -7,10 +7,15 @@
 
 
 class EmailBackend(BaseEmailBackend):
-    """A substitute SMTP connection for use during test sessions.
+    """A email backend for use during test sessions.
+
     The test connection stores email messages in a dummy outbox,
     rather than sending them out on the wire.
 
+    The backend requires the 'django.core.mail' module to provide
+    a 'outbox' attribute (a list). This attribute is set during the setup
+    of the test environment and removed when the test environment is
+    teared down.
     """
 
     def send_messages(self, messages):
diff -r 864d5ad60910 -r f480711a4050 tests/modeltests/test_client/views.py
--- a/tests/modeltests/test_client/views.py	Mon Aug 24 13:39:11 2009 +0200
+++ b/tests/modeltests/test_client/views.py	Mon Aug 24 14:39:16 2009 +0200
@@ -203,8 +203,6 @@
         ['second@example.com', 'third@example.com'])
 
     c = mail.get_connection()
-    c.open()
     c.send_messages([m1,m2])
-    c.close()
 
     return HttpResponse("Mail sent")
diff -r 864d5ad60910 -r f480711a4050 tests/regressiontests/mail/tests.py
--- a/tests/regressiontests/mail/tests.py	Mon Aug 24 13:39:11 2009 +0200
+++ b/tests/regressiontests/mail/tests.py	Mon Aug 24 14:39:16 2009 +0200
@@ -142,13 +142,13 @@
 # Make sure all builtin email backends load correctly.
 >>> failed = []
 >>> for name in mail.BACKENDS:
-...   settings.TEST_EMAIL_BACKEND = name
+...   settings.EMAIL_BACKEND = name
 ...   conn = mail.get_connection(foo='bar')
 ...   if not isinstance(conn, BaseEmailBackend):
 ...     failed.append(name)
 >>> failed
 []
->>> settings.TEST_EMAIL_BACKEND = 'test' # reset test backend
+>>> settings.EMAIL_BACKEND = 'test' # reset test backend
 
 # Make sure that get_connection() accepts arbitrary keyword that might be
 # used with custom backends.

