Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit aba5389

Browse files
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
1 parent 8287c27 commit aba5389

File tree

19 files changed

+1011
-287
lines changed

19 files changed

+1011
-287
lines changed

‎AUTHORS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ answer newbie questions, and generally made Django that much better:
2727

2828
ajs <adi@sieker.info>
2929
alang@bright-green.com
30+
Andi Albrecht <albrecht.andi@gmail.com>
3031
Marty Alchin <gulopine@gamemusic.org>
3132
Ahmad Alhashemi <trans@ahmadh.com>
3233
Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com>

‎django/conf/global_settings.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@
131131
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
132132
DATABASE_OPTIONS = {} # Set to empty dictionary for default.
133133

134+
# The email backend to use. For possible shortcuts see django.core.mail.
135+
# The default is to use the SMTP backend.
136+
# Third-party backends can be specified by providing a Python path
137+
# to a module that defines an EmailBackend class.
138+
EMAIL_BACKEND = 'django.core.mail.backends.smtp'
139+
134140
# Host for sending e-mail.
135141
EMAIL_HOST = 'localhost'
136142

‎django/core/mail/__init__.py‎

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
Tools for sending email.
3+
"""
4+
5+
from django.conf import settings
6+
from django.core.exceptions import ImproperlyConfigured
7+
from django.utils.importlib import import_module
8+
9+
# Imported for backwards compatibility, and for the sake
10+
# of a cleaner namespace. These symbols used to be in
11+
# django/core/mail.py before the introduction of email
12+
# backends and the subsequent reorganization (See #10355)
13+
from django.core.mail.utils import CachedDnsName, DNS_NAME
14+
from django.core.mail.message import \
15+
EmailMessage, EmailMultiAlternatives, \
16+
SafeMIMEText, SafeMIMEMultipart, \
17+
DEFAULT_ATTACHMENT_MIME_TYPE, make_msgid, \
18+
BadHeaderError, forbid_multi_line_headers
19+
from django.core.mail.backends.smtp import EmailBackend as _SMTPConnection
20+
21+
def get_connection(backend=None, fail_silently=False, **kwds):
22+
"""Load an e-mail backend and return an instance of it.
23+
24+
If backend is None (default) settings.EMAIL_BACKEND is used.
25+
26+
Both fail_silently and other keyword arguments are used in the
27+
constructor of the backend.
28+
"""
29+
path = backend or settings.EMAIL_BACKEND
30+
try:
31+
mod = import_module(path)
32+
except ImportError, e:
33+
raise ImproperlyConfigured(('Error importing email backend %s: "%s"'
34+
% (path, e)))
35+
try:
36+
cls = getattr(mod, 'EmailBackend')
37+
except AttributeError:
38+
raise ImproperlyConfigured(('Module "%s" does not define a '
39+
'"EmailBackend" class' % path))
40+
return cls(fail_silently=fail_silently, **kwds)
41+
42+
43+
def send_mail(subject, message, from_email, recipient_list,
44+
fail_silently=False, auth_user=None, auth_password=None,
45+
connection=None):
46+
"""
47+
Easy wrapper for sending a single message to a recipient list. All members
48+
of the recipient list will see the other recipients in the 'To' field.
49+
50+
If auth_user is None, the EMAIL_HOST_USER setting is used.
51+
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
52+
53+
Note: The API for this method is frozen. New code wanting to extend the
54+
functionality should use the EmailMessage class directly.
55+
"""
56+
connection = connection or get_connection(username=auth_user,
57+
password=auth_password,
58+
fail_silently=fail_silently)
59+
return EmailMessage(subject, message, from_email, recipient_list,
60+
connection=connection).send()
61+
62+
63+
def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
64+
auth_password=None, connection=None):
65+
"""
66+
Given a datatuple of (subject, message, from_email, recipient_list), sends
67+
each message to each recipient list. Returns the number of e-mails sent.
68+
69+
If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
70+
If auth_user and auth_password are set, they're used to log in.
71+
If auth_user is None, the EMAIL_HOST_USER setting is used.
72+
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
73+
74+
Note: The API for this method is frozen. New code wanting to extend the
75+
functionality should use the EmailMessage class directly.
76+
"""
77+
connection = connection or get_connection(username=auth_user,
78+
password=auth_password,
79+
fail_silently=fail_silently)
80+
messages = [EmailMessage(subject, message, sender, recipient)
81+
for subject, message, sender, recipient in datatuple]
82+
return connection.send_messages(messages)
83+
84+
85+
def mail_admins(subject, message, fail_silently=False, connection=None):
86+
"""Sends a message to the admins, as defined by the ADMINS setting."""
87+
if not settings.ADMINS:
88+
return
89+
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
90+
settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS],
91+
connection=connection).send(fail_silently=fail_silently)
92+
93+
94+
def mail_managers(subject, message, fail_silently=False, connection=None):
95+
"""Sends a message to the managers, as defined by the MANAGERS setting."""
96+
if not settings.MANAGERS:
97+
return
98+
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
99+
settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS],
100+
connection=connection).send(fail_silently=fail_silently)
101+
102+
103+
class SMTPConnection(_SMTPConnection):
104+
def __init__(self, *args, **kwds):
105+
import warnings
106+
warnings.warn(
107+
'mail.SMTPConnection is deprecated; use mail.get_connection() instead.',
108+
DeprecationWarning
109+
)
110+
super(SMTPConnection, self).__init__(*args, **kwds)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Mail backends shipped with Django.

‎django/core/mail/backends/base.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Base email backend class."""
2+
3+
class BaseEmailBackend(object):
4+
"""
5+
Base class for email backend implementations.
6+
7+
Subclasses must at least overwrite send_messages().
8+
"""
9+
def __init__(self, fail_silently=False, **kwargs):
10+
self.fail_silently = fail_silently
11+
12+
def open(self):
13+
"""Open a network connection.
14+
15+
This method can be overwritten by backend implementations to
16+
open a network connection.
17+
18+
It's up to the backend implementation to track the status of
19+
a network connection if it's needed by the backend.
20+
21+
This method can be called by applications to force a single
22+
network connection to be used when sending mails. See the
23+
send_messages() method of the SMTP backend for a reference
24+
implementation.
25+
26+
The default implementation does nothing.
27+
"""
28+
pass
29+
30+
def close(self):
31+
"""Close a network connection."""
32+
pass
33+
34+
def send_messages(self, email_messages):
35+
"""
36+
Sends one or more EmailMessage objects and returns the number of email
37+
messages sent.
38+
"""
39+
raise NotImplementedError

‎django/core/mail/backends/console.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Email backend that writes messages to console instead of sending them.
3+
"""
4+
import sys
5+
import threading
6+
7+
from django.core.mail.backends.base import BaseEmailBackend
8+
9+
class EmailBackend(BaseEmailBackend):
10+
def __init__(self, *args, **kwargs):
11+
self.stream = kwargs.pop('stream', sys.stdout)
12+
self._lock = threading.RLock()
13+
super(EmailBackend, self).__init__(*args, **kwargs)
14+
15+
def send_messages(self, email_messages):
16+
"""Write all messages to the stream in a thread-safe way."""
17+
if not email_messages:
18+
return
19+
self._lock.acquire()
20+
try:
21+
stream_created = self.open()
22+
for message in email_messages:
23+
self.stream.write('%s\n' % message.message().as_string())
24+
self.stream.write('-'*79)
25+
self.stream.write('\n')
26+
self.stream.flush() # flush after each message
27+
if stream_created:
28+
self.close()
29+
except:
30+
if not self.fail_silently:
31+
raise
32+
finally:
33+
self._lock.release()
34+
return len(email_messages)

‎django/core/mail/backends/dummy.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""
2+
Dummy email backend that does nothing.
3+
"""
4+
5+
from django.core.mail.backends.base import BaseEmailBackend
6+
7+
class EmailBackend(BaseEmailBackend):
8+
def send_messages(self, email_messages):
9+
return len(email_messages)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Email backend that writes messages to a file."""
2+
3+
import datetime
4+
import os
5+
6+
from django.conf import settings
7+
from django.core.exceptions import ImproperlyConfigured
8+
from django.core.mail.backends.console import EmailBackend as ConsoleEmailBackend
9+
10+
class EmailBackend(ConsoleEmailBackend):
11+
def __init__(self, *args, **kwargs):
12+
self._fname = None
13+
if 'file_path' in kwargs:
14+
self.file_path = kwargs.pop('file_path')
15+
else:
16+
self.file_path = getattr(settings, 'EMAIL_FILE_PATH',None)
17+
# Make sure self.file_path is a string.
18+
if not isinstance(self.file_path, basestring):
19+
raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.file_path)
20+
self.file_path = os.path.abspath(self.file_path)
21+
# Make sure that self.file_path is an directory if it exists.
22+
if os.path.exists(self.file_path) and not os.path.isdir(self.file_path):
23+
raise ImproperlyConfigured('Path for saving email messages exists, but is not a directory: %s' % self.file_path)
24+
# Try to create it, if it not exists.
25+
elif not os.path.exists(self.file_path):
26+
try:
27+
os.makedirs(self.file_path)
28+
except OSError, err:
29+
raise ImproperlyConfigured('Could not create directory for saving email messages: %s (%s)' % (self.file_path, err))
30+
# Make sure that self.file_path is writable.
31+
if not os.access(self.file_path, os.W_OK):
32+
raise ImproperlyConfigured('Could not write to directory: %s' % self.file_path)
33+
# Finally, call super().
34+
# Since we're using the console-based backend as a base,
35+
# force the stream to be None, so we don't default to stdout
36+
kwargs['stream'] = None
37+
super(EmailBackend, self).__init__(*args, **kwargs)
38+
39+
def _get_filename(self):
40+
"""Return a unique file name."""
41+
if self._fname is None:
42+
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
43+
fname = "%s-%s.log" % (timestamp, abs(id(self)))
44+
self._fname = os.path.join(self.file_path, fname)
45+
return self._fname
46+
47+
def open(self):
48+
if self.stream is None:
49+
self.stream = open(self._get_filename(), 'a')
50+
return True
51+
return False
52+
53+
def close(self):
54+
try:
55+
if self.stream is not None:
56+
self.stream.close()
57+
finally:
58+
self.stream = None
59+

‎django/core/mail/backends/locmem.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
Backend for test environment.
3+
"""
4+
5+
from django.core import mail
6+
from django.core.mail.backends.base import BaseEmailBackend
7+
8+
class EmailBackend(BaseEmailBackend):
9+
"""A email backend for use during test sessions.
10+
11+
The test connection stores email messages in a dummy outbox,
12+
rather than sending them out on the wire.
13+
14+
The dummy outbox is accessible through the outbox instance attribute.
15+
"""
16+
def __init__(self, *args, **kwargs):
17+
super(EmailBackend, self).__init__(*args, **kwargs)
18+
if not hasattr(mail, 'outbox'):
19+
mail.outbox = []
20+
21+
def send_messages(self, messages):
22+
"""Redirect messages to the dummy outbox"""
23+
mail.outbox.extend(messages)
24+
return len(messages)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /