Fixed #21271 -- Added timeout parameter to SMTP EmailBackend.

Thanks Tobias McNulty and Tim Graham for discussions and code review.
Thanks Andre Cruz the suggestion and initial patch.
This commit is contained in:
SusanTan 2013-10-15 16:18:06 +01:00 committed by Tim Graham
parent 9eecb91695
commit 4e0a2fe59c
4 changed files with 67 additions and 26 deletions

View File

@ -15,7 +15,8 @@ class EmailBackend(BaseEmailBackend):
A wrapper that manages the SMTP network connection. A wrapper that manages the SMTP network connection.
""" """
def __init__(self, host=None, port=None, username=None, password=None, def __init__(self, host=None, port=None, username=None, password=None,
use_tls=None, fail_silently=False, use_ssl=None, **kwargs): use_tls=None, fail_silently=False, use_ssl=None, timeout=None,
**kwargs):
super(EmailBackend, self).__init__(fail_silently=fail_silently) super(EmailBackend, self).__init__(fail_silently=fail_silently)
self.host = host or settings.EMAIL_HOST self.host = host or settings.EMAIL_HOST
self.port = port or settings.EMAIL_PORT self.port = port or settings.EMAIL_PORT
@ -23,6 +24,7 @@ class EmailBackend(BaseEmailBackend):
self.password = settings.EMAIL_HOST_PASSWORD if password is None else password self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
self.timeout = timeout
if self.use_ssl and self.use_tls: if self.use_ssl and self.use_tls:
raise ValueError( raise ValueError(
"EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set " "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
@ -38,24 +40,22 @@ class EmailBackend(BaseEmailBackend):
if self.connection: if self.connection:
# Nothing to do if the connection is already open. # Nothing to do if the connection is already open.
return False return False
try:
connection_class = smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP
# If local_hostname is not specified, socket.getfqdn() gets used. # If local_hostname is not specified, socket.getfqdn() gets used.
# For performance, we use the cached FQDN for local_hostname. # For performance, we use the cached FQDN for local_hostname.
if self.use_ssl: connection_params = {'local_hostname': DNS_NAME.get_fqdn()}
self.connection = smtplib.SMTP_SSL(self.host, self.port, if self.timeout is not None:
local_hostname=DNS_NAME.get_fqdn()) connection_params['timeout'] = self.timeout
else: try:
self.connection = smtplib.SMTP(self.host, self.port, self.connection = connection_class(self.host, self.port, **connection_params)
local_hostname=DNS_NAME.get_fqdn())
# TLS/SSL are mutually exclusive, so only attempt TLS over # TLS/SSL are mutually exclusive, so only attempt TLS over
# non-secure connections. # non-secure connections.
if self.use_tls: if not self.use_ssl and self.use_tls:
self.connection.ehlo() self.connection.ehlo()
self.connection.starttls() self.connection.starttls()
self.connection.ehlo() self.connection.ehlo()
if self.username and self.password:
self.connection.login(self.username, self.password)
return True
except smtplib.SMTPException: except smtplib.SMTPException:
if not self.fail_silently: if not self.fail_silently:
raise raise

View File

@ -248,6 +248,8 @@ Email
* :func:`~django.core.mail.send_mail` now accepts an ``html_message`` * :func:`~django.core.mail.send_mail` now accepts an ``html_message``
parameter for sending a multipart ``text/plain`` and ``text/html`` email. parameter for sending a multipart ``text/plain`` and ``text/html`` email.
* The SMTP :class:`~django.core.mail.backends.smtp.EmailBackend` now accepts a
:attr:`~django.core.mail.backends.smtp.EmailBackend.timeout` parameter.
File Uploads File Uploads
^^^^^^^^^^^^ ^^^^^^^^^^^^

View File

@ -424,17 +424,39 @@ can :ref:`write your own email backend <topic-custom-email-backend>`.
SMTP backend SMTP backend
~~~~~~~~~~~~ ~~~~~~~~~~~~
This is the default backend. Email will be sent through a SMTP server. .. class:: backends.smtp.EmailBackend([host=None, port=None, username=None, password=None, use_tls=None, fail_silently=False, use_ssl=None, timeout=None, **kwargs])
The server address and authentication credentials are set in the
:setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and
:setting:`EMAIL_USE_SSL` settings in your settings file.
The SMTP backend is the default configuration inherited by Django. If you This is the default backend. Email will be sent through a SMTP server.
want to specify it explicitly, put the following in your settings:: The server address and authentication credentials are set in the
:setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and
:setting:`EMAIL_USE_SSL` 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.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
Here is an attribute which doesn't have a corresponding settting like the
others described above:
.. attribute:: timeout
.. versionadded:: 1.7
This backend contains a ``timeout`` parameter, which can be set with
the following sample code::
from django.core.mail.backends import smtp
class MyEmailBackend(smtp.EmailBackend):
def __init__(self, *args, **kwargs):
kwargs.setdefault('timeout', 42)
super(MyEmailBackend, self).__init__(*args, **kwargs)
Then point the :setting:`EMAIL_BACKEND` setting at your custom backend as
described above.
.. _topic-email-console-backend: .. _topic-email-console-backend:
Console backend Console backend

View File

@ -933,3 +933,20 @@ class SMTPBackendTests(BaseEmailBackendTests, SimpleTestCase):
backend = smtp.EmailBackend() backend = smtp.EmailBackend()
self.assertTrue(backend.use_ssl) self.assertTrue(backend.use_ssl)
self.assertRaises(SSLError, backend.open) self.assertRaises(SSLError, backend.open)
def test_connection_timeout_default(self):
"""Test that the connection's timeout value is None by default."""
connection = mail.get_connection('django.core.mail.backends.smtp.EmailBackend')
self.assertEqual(connection.timeout, None)
def test_connection_timeout_custom(self):
"""Test that the timeout parameter can be customized."""
class MyEmailBackend(smtp.EmailBackend):
def __init__(self, *args, **kwargs):
kwargs.setdefault('timeout', 42)
super(MyEmailBackend, self).__init__(*args, **kwargs)
myemailbackend = MyEmailBackend()
myemailbackend.open()
self.assertEqual(myemailbackend.timeout, 42)
self.assertEqual(myemailbackend.connection.timeout, 42)