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>
|
||||
alang@bright-green.com
|
||||
Andi Albrecht <albrecht.andi@gmail.com>
|
||||
Marty Alchin <gulopine@gamemusic.org>
|
||||
Ahmad Alhashemi <trans@ahmadh.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_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'
|
||||
|
||||
|
|
|
@ -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 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)
|
|
@ -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.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('.')
|
||||
|
|
|
@ -22,6 +22,9 @@ their deprecation, as per the :ref:`Django deprecation policy
|
|||
* The old imports for CSRF functionality (``django.contrib.csrf.*``),
|
||||
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
|
||||
* ``django.views.defaults.shortcut()``. This function has been moved
|
||||
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
|
||||
: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
|
||||
|
||||
EMAIL_HOST
|
||||
|
|
|
@ -7,11 +7,13 @@ Sending e-mail
|
|||
.. module:: django.core.mail
|
||||
:synopsis: Helpers to easily send e-mail.
|
||||
|
||||
Although Python makes sending e-mail relatively easy via the `smtplib library`_,
|
||||
Django provides a couple of light wrappers over it, to make sending e-mail
|
||||
extra quick.
|
||||
Although Python makes sending e-mail relatively easy via the `smtplib
|
||||
library`_, Django provides a couple of light wrappers over it. These wrappers
|
||||
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
|
||||
|
||||
|
@ -25,11 +27,11 @@ In two lines::
|
|||
send_mail('Subject here', 'Here is the message.', 'from@example.com',
|
||||
['to@example.com'], fail_silently=False)
|
||||
|
||||
Mail is sent using the SMTP host and port specified in the :setting:`EMAIL_HOST`
|
||||
and :setting:`EMAIL_PORT` settings. The :setting:`EMAIL_HOST_USER` and
|
||||
:setting:`EMAIL_HOST_PASSWORD` settings, if set, are used to authenticate to the
|
||||
SMTP server, and the :setting:`EMAIL_USE_TLS` setting controls whether a secure
|
||||
connection is used.
|
||||
Mail is sent using the SMTP host and port specified in the
|
||||
:setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
|
||||
:setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
|
||||
set, are used to authenticate to the SMTP server, and the
|
||||
:setting:`EMAIL_USE_TLS` setting controls whether a secure connection is used.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -42,7 +44,7 @@ send_mail()
|
|||
The simplest way to send e-mail is using the function
|
||||
``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
|
||||
are required.
|
||||
|
@ -62,6 +64,10 @@ are required.
|
|||
* ``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
|
||||
``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
|
||||
|
||||
|
@ -71,26 +77,29 @@ send_mass_mail()
|
|||
``django.core.mail.send_mass_mail()`` is intended to handle mass e-mailing.
|
||||
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::
|
||||
|
||||
(subject, message, from_email, recipient_list)
|
||||
|
||||
``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.
|
||||
As in ``send_mail()``, recipients in the same ``recipient_list`` will all see
|
||||
the other addresses in the e-mail messages' "To:" field.
|
||||
As in :meth:`~django.core.mail.send_mail()`, recipients in the same
|
||||
``recipient_list`` will all see the other addresses in the e-mail messages'
|
||||
"To:" field.
|
||||
|
||||
send_mass_mail() vs. send_mail()
|
||||
--------------------------------
|
||||
|
||||
The main difference between ``send_mass_mail()`` and ``send_mail()`` is that
|
||||
``send_mail()`` opens a connection to the mail server each time it's executed,
|
||||
while ``send_mass_mail()`` uses a single connection for all of its messages.
|
||||
This makes ``send_mass_mail()`` slightly more efficient.
|
||||
The main difference between :meth:`~django.core.mail.send_mass_mail()` and
|
||||
:meth:`~django.core.mail.send_mail()` is that
|
||||
:meth:`~django.core.mail.send_mail()` opens a connection to the mail server
|
||||
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()
|
||||
=============
|
||||
|
@ -98,7 +107,7 @@ mail_admins()
|
|||
``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:
|
||||
|
||||
.. 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
|
||||
: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`
|
||||
setting. Here's the definition:
|
||||
|
||||
.. function:: mail_managers(subject, message, fail_silently=False)
|
||||
.. function:: mail_managers(subject, message, fail_silently=False, connection=None)
|
||||
|
||||
Examples
|
||||
========
|
||||
|
@ -145,7 +154,7 @@ scripts generate.
|
|||
The Django e-mail functions outlined above all protect against header injection
|
||||
by forbidding newlines in header values. If any ``subject``, ``from_email`` or
|
||||
``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,
|
||||
will not send the e-mail. It's your responsibility to validate all data before
|
||||
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:
|
||||
|
||||
The EmailMessage and SMTPConnection classes
|
||||
===========================================
|
||||
The EmailMessage class
|
||||
======================
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
|
||||
wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
|
||||
in ``django.core.mail``. If you ever need to customize the way Django sends
|
||||
e-mail, you can subclass these two classes to suit your needs.
|
||||
Django's :meth:`~django.core.mail.send_mail()` and
|
||||
:meth:`~django.core.mail.send_mass_mail()` functions are actually thin
|
||||
wrappers that make use of the :class:`~django.core.mail.EmailMessage` class.
|
||||
|
||||
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::
|
||||
Not all features of the ``EmailMessage`` class are available through the
|
||||
``send_mail()`` and related wrapper functions. If you wish to use advanced
|
||||
features, such as BCC'ed recipients, file attachments, or multi-part
|
||||
e-mail, you'll need to create ``EmailMessage`` instances directly.
|
||||
This is a design feature. :meth:`~django.core.mail.send_mail()` and
|
||||
related functions were originally the only interface Django provided.
|
||||
However, the list of parameters they accepted was slowly growing over
|
||||
time. It made sense to move to a more object-oriented design for e-mail
|
||||
messages and retain the original functions only for backwards
|
||||
compatibility.
|
||||
|
||||
This is a design feature. ``send_mail()`` and related functions were
|
||||
originally the only interface Django provided. However, the list of
|
||||
parameters they accepted was slowly growing over time. It made sense to
|
||||
move to a more object-oriented design for e-mail messages and retain the
|
||||
original functions only for backwards compatibility.
|
||||
:class:`~django.core.mail.EmailMessage` is responsible for creating the e-mail
|
||||
message itself. The :ref:`e-mail backend <topic-email-backends>` is then
|
||||
responsible for sending the e-mail.
|
||||
|
||||
In general, ``EmailMessage`` is responsible for creating the e-mail message
|
||||
itself. ``SMTPConnection`` is responsible for the network connection side of
|
||||
the operation. This means you can reuse the same connection (an
|
||||
``SMTPConnection`` instance) for multiple messages.
|
||||
For convenience, :class:`~django.core.mail.EmailMessage` provides a simple
|
||||
``send()`` method for sending a single email. If you need to send multiple
|
||||
messages, the email backend API :ref:`provides an alternative
|
||||
<topics-sending-multiple-emails>`.
|
||||
|
||||
EmailMessage Objects
|
||||
--------------------
|
||||
|
||||
.. class:: EmailMessage
|
||||
|
||||
The ``EmailMessage`` class is initialized with the following parameters (in
|
||||
the given order, if positional arguments are used). All parameters are
|
||||
optional and can be set at any time prior to calling the ``send()`` method.
|
||||
The :class:`~django.core.mail.EmailMessage` class is initialized with the
|
||||
following parameters (in the given order, if positional arguments are used).
|
||||
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.
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
new connection is created when ``send()`` is called.
|
||||
|
||||
|
@ -248,18 +263,18 @@ For example::
|
|||
|
||||
The class has the following methods:
|
||||
|
||||
* ``send(fail_silently=False)`` sends the message, using either
|
||||
the connection that is specified in the ``connection``
|
||||
attribute, or creating a new connection if none already
|
||||
exists. If the keyword argument ``fail_silently`` is ``True``,
|
||||
exceptions raised while sending the message will be quashed.
|
||||
* ``send(fail_silently=False)`` sends the message. If a connection was
|
||||
specified when the email was constructed, that connection will be used.
|
||||
Otherwise, an instance of the default backend will be instantiated and
|
||||
used. If the keyword argument ``fail_silently`` is ``True``, exceptions
|
||||
raised while sending the message will be quashed.
|
||||
|
||||
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
|
||||
subclass of Python's ``email.MIMEText.MIMEText`` class) or a
|
||||
``django.core.mail.SafeMIMEMultipart`` object holding the
|
||||
message to be sent. If you ever need to extend the ``EmailMessage`` class,
|
||||
you'll probably want to override this method to put the content you want
|
||||
into the MIME object.
|
||||
``django.core.mail.SafeMIMEMultipart`` object holding the message to be
|
||||
sent. If you ever need to extend the
|
||||
:class:`~django.core.mail.EmailMessage` class, you'll probably want to
|
||||
override this method to put the content you want into the MIME object.
|
||||
|
||||
* ``recipients()`` returns a list of all the recipients of the message,
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It can be useful to include multiple versions of the content in an e-mail;
|
||||
the classic example is to send both text and HTML versions of a message. With
|
||||
It can be useful to include multiple versions of the content in an e-mail; the
|
||||
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``
|
||||
class. This subclass of ``EmailMessage`` has an ``attach_alternative()`` method
|
||||
for including extra versions of the message body in the e-mail. All the other
|
||||
methods (including the class initialization) are inherited directly from
|
||||
``EmailMessage``.
|
||||
class. This subclass of :class:`~django.core.mail.EmailMessage` has an
|
||||
``attach_alternative()`` method for including extra versions of the message
|
||||
body in the e-mail. All the other methods (including the class initialization)
|
||||
are inherited directly from :class:`~django.core.mail.EmailMessage`.
|
||||
|
||||
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.send()
|
||||
|
||||
By default, the MIME type of the ``body`` parameter in an ``EmailMessage`` is
|
||||
``"text/plain"``. It is good practice to leave this alone, because it
|
||||
guarantees that any recipient will be able to read the e-mail, regardless of
|
||||
their mail client. However, if you are confident that your recipients can
|
||||
handle an alternative content type, you can use the ``content_subtype``
|
||||
attribute on the ``EmailMessage`` class to change the main content type. The
|
||||
major type will always be ``"text"``, but you can change it to the subtype. For
|
||||
example::
|
||||
By default, the MIME type of the ``body`` parameter in an
|
||||
:class:`~django.core.mail.EmailMessage` is ``"text/plain"``. It is good
|
||||
practice to leave this alone, because it guarantees that any recipient will be
|
||||
able to read the e-mail, regardless of their mail client. However, if you are
|
||||
confident that your recipients can handle an alternative content type, you can
|
||||
use the ``content_subtype`` attribute on the
|
||||
:class:`~django.core.mail.EmailMessage` class to change the main content type.
|
||||
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.content_subtype = "html" # Main content is now text/html
|
||||
msg.send()
|
||||
|
||||
SMTPConnection Objects
|
||||
----------------------
|
||||
.. _topic-email-backends:
|
||||
|
||||
.. class:: SMTPConnection
|
||||
E-Mail Backends
|
||||
===============
|
||||
|
||||
The ``SMTPConnection`` class is initialized with the host, port, username and
|
||||
password for the SMTP server. If you don't specify one or more of those
|
||||
options, they are read from your settings file.
|
||||
.. versionadded:: 1.2
|
||||
|
||||
If you're sending lots of messages at once, the ``send_messages()`` method of
|
||||
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::
|
||||
The actual sending of an e-mail is handled by the e-mail backend.
|
||||
|
||||
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()
|
||||
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
|
||||
----------------------
|
||||
======================
|
||||
|
||||
The are times when you do not want Django to send e-mails at all. For example,
|
||||
while developing a website, you probably don't want to send out thousands of
|
||||
|
@ -360,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
|
||||
correct content.
|
||||
|
||||
The easiest way to test your project's use of e-mail is to use a "dumb" e-mail
|
||||
server that receives the e-mails locally and displays them to the terminal,
|
||||
but does not actually send anything. Python has a built-in way to accomplish
|
||||
this with a single command::
|
||||
The easiest way to test your project's use of e-mail is to use the ``console``
|
||||
email backend. This backend redirects all email to stdout, allowing you to
|
||||
inspect the content of mail.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
the email body. You then only need to set the :setting:`EMAIL_HOST` and
|
||||
localhost. This server simply prints to standard output all e-mail headers and
|
||||
the e-mail body. You then only need to set the :setting:`EMAIL_HOST` and
|
||||
:setting:`EMAIL_PORT` accordingly, and you are set.
|
||||
|
||||
For more entailed testing and processing of e-mails locally, see the Python
|
||||
documentation on the `SMTP Server`_.
|
||||
For a more detailed discussion of testing and processing of e-mails locally,
|
||||
see the Python documentation on the `SMTP Server`_.
|
||||
|
||||
.. _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
|
||||
point of the redirect chain.
|
||||
|
||||
.. _topics-testing-email:
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
``django.core.mail.outbox``. This is a simple list of all
|
||||
: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 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
|
||||
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
|
||||
|
||||
r"""
|
||||
# Tests for the django.core.mail.
|
||||
|
||||
>>> import os
|
||||
>>> import shutil
|
||||
>>> import tempfile
|
||||
>>> from StringIO import StringIO
|
||||
>>> from django.conf import settings
|
||||
>>> from django.core import mail
|
||||
>>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
|
||||
>>> from django.core.mail 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
|
||||
|
||||
# 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')
|
||||
>>> len(mail.outbox)
|
||||
1
|
||||
>>> settings.ADMINS = old_admins
|
||||
>>> settings.MANAGERS = old_managers
|
||||
|
||||
# Make sure we can manually set the From header (#9214)
|
||||
|
||||
|
@ -138,4 +144,217 @@ Content-Disposition: attachment; filename="an attachment.pdf"
|
|||
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