From 4e0a2fe59c8b9c32c2f3111474354356474128a8 Mon Sep 17 00:00:00 2001 From: SusanTan Date: Tue, 15 Oct 2013 16:18:06 +0100 Subject: [PATCH] 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. --- django/core/mail/backends/smtp.py | 36 ++++++++++++++--------------- docs/releases/1.7.txt | 2 ++ docs/topics/email.txt | 38 ++++++++++++++++++++++++------- tests/mail/tests.py | 17 ++++++++++++++ 4 files changed, 67 insertions(+), 26 deletions(-) diff --git a/django/core/mail/backends/smtp.py b/django/core/mail/backends/smtp.py index 49145e1abc..df47d4e823 100644 --- a/django/core/mail/backends/smtp.py +++ b/django/core/mail/backends/smtp.py @@ -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 + + 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) - return True + 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 diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 9312aeaecb..09014980d5 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -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 ^^^^^^^^^^^^ diff --git a/docs/topics/email.txt b/docs/topics/email.txt index bfb3765d15..06815c3886 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -424,16 +424,38 @@ can :ref:`write your own 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 + 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: diff --git a/tests/mail/tests.py b/tests/mail/tests.py index f169457d1d..a380ecf3d7 100644 --- a/tests/mail/tests.py +++ b/tests/mail/tests.py @@ -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)