Browse Source

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.
SusanTan 11 years ago
parent
commit
4e0a2fe59c
4 changed files 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

+ 18 - 18
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

+ 2 - 0
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
 ^^^^^^^^^^^^

+ 30 - 8
docs/topics/email.txt

@@ -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
+    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:
 

+ 17 - 0
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)