summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2009-11-03 12:53:26 +0000
committerRussell Keith-Magee <russell@keith-magee.com>2009-11-03 12:53:26 +0000
commitaba5389326372be43b2a3bdcda16646fd197e807 (patch)
treea296f56691d3cb6da4dcbc3a99e16a7eab9a7c43 /django
parent8287c27b1895ba56c6680295ff3d202fc7a4b64e (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.py6
-rw-r--r--django/core/mail/__init__.py110
-rw-r--r--django/core/mail/backends/__init__.py1
-rw-r--r--django/core/mail/backends/base.py39
-rw-r--r--django/core/mail/backends/console.py34
-rw-r--r--django/core/mail/backends/dummy.py9
-rw-r--r--django/core/mail/backends/filebased.py59
-rw-r--r--django/core/mail/backends/locmem.py24
-rw-r--r--django/core/mail/backends/smtp.py103
-rw-r--r--django/core/mail/message.py (renamed from django/core/mail.py)178
-rw-r--r--django/core/mail/utils.py19
-rw-r--r--django/test/utils.py30
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('.')