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.
"""
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)
self.host = host or settings.EMAIL_HOST
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.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.timeout = timeout
if self.use_ssl and self.use_tls:
raise ValueError(
"EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
@ -38,24 +40,22 @@ class EmailBackend(BaseEmailBackend):
if self.connection:
# Nothing to do if the connection is already open.
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.
# For performance, we use the cached FQDN for local_hostname.
if self.use_ssl:
self.connection = smtplib.SMTP_SSL(self.host, self.port,
local_hostname=DNS_NAME.get_fqdn())
else:
self.connection = smtplib.SMTP(self.host, self.port,
local_hostname=DNS_NAME.get_fqdn())
connection_params = {'local_hostname': DNS_NAME.get_fqdn()}
if self.timeout is not None:
connection_params['timeout'] = self.timeout
try:
self.connection = connection_class(self.host, self.port, **connection_params)
# TLS/SSL are mutually exclusive, so only attempt TLS over
# non-secure connections.
if self.use_tls:
if not self.use_ssl and 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 smtplib.SMTPException:
if not self.fail_silently:
raise

View File

@ -248,6 +248,8 @@ Email
* :func:`~django.core.mail.send_mail` now accepts an ``html_message``
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
^^^^^^^^^^^^

View File

@ -424,6 +424,8 @@ can :ref:`write your own email backend <topic-custom-email-backend>`.
SMTP backend
~~~~~~~~~~~~
.. class:: backends.smtp.EmailBackend([host=None, port=None, username=None, password=None, use_tls=None, fail_silently=False, use_ssl=None, timeout=None, **kwargs])
This is the default backend. Email will be sent through a SMTP server.
The server address and authentication credentials are set in the
:setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
@ -435,6 +437,26 @@ want to specify it explicitly, put the following in your settings::
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:
Console backend

View File

@ -933,3 +933,20 @@ class SMTPBackendTests(BaseEmailBackendTests, SimpleTestCase):
backend = smtp.EmailBackend()
self.assertTrue(backend.use_ssl)
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)