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
This commit is contained in:
parent
8287c27b18
commit
aba5389326
1
AUTHORS
1
AUTHORS
|
@ -27,6 +27,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
|
|
||||||
ajs <adi@sieker.info>
|
ajs <adi@sieker.info>
|
||||||
alang@bright-green.com
|
alang@bright-green.com
|
||||||
|
Andi Albrecht <albrecht.andi@gmail.com>
|
||||||
Marty Alchin <gulopine@gamemusic.org>
|
Marty Alchin <gulopine@gamemusic.org>
|
||||||
Ahmad Alhashemi <trans@ahmadh.com>
|
Ahmad Alhashemi <trans@ahmadh.com>
|
||||||
Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com>
|
Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com>
|
||||||
|
|
|
@ -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_PORT = '' # Set to empty string for default. Not used with sqlite3.
|
||||||
DATABASE_OPTIONS = {} # Set to empty dictionary for default.
|
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.
|
# Host for sending e-mail.
|
||||||
EMAIL_HOST = 'localhost'
|
EMAIL_HOST = 'localhost'
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
||||||
|
# Mail backends shipped with Django.
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -1,13 +1,7 @@
|
||||||
"""
|
|
||||||
Tools for sending email.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import smtplib
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
from email import Charset, Encoders
|
from email import Charset, Encoders
|
||||||
from email.MIMEText import MIMEText
|
from email.MIMEText import MIMEText
|
||||||
from email.MIMEMultipart import MIMEMultipart
|
from email.MIMEMultipart import MIMEMultipart
|
||||||
|
@ -16,6 +10,7 @@ from email.Header import Header
|
||||||
from email.Utils import formatdate, parseaddr, formataddr
|
from email.Utils import formatdate, parseaddr, formataddr
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.mail.utils import DNS_NAME
|
||||||
from django.utils.encoding import smart_str, force_unicode
|
from django.utils.encoding import smart_str, force_unicode
|
||||||
|
|
||||||
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
|
# 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).
|
# and cannot be guessed).
|
||||||
DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
|
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):
|
class BadHeaderError(ValueError):
|
||||||
if not hasattr(self, '_fqdn'):
|
pass
|
||||||
self._fqdn = socket.getfqdn()
|
|
||||||
return self._fqdn
|
|
||||||
|
|
||||||
DNS_NAME = CachedDnsName()
|
|
||||||
|
|
||||||
# Copied from Python standard library, with the following modifications:
|
# Copied from Python standard library, with the following modifications:
|
||||||
# * Used cached hostname for performance.
|
# * 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)
|
msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
|
||||||
return msgid
|
return msgid
|
||||||
|
|
||||||
class BadHeaderError(ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def forbid_multi_line_headers(name, val):
|
def forbid_multi_line_headers(name, val):
|
||||||
"""Forbids multi-line headers, to prevent header injection."""
|
"""Forbids multi-line headers, to prevent header injection."""
|
||||||
|
@ -91,104 +76,18 @@ def forbid_multi_line_headers(name, val):
|
||||||
val = Header(val)
|
val = Header(val)
|
||||||
return name, val
|
return name, val
|
||||||
|
|
||||||
|
|
||||||
class SafeMIMEText(MIMEText):
|
class SafeMIMEText(MIMEText):
|
||||||
def __setitem__(self, name, val):
|
def __setitem__(self, name, val):
|
||||||
name, val = forbid_multi_line_headers(name, val)
|
name, val = forbid_multi_line_headers(name, val)
|
||||||
MIMEText.__setitem__(self, name, val)
|
MIMEText.__setitem__(self, name, val)
|
||||||
|
|
||||||
|
|
||||||
class SafeMIMEMultipart(MIMEMultipart):
|
class SafeMIMEMultipart(MIMEMultipart):
|
||||||
def __setitem__(self, name, val):
|
def __setitem__(self, name, val):
|
||||||
name, val = forbid_multi_line_headers(name, val)
|
name, val = forbid_multi_line_headers(name, val)
|
||||||
MIMEMultipart.__setitem__(self, 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):
|
class EmailMessage(object):
|
||||||
"""
|
"""
|
||||||
|
@ -199,14 +98,14 @@ class EmailMessage(object):
|
||||||
encoding = None # None => use settings default
|
encoding = None # None => use settings default
|
||||||
|
|
||||||
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
|
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
|
Initialize a single email message (which can be sent to multiple
|
||||||
recipients).
|
recipients).
|
||||||
|
|
||||||
All strings used to create the message can be unicode strings (or UTF-8
|
All strings used to create the message can be unicode strings
|
||||||
bytestrings). The SafeMIMEText class will handle any necessary encoding
|
(or UTF-8 bytestrings). The SafeMIMEText class will handle any
|
||||||
conversions.
|
necessary encoding conversions.
|
||||||
"""
|
"""
|
||||||
if to:
|
if to:
|
||||||
assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
|
assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
|
||||||
|
@ -226,8 +125,9 @@ class EmailMessage(object):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
def get_connection(self, fail_silently=False):
|
def get_connection(self, fail_silently=False):
|
||||||
|
from django.core.mail import get_connection
|
||||||
if not self.connection:
|
if not self.connection:
|
||||||
self.connection = SMTPConnection(fail_silently=fail_silently)
|
self.connection = get_connection(fail_silently=fail_silently)
|
||||||
return self.connection
|
return self.connection
|
||||||
|
|
||||||
def message(self):
|
def message(self):
|
||||||
|
@ -332,6 +232,7 @@ class EmailMessage(object):
|
||||||
filename=filename)
|
filename=filename)
|
||||||
return attachment
|
return attachment
|
||||||
|
|
||||||
|
|
||||||
class EmailMultiAlternatives(EmailMessage):
|
class EmailMultiAlternatives(EmailMessage):
|
||||||
"""
|
"""
|
||||||
A version of EmailMessage that makes it easy to send multipart/alternative
|
A version of EmailMessage that makes it easy to send multipart/alternative
|
||||||
|
@ -371,56 +272,3 @@ class EmailMultiAlternatives(EmailMessage):
|
||||||
for alternative in self.alternatives:
|
for alternative in self.alternatives:
|
||||||
msg.attach(self._create_mime_attachment(*alternative))
|
msg.attach(self._create_mime_attachment(*alternative))
|
||||||
return msg
|
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)
|
|
|
@ -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()
|
|
@ -2,6 +2,7 @@ import sys, time, os
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
|
from django.core.mail.backends import locmem
|
||||||
from django.test import signals
|
from django.test import signals
|
||||||
from django.template import Template
|
from django.template import Template
|
||||||
from django.utils.translation import deactivate
|
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)
|
signals.template_rendered.send(sender=self, template=self, context=context)
|
||||||
return self.nodelist.render(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():
|
def setup_test_environment():
|
||||||
"""Perform any global pre-test setup. This involves:
|
"""Perform any global pre-test setup. This involves:
|
||||||
|
|
||||||
- Installing the instrumented test renderer
|
- 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.
|
- Setting the active locale to match the LANGUAGE_CODE setting.
|
||||||
"""
|
"""
|
||||||
Template.original_render = Template.render
|
Template.original_render = Template.render
|
||||||
Template.render = instrumented_test_render
|
Template.render = instrumented_test_render
|
||||||
|
|
||||||
mail.original_SMTPConnection = mail.SMTPConnection
|
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 = []
|
mail.outbox = []
|
||||||
|
|
||||||
|
@ -77,8 +63,10 @@ def teardown_test_environment():
|
||||||
mail.SMTPConnection = mail.original_SMTPConnection
|
mail.SMTPConnection = mail.original_SMTPConnection
|
||||||
del 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):
|
def get_runner(settings):
|
||||||
test_path = settings.TEST_RUNNER.split('.')
|
test_path = settings.TEST_RUNNER.split('.')
|
||||||
|
|
|
@ -22,6 +22,9 @@ their deprecation, as per the :ref:`Django deprecation policy
|
||||||
* The old imports for CSRF functionality (``django.contrib.csrf.*``),
|
* The old imports for CSRF functionality (``django.contrib.csrf.*``),
|
||||||
which moved to core in 1.2, will be removed.
|
which moved to core in 1.2, will be removed.
|
||||||
|
|
||||||
|
* ``SMTPConnection``. The 1.2 release deprecated the ``SMTPConnection``
|
||||||
|
class in favor of a generic E-mail backend API.
|
||||||
|
|
||||||
* 2.0
|
* 2.0
|
||||||
* ``django.views.defaults.shortcut()``. This function has been moved
|
* ``django.views.defaults.shortcut()``. This function has been moved
|
||||||
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
|
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
|
||||||
|
|
|
@ -424,6 +424,29 @@ are not allowed to visit any page, systemwide. Use this for bad robots/crawlers.
|
||||||
This is only used if ``CommonMiddleware`` is installed (see
|
This is only used if ``CommonMiddleware`` is installed (see
|
||||||
:ref:`topics-http-middleware`).
|
:ref:`topics-http-middleware`).
|
||||||
|
|
||||||
|
.. setting:: EMAIL_BACKEND
|
||||||
|
|
||||||
|
EMAIL_BACKEND
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. versionadded:: 1.2
|
||||||
|
|
||||||
|
Default: ``'smtp'``
|
||||||
|
|
||||||
|
The backend to use for sending emails. For the list of available backends see
|
||||||
|
:ref:`topics-email`.
|
||||||
|
|
||||||
|
.. setting:: EMAIL_FILE_PATH
|
||||||
|
|
||||||
|
EMAIL_FILE_PATH
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. versionadded:: 1.2
|
||||||
|
|
||||||
|
Default: Not defined
|
||||||
|
|
||||||
|
The directory used by the ``file`` email backend to store output files.
|
||||||
|
|
||||||
.. setting:: EMAIL_HOST
|
.. setting:: EMAIL_HOST
|
||||||
|
|
||||||
EMAIL_HOST
|
EMAIL_HOST
|
||||||
|
|
|
@ -7,11 +7,13 @@ Sending e-mail
|
||||||
.. module:: django.core.mail
|
.. module:: django.core.mail
|
||||||
:synopsis: Helpers to easily send e-mail.
|
:synopsis: Helpers to easily send e-mail.
|
||||||
|
|
||||||
Although Python makes sending e-mail relatively easy via the `smtplib library`_,
|
Although Python makes sending e-mail relatively easy via the `smtplib
|
||||||
Django provides a couple of light wrappers over it, to make sending e-mail
|
library`_, Django provides a couple of light wrappers over it. These wrappers
|
||||||
extra quick.
|
are provided to make sending e-mail extra quick, to make it easy to test
|
||||||
|
email sending during development, and to provide support for platforms that
|
||||||
|
can't use SMTP.
|
||||||
|
|
||||||
The code lives in a single module: ``django.core.mail``.
|
The code lives in the ``django.core.mail`` module.
|
||||||
|
|
||||||
.. _smtplib library: http://docs.python.org/library/smtplib.html
|
.. _smtplib library: http://docs.python.org/library/smtplib.html
|
||||||
|
|
||||||
|
@ -25,11 +27,11 @@ In two lines::
|
||||||
send_mail('Subject here', 'Here is the message.', 'from@example.com',
|
send_mail('Subject here', 'Here is the message.', 'from@example.com',
|
||||||
['to@example.com'], fail_silently=False)
|
['to@example.com'], fail_silently=False)
|
||||||
|
|
||||||
Mail is sent using the SMTP host and port specified in the :setting:`EMAIL_HOST`
|
Mail is sent using the SMTP host and port specified in the
|
||||||
and :setting:`EMAIL_PORT` settings. The :setting:`EMAIL_HOST_USER` and
|
:setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
|
||||||
:setting:`EMAIL_HOST_PASSWORD` settings, if set, are used to authenticate to the
|
:setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
|
||||||
SMTP server, and the :setting:`EMAIL_USE_TLS` setting controls whether a secure
|
set, are used to authenticate to the SMTP server, and the
|
||||||
connection is used.
|
:setting:`EMAIL_USE_TLS` setting controls whether a secure connection is used.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -42,7 +44,7 @@ send_mail()
|
||||||
The simplest way to send e-mail is using the function
|
The simplest way to send e-mail is using the function
|
||||||
``django.core.mail.send_mail()``. Here's its definition:
|
``django.core.mail.send_mail()``. Here's its definition:
|
||||||
|
|
||||||
.. function:: send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None)
|
.. function:: send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None)
|
||||||
|
|
||||||
The ``subject``, ``message``, ``from_email`` and ``recipient_list`` parameters
|
The ``subject``, ``message``, ``from_email`` and ``recipient_list`` parameters
|
||||||
are required.
|
are required.
|
||||||
|
@ -62,6 +64,10 @@ are required.
|
||||||
* ``auth_password``: The optional password to use to authenticate to the
|
* ``auth_password``: The optional password to use to authenticate to the
|
||||||
SMTP server. If this isn't provided, Django will use the value of the
|
SMTP server. If this isn't provided, Django will use the value of the
|
||||||
``EMAIL_HOST_PASSWORD`` setting.
|
``EMAIL_HOST_PASSWORD`` setting.
|
||||||
|
* ``connection``: The optional email backend to use to send the mail.
|
||||||
|
If unspecified, an instance of the default backend will be used.
|
||||||
|
See the documentation on :ref:`E-mail backends <topic-email-backends>`
|
||||||
|
for more details.
|
||||||
|
|
||||||
.. _smtplib docs: http://docs.python.org/library/smtplib.html
|
.. _smtplib docs: http://docs.python.org/library/smtplib.html
|
||||||
|
|
||||||
|
@ -71,26 +77,29 @@ send_mass_mail()
|
||||||
``django.core.mail.send_mass_mail()`` is intended to handle mass e-mailing.
|
``django.core.mail.send_mass_mail()`` is intended to handle mass e-mailing.
|
||||||
Here's the definition:
|
Here's the definition:
|
||||||
|
|
||||||
.. function:: send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None)
|
.. function:: send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None, connection=None)
|
||||||
|
|
||||||
``datatuple`` is a tuple in which each element is in this format::
|
``datatuple`` is a tuple in which each element is in this format::
|
||||||
|
|
||||||
(subject, message, from_email, recipient_list)
|
(subject, message, from_email, recipient_list)
|
||||||
|
|
||||||
``fail_silently``, ``auth_user`` and ``auth_password`` have the same functions
|
``fail_silently``, ``auth_user`` and ``auth_password`` have the same functions
|
||||||
as in ``send_mail()``.
|
as in :meth:`~django.core.mail.send_mail()`.
|
||||||
|
|
||||||
Each separate element of ``datatuple`` results in a separate e-mail message.
|
Each separate element of ``datatuple`` results in a separate e-mail message.
|
||||||
As in ``send_mail()``, recipients in the same ``recipient_list`` will all see
|
As in :meth:`~django.core.mail.send_mail()`, recipients in the same
|
||||||
the other addresses in the e-mail messages' "To:" field.
|
``recipient_list`` will all see the other addresses in the e-mail messages'
|
||||||
|
"To:" field.
|
||||||
|
|
||||||
send_mass_mail() vs. send_mail()
|
send_mass_mail() vs. send_mail()
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
The main difference between ``send_mass_mail()`` and ``send_mail()`` is that
|
The main difference between :meth:`~django.core.mail.send_mass_mail()` and
|
||||||
``send_mail()`` opens a connection to the mail server each time it's executed,
|
:meth:`~django.core.mail.send_mail()` is that
|
||||||
while ``send_mass_mail()`` uses a single connection for all of its messages.
|
:meth:`~django.core.mail.send_mail()` opens a connection to the mail server
|
||||||
This makes ``send_mass_mail()`` slightly more efficient.
|
each time it's executed, while :meth:`~django.core.mail.send_mass_mail()` uses
|
||||||
|
a single connection for all of its messages. This makes
|
||||||
|
:meth:`~django.core.mail.send_mass_mail()` slightly more efficient.
|
||||||
|
|
||||||
mail_admins()
|
mail_admins()
|
||||||
=============
|
=============
|
||||||
|
@ -98,7 +107,7 @@ mail_admins()
|
||||||
``django.core.mail.mail_admins()`` is a shortcut for sending an e-mail to the
|
``django.core.mail.mail_admins()`` is a shortcut for sending an e-mail to the
|
||||||
site admins, as defined in the :setting:`ADMINS` setting. Here's the definition:
|
site admins, as defined in the :setting:`ADMINS` setting. Here's the definition:
|
||||||
|
|
||||||
.. function:: mail_admins(subject, message, fail_silently=False)
|
.. function:: mail_admins(subject, message, fail_silently=False, connection=None)
|
||||||
|
|
||||||
``mail_admins()`` prefixes the subject with the value of the
|
``mail_admins()`` prefixes the subject with the value of the
|
||||||
:setting:`EMAIL_SUBJECT_PREFIX` setting, which is ``"[Django] "`` by default.
|
:setting:`EMAIL_SUBJECT_PREFIX` setting, which is ``"[Django] "`` by default.
|
||||||
|
@ -115,7 +124,7 @@ mail_managers() function
|
||||||
sends an e-mail to the site managers, as defined in the :setting:`MANAGERS`
|
sends an e-mail to the site managers, as defined in the :setting:`MANAGERS`
|
||||||
setting. Here's the definition:
|
setting. Here's the definition:
|
||||||
|
|
||||||
.. function:: mail_managers(subject, message, fail_silently=False)
|
.. function:: mail_managers(subject, message, fail_silently=False, connection=None)
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
========
|
========
|
||||||
|
@ -145,7 +154,7 @@ scripts generate.
|
||||||
The Django e-mail functions outlined above all protect against header injection
|
The Django e-mail functions outlined above all protect against header injection
|
||||||
by forbidding newlines in header values. If any ``subject``, ``from_email`` or
|
by forbidding newlines in header values. If any ``subject``, ``from_email`` or
|
||||||
``recipient_list`` contains a newline (in either Unix, Windows or Mac style),
|
``recipient_list`` contains a newline (in either Unix, Windows or Mac style),
|
||||||
the e-mail function (e.g. ``send_mail()``) will raise
|
the e-mail function (e.g. :meth:`~django.core.mail.send_mail()`) will raise
|
||||||
``django.core.mail.BadHeaderError`` (a subclass of ``ValueError``) and, hence,
|
``django.core.mail.BadHeaderError`` (a subclass of ``ValueError``) and, hence,
|
||||||
will not send the e-mail. It's your responsibility to validate all data before
|
will not send the e-mail. It's your responsibility to validate all data before
|
||||||
passing it to the e-mail functions.
|
passing it to the e-mail functions.
|
||||||
|
@ -178,41 +187,47 @@ from the request's POST data, sends that to admin@example.com and redirects to
|
||||||
|
|
||||||
.. _emailmessage-and-smtpconnection:
|
.. _emailmessage-and-smtpconnection:
|
||||||
|
|
||||||
The EmailMessage and SMTPConnection classes
|
The EmailMessage class
|
||||||
===========================================
|
======================
|
||||||
|
|
||||||
.. versionadded:: 1.0
|
.. versionadded:: 1.0
|
||||||
|
|
||||||
Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
|
Django's :meth:`~django.core.mail.send_mail()` and
|
||||||
wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
|
:meth:`~django.core.mail.send_mass_mail()` functions are actually thin
|
||||||
in ``django.core.mail``. If you ever need to customize the way Django sends
|
wrappers that make use of the :class:`~django.core.mail.EmailMessage` class.
|
||||||
e-mail, you can subclass these two classes to suit your needs.
|
|
||||||
|
Not all features of the :class:`~django.core.mail.EmailMessage` class are
|
||||||
|
available through the :meth:`~django.core.mail.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
|
||||||
|
:class:`~django.core.mail.EmailMessage` instances directly.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Not all features of the ``EmailMessage`` class are available through the
|
This is a design feature. :meth:`~django.core.mail.send_mail()` and
|
||||||
``send_mail()`` and related wrapper functions. If you wish to use advanced
|
related functions were originally the only interface Django provided.
|
||||||
features, such as BCC'ed recipients, file attachments, or multi-part
|
However, the list of parameters they accepted was slowly growing over
|
||||||
e-mail, you'll need to create ``EmailMessage`` instances directly.
|
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
|
:class:`~django.core.mail.EmailMessage` is responsible for creating the e-mail
|
||||||
originally the only interface Django provided. However, the list of
|
message itself. The :ref:`e-mail backend <topic-email-backends>` is then
|
||||||
parameters they accepted was slowly growing over time. It made sense to
|
responsible for sending the e-mail.
|
||||||
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
|
For convenience, :class:`~django.core.mail.EmailMessage` provides a simple
|
||||||
itself. ``SMTPConnection`` is responsible for the network connection side of
|
``send()`` method for sending a single email. If you need to send multiple
|
||||||
the operation. This means you can reuse the same connection (an
|
messages, the email backend API :ref:`provides an alternative
|
||||||
``SMTPConnection`` instance) for multiple messages.
|
<topics-sending-multiple-emails>`.
|
||||||
|
|
||||||
EmailMessage Objects
|
EmailMessage Objects
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
.. class:: EmailMessage
|
.. class:: EmailMessage
|
||||||
|
|
||||||
The ``EmailMessage`` class is initialized with the following parameters (in
|
The :class:`~django.core.mail.EmailMessage` class is initialized with the
|
||||||
the given order, if positional arguments are used). All parameters are
|
following parameters (in the given order, if positional arguments are used).
|
||||||
optional and can be set at any time prior to calling the ``send()`` method.
|
All parameters are optional and can be set at any time prior to calling the
|
||||||
|
``send()`` method.
|
||||||
|
|
||||||
* ``subject``: The subject line of the e-mail.
|
* ``subject``: The subject line of the e-mail.
|
||||||
|
|
||||||
|
@ -227,7 +242,7 @@ optional and can be set at any time prior to calling the ``send()`` method.
|
||||||
* ``bcc``: A list or tuple of addresses used in the "Bcc" header when
|
* ``bcc``: A list or tuple of addresses used in the "Bcc" header when
|
||||||
sending the e-mail.
|
sending the e-mail.
|
||||||
|
|
||||||
* ``connection``: An ``SMTPConnection`` instance. Use this parameter if
|
* ``connection``: An e-mail backend instance. Use this parameter if
|
||||||
you want to use the same connection for multiple messages. If omitted, a
|
you want to use the same connection for multiple messages. If omitted, a
|
||||||
new connection is created when ``send()`` is called.
|
new connection is created when ``send()`` is called.
|
||||||
|
|
||||||
|
@ -248,18 +263,18 @@ For example::
|
||||||
|
|
||||||
The class has the following methods:
|
The class has the following methods:
|
||||||
|
|
||||||
* ``send(fail_silently=False)`` sends the message, using either
|
* ``send(fail_silently=False)`` sends the message. If a connection was
|
||||||
the connection that is specified in the ``connection``
|
specified when the email was constructed, that connection will be used.
|
||||||
attribute, or creating a new connection if none already
|
Otherwise, an instance of the default backend will be instantiated and
|
||||||
exists. If the keyword argument ``fail_silently`` is ``True``,
|
used. If the keyword argument ``fail_silently`` is ``True``, exceptions
|
||||||
exceptions raised while sending the message will be quashed.
|
raised while sending the message will be quashed.
|
||||||
|
|
||||||
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
|
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
|
||||||
subclass of Python's ``email.MIMEText.MIMEText`` class) or a
|
subclass of Python's ``email.MIMEText.MIMEText`` class) or a
|
||||||
``django.core.mail.SafeMIMEMultipart`` object holding the
|
``django.core.mail.SafeMIMEMultipart`` object holding the message to be
|
||||||
message to be sent. If you ever need to extend the ``EmailMessage`` class,
|
sent. If you ever need to extend the
|
||||||
you'll probably want to override this method to put the content you want
|
:class:`~django.core.mail.EmailMessage` class, you'll probably want to
|
||||||
into the MIME object.
|
override this method to put the content you want into the MIME object.
|
||||||
|
|
||||||
* ``recipients()`` returns a list of all the recipients of the message,
|
* ``recipients()`` returns a list of all the recipients of the message,
|
||||||
whether they're recorded in the ``to`` or ``bcc`` attributes. This is
|
whether they're recorded in the ``to`` or ``bcc`` attributes. This is
|
||||||
|
@ -299,13 +314,13 @@ The class has the following methods:
|
||||||
Sending alternative content types
|
Sending alternative content types
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
It can be useful to include multiple versions of the content in an e-mail;
|
It can be useful to include multiple versions of the content in an e-mail; the
|
||||||
the classic example is to send both text and HTML versions of a message. With
|
classic example is to send both text and HTML versions of a message. With
|
||||||
Django's e-mail library, you can do this using the ``EmailMultiAlternatives``
|
Django's e-mail library, you can do this using the ``EmailMultiAlternatives``
|
||||||
class. This subclass of ``EmailMessage`` has an ``attach_alternative()`` method
|
class. This subclass of :class:`~django.core.mail.EmailMessage` has an
|
||||||
for including extra versions of the message body in the e-mail. All the other
|
``attach_alternative()`` method for including extra versions of the message
|
||||||
methods (including the class initialization) are inherited directly from
|
body in the e-mail. All the other methods (including the class initialization)
|
||||||
``EmailMessage``.
|
are inherited directly from :class:`~django.core.mail.EmailMessage`.
|
||||||
|
|
||||||
To send a text and HTML combination, you could write::
|
To send a text and HTML combination, you could write::
|
||||||
|
|
||||||
|
@ -318,41 +333,231 @@ To send a text and HTML combination, you could write::
|
||||||
msg.attach_alternative(html_content, "text/html")
|
msg.attach_alternative(html_content, "text/html")
|
||||||
msg.send()
|
msg.send()
|
||||||
|
|
||||||
By default, the MIME type of the ``body`` parameter in an ``EmailMessage`` is
|
By default, the MIME type of the ``body`` parameter in an
|
||||||
``"text/plain"``. It is good practice to leave this alone, because it
|
:class:`~django.core.mail.EmailMessage` is ``"text/plain"``. It is good
|
||||||
guarantees that any recipient will be able to read the e-mail, regardless of
|
practice to leave this alone, because it guarantees that any recipient will be
|
||||||
their mail client. However, if you are confident that your recipients can
|
able to read the e-mail, regardless of their mail client. However, if you are
|
||||||
handle an alternative content type, you can use the ``content_subtype``
|
confident that your recipients can handle an alternative content type, you can
|
||||||
attribute on the ``EmailMessage`` class to change the main content type. The
|
use the ``content_subtype`` attribute on the
|
||||||
major type will always be ``"text"``, but you can change it to the subtype. For
|
:class:`~django.core.mail.EmailMessage` class to change the main content type.
|
||||||
example::
|
The major type will always be ``"text"``, but you can change it to the
|
||||||
|
subtype. For example::
|
||||||
|
|
||||||
msg = EmailMessage(subject, html_content, from_email, [to])
|
msg = EmailMessage(subject, html_content, from_email, [to])
|
||||||
msg.content_subtype = "html" # Main content is now text/html
|
msg.content_subtype = "html" # Main content is now text/html
|
||||||
msg.send()
|
msg.send()
|
||||||
|
|
||||||
SMTPConnection Objects
|
.. _topic-email-backends:
|
||||||
----------------------
|
|
||||||
|
|
||||||
.. class:: SMTPConnection
|
E-Mail Backends
|
||||||
|
===============
|
||||||
|
|
||||||
The ``SMTPConnection`` class is initialized with the host, port, username and
|
.. versionadded:: 1.2
|
||||||
password for the SMTP server. If you don't specify one or more of those
|
|
||||||
options, they are read from your settings file.
|
|
||||||
|
|
||||||
If you're sending lots of messages at once, the ``send_messages()`` method of
|
The actual sending of an e-mail is handled by the e-mail backend.
|
||||||
the ``SMTPConnection`` class is useful. It takes a list of ``EmailMessage``
|
|
||||||
instances (or subclasses) and sends them over a single connection. For example,
|
|
||||||
if you have a function called ``get_notification_email()`` that returns a
|
|
||||||
list of ``EmailMessage`` objects representing some periodic e-mail you wish to
|
|
||||||
send out, you could send this with::
|
|
||||||
|
|
||||||
connection = SMTPConnection() # Use default settings for connection
|
The e-mail backend class has the following methods:
|
||||||
|
|
||||||
|
* ``open()`` instantiates an long-lived email-sending connection.
|
||||||
|
|
||||||
|
* ``close()`` closes the current email-sending connection.
|
||||||
|
|
||||||
|
* ``send_messages(email_messages)`` sends a list of
|
||||||
|
:class:`~django.core.mail.EmailMessage` objects. If the connection is
|
||||||
|
not open, this call will implicitly open the connection, and close the
|
||||||
|
connection afterwards. If the connection is already open, it will be
|
||||||
|
left open after mail has been sent.
|
||||||
|
|
||||||
|
Obtaining an instance of an e-mail backend
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
The :meth:`get_connection` function in ``django.core.mail`` returns an
|
||||||
|
instance of the e-mail backend that you can use.
|
||||||
|
|
||||||
|
.. currentmodule:: django.core.mail
|
||||||
|
|
||||||
|
.. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs)
|
||||||
|
|
||||||
|
By default, a call to ``get_connection()`` will return an instance of the
|
||||||
|
email backend specified in :setting:`EMAIL_BACKEND`. If you specify the
|
||||||
|
``backend`` argument, an instance of that backend will be instantiated.
|
||||||
|
|
||||||
|
The ``fail_silently`` argument controls how the backend should handle errors.
|
||||||
|
If ``fail_silently`` is True, exceptions during the email sending process
|
||||||
|
will be silently ignored.
|
||||||
|
|
||||||
|
All other arguments are passed directly to the constructor of the
|
||||||
|
e-mail backend.
|
||||||
|
|
||||||
|
Django ships with several e-mail sending backends. With the exception of the
|
||||||
|
SMTP backend (which is the default), these backends are only useful during
|
||||||
|
testing and development. If you have special email sending requirements, you
|
||||||
|
can :ref:`write your own email backend <topic-custom-email-backend>`.
|
||||||
|
|
||||||
|
.. _topic-email-smtp-backend:
|
||||||
|
|
||||||
|
SMTP backend
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is the default backend. E-mail 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 backend is the default configuration inherited by Django. If you
|
||||||
|
want to specify it explicitly, put the following in your settings::
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp'
|
||||||
|
|
||||||
|
.. admonition:: SMTPConnection objects
|
||||||
|
|
||||||
|
Prior to version 1.2, Django provided a
|
||||||
|
:class:`~django.core.mail.SMTPConnection` class. This class provided a way
|
||||||
|
to directly control the use of SMTP to send email. This class has been
|
||||||
|
deprecated in favor of the generic email backend API.
|
||||||
|
|
||||||
|
For backwards compatibility :class:`~django.core.mail.SMTPConnection` is
|
||||||
|
still available in ``django.core.mail`` as an alias for the SMTP backend.
|
||||||
|
New code should use :meth:`~django.core.mail.get_connection` instead.
|
||||||
|
|
||||||
|
Console backend
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Instead of sending out real e-mails the console backend just writes the
|
||||||
|
e-mails that would be send to the standard output. By default, the console
|
||||||
|
backend writes to ``stdout``. You can use a different stream-like object by
|
||||||
|
providing the ``stream`` keyword argument when constructing the connection.
|
||||||
|
|
||||||
|
To specify this backend, put the following in your settings::
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.console'
|
||||||
|
|
||||||
|
This backend is not intended for use in production -- it is provided as a
|
||||||
|
convenience that can be used during development.
|
||||||
|
|
||||||
|
File backend
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The file backend writes e-mails to a file. A new file is created for each new
|
||||||
|
session that is opened on this backend. The directory to which the files are
|
||||||
|
written is either taken from the :setting:`EMAIL_FILE_PATH` setting or from
|
||||||
|
the ``file_path`` keyword when creating a connection with
|
||||||
|
:meth:`~django.core.mail.get_connection`.
|
||||||
|
|
||||||
|
To specify this backend, put the following in your settings::
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.filebased'
|
||||||
|
EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location
|
||||||
|
|
||||||
|
This backend is not intended for use in production -- it is provided as a
|
||||||
|
convenience that can be used during development.
|
||||||
|
|
||||||
|
In-memory backend
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``'locmem'`` backend stores messages in a special attribute of the
|
||||||
|
``django.core.mail`` module. The ``outbox`` attribute is created when the
|
||||||
|
first message is send. It's a list with an
|
||||||
|
:class:`~django.core.mail.EmailMessage` instance for each message that would
|
||||||
|
be send.
|
||||||
|
|
||||||
|
To specify this backend, put the following in your settings::
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.locmem'
|
||||||
|
|
||||||
|
This backend is not intended for use in production -- it is provided as a
|
||||||
|
convenience that can be used during development and testing.
|
||||||
|
|
||||||
|
Dummy backend
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
As the name suggests the dummy backend does nothing with your messages. To
|
||||||
|
specify this backend, put the following in your settings::
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.dummy'
|
||||||
|
|
||||||
|
This backend is not intended for use in production -- it is provided as a
|
||||||
|
convenience that can be used during development.
|
||||||
|
|
||||||
|
.. _topic-custom-email-backend:
|
||||||
|
|
||||||
|
Defining a custom e-mail backend
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
If you need to change how e-mails are send you can write your own e-mail
|
||||||
|
backend. The ``EMAIL_BACKEND`` setting in your settings file is then the
|
||||||
|
Python import path for your backend.
|
||||||
|
|
||||||
|
Custom e-mail backends should subclass ``BaseEmailBackend`` that is located in
|
||||||
|
the ``django.core.mail.backends.base`` module. A custom e-mail backend must
|
||||||
|
implement the ``send_messages(email_messages)`` method. This method receives a
|
||||||
|
list of :class:`~django.core.mail.EmailMessage` instances and returns the
|
||||||
|
number of successfully delivered messages. If your backend has any concept of
|
||||||
|
a persistent session or connection, you should also implement the ``open()``
|
||||||
|
and ``close()`` methods. Refer to ``SMTPEmailBackend`` for a reference
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
.. _topics-sending-multiple-emails:
|
||||||
|
|
||||||
|
Sending multiple emails
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Establishing and closing an SMTP connection (or any other network connection,
|
||||||
|
for that matter) is an expensive process. If you have a lot of emails to send,
|
||||||
|
it makes sense to reuse an SMTP connection, rather than creating and
|
||||||
|
destroying a connection every time you want to send an email.
|
||||||
|
|
||||||
|
There are two ways you tell an email backend to reuse a connection.
|
||||||
|
|
||||||
|
Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes
|
||||||
|
a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses),
|
||||||
|
and sends them all using a single connection.
|
||||||
|
|
||||||
|
For example, if you have a function called ``get_notification_email()`` that
|
||||||
|
returns a list of :class:`~django.core.mail.EmailMessage` objects representing
|
||||||
|
some periodic e-mail you wish to send out, you could send these emails using
|
||||||
|
a single call to send_messages::
|
||||||
|
|
||||||
|
from django.core import mail
|
||||||
|
connection = mail.get_connection() # Use default email connection
|
||||||
messages = get_notification_email()
|
messages = get_notification_email()
|
||||||
connection.send_messages(messages)
|
connection.send_messages(messages)
|
||||||
|
|
||||||
|
In this example, the call to ``send_messages()`` opens a connection on the
|
||||||
|
backend, sends the list of messages, and then closes the connection again.
|
||||||
|
|
||||||
|
The second approach is to use the ``open()`` and ``close()`` methods on the
|
||||||
|
email backend to manually control the connection. ``send_messages()`` will not
|
||||||
|
manually open or close the connection if it is already open, so if you
|
||||||
|
manually open the connection, you can control when it is closed. For example::
|
||||||
|
|
||||||
|
from django.core import mail
|
||||||
|
connection = mail.get_connection()
|
||||||
|
|
||||||
|
# Manually open the connection
|
||||||
|
connection.open()
|
||||||
|
|
||||||
|
# Construct an email message that uses the connection
|
||||||
|
email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
|
||||||
|
['to1@example.com'], connection=connection)
|
||||||
|
email1.send() # Send the email
|
||||||
|
|
||||||
|
# Construct two more messages
|
||||||
|
email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
|
||||||
|
['to2@example.com'])
|
||||||
|
email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
|
||||||
|
['to3@example.com'])
|
||||||
|
|
||||||
|
# Send the two emails in a single call -
|
||||||
|
connection.send_messages([email2, email3])
|
||||||
|
# The connection was already open so send_messages() doesn't close it.
|
||||||
|
# We need to manually close the connection.
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
|
||||||
Testing e-mail sending
|
Testing e-mail sending
|
||||||
----------------------
|
======================
|
||||||
|
|
||||||
The are times when you do not want Django to send e-mails at all. For example,
|
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
|
while developing a website, you probably don't want to send out thousands of
|
||||||
|
@ -360,19 +565,41 @@ e-mails -- but you may want to validate that e-mails will be sent to the right
|
||||||
people under the right conditions, and that those e-mails will contain the
|
people under the right conditions, and that those e-mails will contain the
|
||||||
correct content.
|
correct content.
|
||||||
|
|
||||||
The easiest way to test your project's use of e-mail is to use a "dumb" e-mail
|
The easiest way to test your project's use of e-mail is to use the ``console``
|
||||||
server that receives the e-mails locally and displays them to the terminal,
|
email backend. This backend redirects all email to stdout, allowing you to
|
||||||
but does not actually send anything. Python has a built-in way to accomplish
|
inspect the content of mail.
|
||||||
this with a single command::
|
|
||||||
|
The ``file`` email backend can also be useful during development -- this backend
|
||||||
|
dumps the contents of every SMTP connection to a file that can be inspected
|
||||||
|
at your leisure.
|
||||||
|
|
||||||
|
Another approach is to use a "dumb" SMTP 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
|
python -m smtpd -n -c DebuggingServer localhost:1025
|
||||||
|
|
||||||
This command will start a simple SMTP server listening on port 1025 of
|
This command will start a simple SMTP server listening on port 1025 of
|
||||||
localhost. This server simply prints to standard output all email headers and
|
localhost. This server simply prints to standard output all e-mail headers and
|
||||||
the email body. You then only need to set the :setting:`EMAIL_HOST` and
|
the e-mail body. You then only need to set the :setting:`EMAIL_HOST` and
|
||||||
:setting:`EMAIL_PORT` accordingly, and you are set.
|
:setting:`EMAIL_PORT` accordingly, and you are set.
|
||||||
|
|
||||||
For more entailed testing and processing of e-mails locally, see the Python
|
For a more detailed discussion of testing and processing of e-mails locally,
|
||||||
documentation on the `SMTP Server`_.
|
see the Python documentation on the `SMTP Server`_.
|
||||||
|
|
||||||
.. _SMTP Server: http://docs.python.org/library/smtpd.html
|
.. _SMTP Server: http://docs.python.org/library/smtpd.html
|
||||||
|
|
||||||
|
SMTPConnection
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. class:: SMTPConnection
|
||||||
|
|
||||||
|
.. deprecated:: 1.2
|
||||||
|
|
||||||
|
The ``SMTPConnection`` class has been deprecated in favor of the generic email
|
||||||
|
backend API.
|
||||||
|
|
||||||
|
For backwards compatibility ``SMTPConnection`` is still available in
|
||||||
|
``django.core.mail`` as an alias for the :ref:`SMTP backend
|
||||||
|
<topic-email-smtp-backend>`. New code should use
|
||||||
|
:meth:`~django.core.mail.get_connection` instead.
|
||||||
|
|
|
@ -1104,6 +1104,8 @@ applications:
|
||||||
``target_status_code`` will be the url and status code for the final
|
``target_status_code`` will be the url and status code for the final
|
||||||
point of the redirect chain.
|
point of the redirect chain.
|
||||||
|
|
||||||
|
.. _topics-testing-email:
|
||||||
|
|
||||||
E-mail services
|
E-mail services
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -1117,7 +1119,7 @@ test every aspect of sending e-mail -- from the number of messages sent to the
|
||||||
contents of each message -- without actually sending the messages.
|
contents of each message -- without actually sending the messages.
|
||||||
|
|
||||||
The test runner accomplishes this by transparently replacing the normal
|
The test runner accomplishes this by transparently replacing the normal
|
||||||
:class:`~django.core.mail.SMTPConnection` class with a different version.
|
email backend with a testing backend.
|
||||||
(Don't worry -- this has no effect on any other e-mail senders outside of
|
(Don't worry -- this has no effect on any other e-mail senders outside of
|
||||||
Django, such as your machine's mail server, if you're running one.)
|
Django, such as your machine's mail server, if you're running one.)
|
||||||
|
|
||||||
|
@ -1128,14 +1130,8 @@ Django, such as your machine's mail server, if you're running one.)
|
||||||
During test running, each outgoing e-mail is saved in
|
During test running, each outgoing e-mail is saved in
|
||||||
``django.core.mail.outbox``. This is a simple list of all
|
``django.core.mail.outbox``. This is a simple list of all
|
||||||
:class:`~django.core.mail.EmailMessage` instances that have been sent.
|
:class:`~django.core.mail.EmailMessage` instances that have been sent.
|
||||||
It does not exist under normal execution conditions, i.e., when you're not
|
|
||||||
running unit tests. The outbox is created during test setup, along with the
|
|
||||||
dummy :class:`~django.core.mail.SMTPConnection`. When the test framework is
|
|
||||||
torn down, the standard :class:`~django.core.mail.SMTPConnection` class is
|
|
||||||
restored, and the test outbox is destroyed.
|
|
||||||
|
|
||||||
The ``outbox`` attribute is a special attribute that is created *only* when
|
The ``outbox`` attribute is a special attribute that is created *only* when
|
||||||
the tests are run. It doesn't normally exist as part of the
|
the ``locmem`` e-mail backend is used. It doesn't normally exist as part of the
|
||||||
:mod:`django.core.mail` module and you can't import it directly. The code
|
:mod:`django.core.mail` module and you can't import it directly. The code
|
||||||
below shows how to access this attribute correctly.
|
below shows how to access this attribute correctly.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
"""A custom backend for testing."""
|
||||||
|
|
||||||
|
from django.core.mail.backends.base import BaseEmailBackend
|
||||||
|
|
||||||
|
|
||||||
|
class EmailBackend(BaseEmailBackend):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(EmailBackend, self).__init__(*args, **kwargs)
|
||||||
|
self.test_outbox = []
|
||||||
|
|
||||||
|
def send_messages(self, email_messages):
|
||||||
|
# Messages are stored in a instance variable for testing.
|
||||||
|
self.test_outbox.extend(email_messages)
|
||||||
|
return len(email_messages)
|
|
@ -1,10 +1,18 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
r"""
|
r"""
|
||||||
# Tests for the django.core.mail.
|
# Tests for the django.core.mail.
|
||||||
|
|
||||||
|
>>> import os
|
||||||
|
>>> import shutil
|
||||||
|
>>> import tempfile
|
||||||
|
>>> from StringIO import StringIO
|
||||||
>>> from django.conf import settings
|
>>> from django.conf import settings
|
||||||
>>> from django.core import mail
|
>>> from django.core import mail
|
||||||
>>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
|
>>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
|
||||||
|
>>> from django.core.mail import send_mail, send_mass_mail
|
||||||
|
>>> from django.core.mail.backends.base import BaseEmailBackend
|
||||||
|
>>> from django.core.mail.backends import console, dummy, locmem, filebased, smtp
|
||||||
>>> from django.utils.translation import ugettext_lazy
|
>>> from django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
# Test normal ascii character case:
|
# Test normal ascii character case:
|
||||||
|
@ -85,8 +93,6 @@ BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection T
|
||||||
>>> mail_managers('hi','there')
|
>>> mail_managers('hi','there')
|
||||||
>>> len(mail.outbox)
|
>>> len(mail.outbox)
|
||||||
1
|
1
|
||||||
>>> settings.ADMINS = old_admins
|
|
||||||
>>> settings.MANAGERS = old_managers
|
|
||||||
|
|
||||||
# Make sure we can manually set the From header (#9214)
|
# Make sure we can manually set the From header (#9214)
|
||||||
|
|
||||||
|
@ -138,4 +144,217 @@ Content-Disposition: attachment; filename="an attachment.pdf"
|
||||||
JVBERi0xLjQuJS4uLg==
|
JVBERi0xLjQuJS4uLg==
|
||||||
...
|
...
|
||||||
|
|
||||||
|
# Make sure that the console backend writes to stdout by default
|
||||||
|
>>> connection = console.EmailBackend()
|
||||||
|
>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||||
|
>>> connection.send_messages([email])
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Subject: Subject
|
||||||
|
From: from@example.com
|
||||||
|
To: to@example.com
|
||||||
|
Date: ...
|
||||||
|
Message-ID: ...
|
||||||
|
|
||||||
|
Content
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
|
||||||
|
# Test that the console backend can be pointed at an arbitrary stream
|
||||||
|
>>> s = StringIO()
|
||||||
|
>>> connection = mail.get_connection('django.core.mail.backends.console', stream=s)
|
||||||
|
>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
|
||||||
|
1
|
||||||
|
>>> print s.getvalue()
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Subject: Subject
|
||||||
|
From: from@example.com
|
||||||
|
To: to@example.com
|
||||||
|
Date: ...
|
||||||
|
Message-ID: ...
|
||||||
|
|
||||||
|
Content
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Make sure that dummy backends returns correct number of sent messages
|
||||||
|
>>> connection = dummy.EmailBackend()
|
||||||
|
>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||||
|
>>> connection.send_messages([email, email, email])
|
||||||
|
3
|
||||||
|
|
||||||
|
# Make sure that locmen backend populates the outbox
|
||||||
|
>>> mail.outbox = []
|
||||||
|
>>> connection = locmem.EmailBackend()
|
||||||
|
>>> email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||||
|
>>> email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||||
|
>>> connection.send_messages([email1, email2])
|
||||||
|
2
|
||||||
|
>>> len(mail.outbox)
|
||||||
|
2
|
||||||
|
>>> mail.outbox[0].subject
|
||||||
|
'Subject'
|
||||||
|
>>> mail.outbox[1].subject
|
||||||
|
'Subject 2'
|
||||||
|
|
||||||
|
# Make sure that multiple locmem connections share mail.outbox
|
||||||
|
>>> mail.outbox = []
|
||||||
|
>>> connection1 = locmem.EmailBackend()
|
||||||
|
>>> connection2 = locmem.EmailBackend()
|
||||||
|
>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||||
|
>>> connection1.send_messages([email])
|
||||||
|
1
|
||||||
|
>>> connection2.send_messages([email])
|
||||||
|
1
|
||||||
|
>>> len(mail.outbox)
|
||||||
|
2
|
||||||
|
|
||||||
|
# Make sure that the file backend write to the right location
|
||||||
|
>>> tmp_dir = tempfile.mkdtemp()
|
||||||
|
>>> connection = filebased.EmailBackend(file_path=tmp_dir)
|
||||||
|
>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||||
|
>>> connection.send_messages([email])
|
||||||
|
1
|
||||||
|
>>> len(os.listdir(tmp_dir))
|
||||||
|
1
|
||||||
|
>>> print open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0])).read()
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Subject: Subject
|
||||||
|
From: from@example.com
|
||||||
|
To: to@example.com
|
||||||
|
Date: ...
|
||||||
|
Message-ID: ...
|
||||||
|
|
||||||
|
Content
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
>>> connection2 = filebased.EmailBackend(file_path=tmp_dir)
|
||||||
|
>>> connection2.send_messages([email])
|
||||||
|
1
|
||||||
|
>>> len(os.listdir(tmp_dir))
|
||||||
|
2
|
||||||
|
>>> connection.send_messages([email])
|
||||||
|
1
|
||||||
|
>>> len(os.listdir(tmp_dir))
|
||||||
|
2
|
||||||
|
>>> email.connection = filebased.EmailBackend(file_path=tmp_dir)
|
||||||
|
>>> connection_created = connection.open()
|
||||||
|
>>> num_sent = email.send()
|
||||||
|
>>> len(os.listdir(tmp_dir))
|
||||||
|
3
|
||||||
|
>>> num_sent = email.send()
|
||||||
|
>>> len(os.listdir(tmp_dir))
|
||||||
|
3
|
||||||
|
>>> connection.close()
|
||||||
|
>>> shutil.rmtree(tmp_dir)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Test custom backend defined in this suite.
|
||||||
|
>>> conn = mail.get_connection('regressiontests.mail.custombackend')
|
||||||
|
>>> hasattr(conn, 'test_outbox')
|
||||||
|
True
|
||||||
|
>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||||
|
>>> conn.send_messages([email])
|
||||||
|
1
|
||||||
|
>>> len(conn.test_outbox)
|
||||||
|
1
|
||||||
|
|
||||||
|
# Test backend argument of mail.get_connection()
|
||||||
|
>>> isinstance(mail.get_connection('django.core.mail.backends.smtp'), smtp.EmailBackend)
|
||||||
|
True
|
||||||
|
>>> isinstance(mail.get_connection('django.core.mail.backends.locmem'), locmem.EmailBackend)
|
||||||
|
True
|
||||||
|
>>> isinstance(mail.get_connection('django.core.mail.backends.dummy'), dummy.EmailBackend)
|
||||||
|
True
|
||||||
|
>>> isinstance(mail.get_connection('django.core.mail.backends.console'), console.EmailBackend)
|
||||||
|
True
|
||||||
|
>>> tmp_dir = tempfile.mkdtemp()
|
||||||
|
>>> isinstance(mail.get_connection('django.core.mail.backends.filebased', file_path=tmp_dir), filebased.EmailBackend)
|
||||||
|
True
|
||||||
|
>>> shutil.rmtree(tmp_dir)
|
||||||
|
>>> isinstance(mail.get_connection(), locmem.EmailBackend)
|
||||||
|
True
|
||||||
|
|
||||||
|
# Test connection argument of send_mail() et al
|
||||||
|
>>> connection = mail.get_connection('django.core.mail.backends.console')
|
||||||
|
>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Subject: Subject
|
||||||
|
From: from@example.com
|
||||||
|
To: to@example.com
|
||||||
|
Date: ...
|
||||||
|
Message-ID: ...
|
||||||
|
|
||||||
|
Content
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
|
||||||
|
>>> send_mass_mail([
|
||||||
|
... ('Subject1', 'Content1', 'from1@example.com', ['to1@example.com']),
|
||||||
|
... ('Subject2', 'Content2', 'from2@example.com', ['to2@example.com'])
|
||||||
|
... ], connection=connection)
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Subject: Subject1
|
||||||
|
From: from1@example.com
|
||||||
|
To: to1@example.com
|
||||||
|
Date: ...
|
||||||
|
Message-ID: ...
|
||||||
|
|
||||||
|
Content1
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Subject: Subject2
|
||||||
|
From: from2@example.com
|
||||||
|
To: to2@example.com
|
||||||
|
Date: ...
|
||||||
|
Message-ID: ...
|
||||||
|
|
||||||
|
Content2
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
2
|
||||||
|
|
||||||
|
>>> mail_admins('Subject', 'Content', connection=connection)
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Subject: [Django] Subject
|
||||||
|
From: root@localhost
|
||||||
|
To: nobody@example.com
|
||||||
|
Date: ...
|
||||||
|
Message-ID: ...
|
||||||
|
|
||||||
|
Content
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
>>> mail_managers('Subject', 'Content', connection=connection)
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Subject: [Django] Subject
|
||||||
|
From: root@localhost
|
||||||
|
To: nobody@example.com
|
||||||
|
Date: ...
|
||||||
|
Message-ID: ...
|
||||||
|
|
||||||
|
Content
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
>>> settings.ADMINS = old_admins
|
||||||
|
>>> settings.MANAGERS = old_managers
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue