diff options
| author | Russell Keith-Magee <russell@keith-magee.com> | 2009-11-03 12:53:26 +0000 |
|---|---|---|
| committer | Russell Keith-Magee <russell@keith-magee.com> | 2009-11-03 12:53:26 +0000 |
| commit | aba5389326372be43b2a3bdcda16646fd197e807 (patch) | |
| tree | a296f56691d3cb6da4dcbc3a99e16a7eab9a7c43 /django | |
| parent | 8287c27b1895ba56c6680295ff3d202fc7a4b64e (diff) | |
Fixed #10355 -- Added an API for pluggable e-mail backends.
Thanks to Andi Albrecht for his work on this patch, and to everyone else that contributed during design and development.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@11709 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rw-r--r-- | django/conf/global_settings.py | 6 | ||||
| -rw-r--r-- | django/core/mail/__init__.py | 110 | ||||
| -rw-r--r-- | django/core/mail/backends/__init__.py | 1 | ||||
| -rw-r--r-- | django/core/mail/backends/base.py | 39 | ||||
| -rw-r--r-- | django/core/mail/backends/console.py | 34 | ||||
| -rw-r--r-- | django/core/mail/backends/dummy.py | 9 | ||||
| -rw-r--r-- | django/core/mail/backends/filebased.py | 59 | ||||
| -rw-r--r-- | django/core/mail/backends/locmem.py | 24 | ||||
| -rw-r--r-- | django/core/mail/backends/smtp.py | 103 | ||||
| -rw-r--r-- | django/core/mail/message.py (renamed from django/core/mail.py) | 178 | ||||
| -rw-r--r-- | django/core/mail/utils.py | 19 | ||||
| -rw-r--r-- | django/test/utils.py | 30 |
12 files changed, 426 insertions, 186 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index a0ce96a818..70d9c1e259 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -131,6 +131,12 @@ DATABASE_HOST = '' # Set to empty string for localhost. Not used wit 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. For possible shortcuts see django.core.mail. +# The default is to use the SMTP backend. +# Third-party backends can be specified by providing a Python path +# to a module that defines an EmailBackend class. +EMAIL_BACKEND = 'django.core.mail.backends.smtp' + # Host for sending e-mail. EMAIL_HOST = 'localhost' diff --git a/django/core/mail/__init__.py b/django/core/mail/__init__.py new file mode 100644 index 0000000000..b02575793d --- /dev/null +++ b/django/core/mail/__init__.py @@ -0,0 +1,110 @@ +""" +Tools for sending email. +""" + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.utils.importlib import import_module + +# Imported for backwards compatibility, and for the sake +# of a cleaner namespace. These symbols used to be in +# django/core/mail.py before the introduction of email +# backends and the subsequent reorganization (See #10355) +from django.core.mail.utils import CachedDnsName, DNS_NAME +from django.core.mail.message import \ + EmailMessage, EmailMultiAlternatives, \ + SafeMIMEText, SafeMIMEMultipart, \ + DEFAULT_ATTACHMENT_MIME_TYPE, make_msgid, \ + BadHeaderError, forbid_multi_line_headers +from django.core.mail.backends.smtp import EmailBackend as _SMTPConnection + +def get_connection(backend=None, fail_silently=False, **kwds): + """Load an e-mail backend and return an instance of it. + + If backend is None (default) settings.EMAIL_BACKEND is used. + + Both fail_silently and other keyword arguments are used in the + constructor of the backend. + """ + path = backend or settings.EMAIL_BACKEND + try: + mod = import_module(path) + except ImportError, e: + raise ImproperlyConfigured(('Error importing email backend %s: "%s"' + % (path, e))) + try: + cls = getattr(mod, 'EmailBackend') + except AttributeError: + raise ImproperlyConfigured(('Module "%s" does not define a ' + '"EmailBackend" class' % path)) + 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, + connection=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 = connection or 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, connection=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 = connection or 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, connection=None): + """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], + connection=connection).send(fail_silently=fail_silently) + + +def mail_managers(subject, message, fail_silently=False, connection=None): + """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], + connection=connection).send(fail_silently=fail_silently) + + +class SMTPConnection(_SMTPConnection): + def __init__(self, *args, **kwds): + import warnings + warnings.warn( + 'mail.SMTPConnection is deprecated; use mail.get_connection() instead.', + DeprecationWarning + ) + super(SMTPConnection, self).__init__(*args, **kwds) diff --git a/django/core/mail/backends/__init__.py b/django/core/mail/backends/__init__.py new file mode 100644 index 0000000000..5973b499b0 --- /dev/null +++ b/django/core/mail/backends/__init__.py @@ -0,0 +1 @@ +# Mail backends shipped with Django. diff --git a/django/core/mail/backends/base.py b/django/core/mail/backends/base.py new file mode 100644 index 0000000000..9a3092849d --- /dev/null +++ b/django/core/mail/backends/base.py @@ -0,0 +1,39 @@ +"""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, **kwargs): + 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. + + 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. + + 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 --git a/django/core/mail/backends/console.py b/django/core/mail/backends/console.py new file mode 100644 index 0000000000..705497520a --- /dev/null +++ b/django/core/mail/backends/console.py @@ -0,0 +1,34 @@ +""" +Email backend that writes messages to console instead of sending them. +""" +import sys +import threading + +from django.core.mail.backends.base import BaseEmailBackend + +class EmailBackend(BaseEmailBackend): + def __init__(self, *args, **kwargs): + self.stream = kwargs.pop('stream', sys.stdout) + self._lock = threading.RLock() + super(EmailBackend, self).__init__(*args, **kwargs) + + def send_messages(self, email_messages): + """Write all messages to the stream in a thread-safe way.""" + if not email_messages: + return + self._lock.acquire() + try: + stream_created = self.open() + for message in email_messages: + self.stream.write('%s\n' % message.message().as_string()) + self.stream.write('-'*79) + self.stream.write('\n') + self.stream.flush() # flush after each message + if stream_created: + self.close() + except: + if not self.fail_silently: + raise + finally: + self._lock.release() + return len(email_messages) diff --git a/django/core/mail/backends/dummy.py b/django/core/mail/backends/dummy.py new file mode 100644 index 0000000000..273aa0d88e --- /dev/null +++ b/django/core/mail/backends/dummy.py @@ -0,0 +1,9 @@ +""" +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 --git a/django/core/mail/backends/filebased.py b/django/core/mail/backends/filebased.py new file mode 100644 index 0000000000..3f6b99b057 --- /dev/null +++ b/django/core/mail/backends/filebased.py @@ -0,0 +1,59 @@ +"""Email backend that writes messages to a file.""" + +import datetime +import os + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.core.mail.backends.console import EmailBackend as ConsoleEmailBackend + +class EmailBackend(ConsoleEmailBackend): + def __init__(self, *args, **kwargs): + self._fname = None + if 'file_path' in kwargs: + self.file_path = kwargs.pop('file_path') + else: + self.file_path = getattr(settings, 'EMAIL_FILE_PATH',None) + # Make sure self.file_path is a string. + if not isinstance(self.file_path, basestring): + raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.file_path) + self.file_path = os.path.abspath(self.file_path) + # Make sure that self.file_path is an directory if it exists. + if os.path.exists(self.file_path) and not os.path.isdir(self.file_path): + raise ImproperlyConfigured('Path for saving email messages exists, but is not a directory: %s' % self.file_path) + # Try to create it, if it not exists. + elif not os.path.exists(self.file_path): + try: + os.makedirs(self.file_path) + except OSError, err: + raise ImproperlyConfigured('Could not create directory for saving email messages: %s (%s)' % (self.file_path, err)) + # Make sure that self.file_path is writable. + if not os.access(self.file_path, os.W_OK): + raise ImproperlyConfigured('Could not write to directory: %s' % self.file_path) + # Finally, call super(). + # Since we're using the console-based backend as a base, + # force the stream to be None, so we don't default to stdout + kwargs['stream'] = None + super(EmailBackend, self).__init__(*args, **kwargs) + + def _get_filename(self): + """Return a unique file name.""" + if self._fname is None: + timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + fname = "%s-%s.log" % (timestamp, abs(id(self))) + self._fname = os.path.join(self.file_path, fname) + return self._fname + + def open(self): + if self.stream is None: + self.stream = open(self._get_filename(), 'a') + return True + return False + + def close(self): + try: + if self.stream is not None: + self.stream.close() + finally: + self.stream = None + diff --git a/django/core/mail/backends/locmem.py b/django/core/mail/backends/locmem.py new file mode 100644 index 0000000000..642bfc49fb --- /dev/null +++ b/django/core/mail/backends/locmem.py @@ -0,0 +1,24 @@ +""" +Backend for test environment. +""" + +from django.core import mail +from django.core.mail.backends.base import BaseEmailBackend + +class EmailBackend(BaseEmailBackend): + """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 dummy outbox is accessible through the outbox instance attribute. + """ + def __init__(self, *args, **kwargs): + super(EmailBackend, self).__init__(*args, **kwargs) + if not hasattr(mail, 'outbox'): + mail.outbox = [] + + def send_messages(self, messages): + """Redirect messages to the dummy outbox""" + mail.outbox.extend(messages) + return len(messages) diff --git a/django/core/mail/backends/smtp.py b/django/core/mail/backends/smtp.py new file mode 100644 index 0000000000..12cb26c059 --- /dev/null +++ b/django/core/mail/backends/smtp.py @@ -0,0 +1,103 @@ +"""SMTP email backend class.""" + +import smtplib +import socket +import threading + +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, **kwargs): + 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 + self._lock = threading.RLock() + + 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 + self._lock.acquire() + try: + 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() + finally: + self._lock.release() + 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 --git a/django/core/mail.py b/django/core/mail/message.py index c305699158..7252a5a620 100644 --- a/django/core/mail.py +++ b/django/core/mail/message.py @@ -1,13 +1,7 @@ -""" -Tools for sending email. -""" - import mimetypes import os -import smtplib -import socket -import time import random +import time from email import Charset, Encoders from email.MIMEText import MIMEText from email.MIMEMultipart import MIMEMultipart @@ -16,6 +10,7 @@ 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 @@ -26,18 +21,10 @@ Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8') # 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 +class BadHeaderError(ValueError): + pass -DNS_NAME = CachedDnsName() # Copied from Python standard library, with the following modifications: # * Used cached hostname for performance. @@ -66,8 +53,6 @@ def make_msgid(idstring=None): 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.""" @@ -91,104 +76,18 @@ def forbid_multi_line_headers(name, val): 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): """ @@ -199,14 +98,14 @@ class EmailMessage(object): encoding = None # None => use settings default def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, - connection=None, attachments=None, headers=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. + 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' @@ -226,8 +125,9 @@ class EmailMessage(object): self.connection = connection def get_connection(self, fail_silently=False): + from django.core.mail import get_connection if not self.connection: - self.connection = SMTPConnection(fail_silently=fail_silently) + self.connection = get_connection(fail_silently=fail_silently) return self.connection def message(self): @@ -332,6 +232,7 @@ class EmailMessage(object): filename=filename) return attachment + class EmailMultiAlternatives(EmailMessage): """ A version of EmailMessage that makes it easy to send multipart/alternative @@ -371,56 +272,3 @@ class EmailMultiAlternatives(EmailMessage): 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 --git a/django/core/mail/utils.py b/django/core/mail/utils.py new file mode 100644 index 0000000000..322a3a1b79 --- /dev/null +++ b/django/core/mail/utils.py @@ -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 --git a/django/test/utils.py b/django/test/utils.py index d34dd33d15..a30bb7e41c 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -2,6 +2,7 @@ import sys, time, os from django.conf import settings from django.db import connection from django.core import mail +from django.core.mail.backends import locmem from django.test import signals from django.template import Template from django.utils.translation import deactivate @@ -28,37 +29,22 @@ def instrumented_test_render(self, context): 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 + - Set the email backend to the locmem email backend. - 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.SMTPConnection = locmem.EmailBackend + + settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem' + mail.original_email_backend = settings.EMAIL_BACKEND mail.outbox = [] @@ -77,8 +63,10 @@ def teardown_test_environment(): mail.SMTPConnection = mail.original_SMTPConnection del mail.original_SMTPConnection - del mail.outbox + settings.EMAIL_BACKEND = mail.original_email_backend + del mail.original_email_backend + del mail.outbox def get_runner(settings): test_path = settings.TEST_RUNNER.split('.') |
