Permalink
Browse files

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.
  • Loading branch information...
1 parent 9eecb91 commit 4e0a2fe59c8b9c32c2f3111474354356474128a8 @ArcTanSusan ArcTanSusan committed with timgraham Oct 15, 2013
Showing with 67 additions and 26 deletions.
  1. +18 −18 django/core/mail/backends/smtp.py
  2. +2 −0 docs/releases/1.7.txt
  3. +30 −8 docs/topics/email.txt
  4. +17 −0 tests/mail/tests.py
@@ -15,14 +15,16 @@ 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
self.username = settings.EMAIL_HOST_USER if username is None else username
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 @@ def open(self):
if self.connection:
# Nothing to do if the connection is already open.
return False
+
+ 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.
+ connection_params = {'local_hostname': DNS_NAME.get_fqdn()}
+ if self.timeout is not None:
+ connection_params['timeout'] = self.timeout
try:
- # 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())
- # TLS/SSL are mutually exclusive, so only attempt TLS over
- # non-secure connections.
- 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)
@claudep

claudep Oct 25, 2013

Member

The login() call has been inadvertantly deleted. I'll fix that and add a test for it.

- return True
@mjtamlyn

mjtamlyn Jan 12, 2014

Member

This line was also inadvertently deleted, and is exceedingly important as without it connections are never closed. Readded and tested in 9d2c5b0.

+ self.connection = connection_class(self.host, self.port, **connection_params)
+
+ # TLS/SSL are mutually exclusive, so only attempt TLS over
+ # non-secure connections.
+ if not self.use_ssl and self.use_tls:
+ self.connection.ehlo()
+ self.connection.starttls()
+ self.connection.ehlo()
except smtplib.SMTPException:
if not self.fail_silently:
raise
View
@@ -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
@@ -424,16 +424,38 @@ can :ref:`write your own email backend <topic-custom-email-backend>`.
SMTP backend
~~~~~~~~~~~~
-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`,
-:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and
-:setting:`EMAIL_USE_SSL` settings in your settings file.
+.. 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 SMTP backend is the default configuration inherited by Django. If you
-want to specify it explicitly, put the following in your settings::
+ 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`,
+ :setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and
+ :setting:`EMAIL_USE_SSL` settings in your settings file.
- EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+ 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'
+
+ Here is an attribute which doesn't have a corresponding settting like the
@ArcTanSusan

ArcTanSusan Oct 25, 2013

Contributor

Typo in settting.

@timgraham

timgraham Oct 25, 2013

Owner

Fixed in 43cfc65, thanks.

+ 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:
View
@@ -933,3 +933,20 @@ def test_email_ssl_attempts_ssl_connection(self):
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)

0 comments on commit 4e0a2fe

Please sign in to comment.