Commit 4e0a2fe5 authored by SusanTan's avatar SusanTan Committed by Tim Graham
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.
parent 9eecb916
Loading
Loading
Loading
Loading
+18 −18
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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
+17 −0
Original line number Diff line number Diff line
@@ -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)