Commit a00b78b1 authored by Jorge C. Leitão's avatar Jorge C. Leitão Committed by Tim Graham
Browse files

Fixed #17431 -- Added send_mail() method to PasswordResetForm.

Credits for the initial patch go to ejucovy;
big thanks to Tim Graham for the review.
parent d8f19bb3
Loading
Loading
Loading
Loading
+22 −11
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from collections import OrderedDict

from django import forms
from django.core.mail import EmailMultiAlternatives
from django.forms.utils import flatatt
from django.template import loader
from django.utils.encoding import force_bytes
@@ -230,6 +231,23 @@ class AuthenticationForm(forms.Form):
class PasswordResetForm(forms.Form):
    email = forms.EmailField(label=_("Email"), max_length=254)

    def send_mail(self, subject_template_name, email_template_name,
                  context, from_email, to_email, html_email_template_name=None):
        """
        Sends a django.core.mail.EmailMultiAlternatives to `to_email`.
        """
        subject = loader.render_to_string(subject_template_name, context)
        # Email subject *must not* contain newlines
        subject = ''.join(subject.splitlines())
        body = loader.render_to_string(email_template_name, context)

        email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
        if html_email_template_name is not None:
            html_email = loader.render_to_string(html_email_template_name, context)
            email_message.attach_alternative(html_email, 'text/html')

        email_message.send()

    def save(self, domain_override=None,
             subject_template_name='registration/password_reset_subject.txt',
             email_template_name='registration/password_reset_email.html',
@@ -239,7 +257,6 @@ class PasswordResetForm(forms.Form):
        Generates a one-use only link for resetting password and sends to the
        user.
        """
        from django.core.mail import send_mail
        UserModel = get_user_model()
        email = self.cleaned_data["email"]
        active_users = UserModel._default_manager.filter(
@@ -255,7 +272,7 @@ class PasswordResetForm(forms.Form):
                domain = current_site.domain
            else:
                site_name = domain = domain_override
            c = {
            context = {
                'email': user.email,
                'domain': domain,
                'site_name': site_name,
@@ -264,16 +281,10 @@ class PasswordResetForm(forms.Form):
                'token': token_generator.make_token(user),
                'protocol': 'https' if use_https else 'http',
            }
            subject = loader.render_to_string(subject_template_name, c)
            # Email subject *must not* contain newlines
            subject = ''.join(subject.splitlines())
            email = loader.render_to_string(email_template_name, c)

            if html_email_template_name:
                html_email = loader.render_to_string(html_email_template_name, c)
            else:
                html_email = None
            send_mail(subject, email, from_email, [user.email], html_message=html_email)
            self.send_mail(subject_template_name, email_template_name,
                           context, from_email, user.email,
                           html_email_template_name=html_email_template_name)


class SetPasswordForm(forms.Form):
+30 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
    ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget)
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core import mail
from django.core.mail import EmailMultiAlternatives
from django.forms.fields import Field, CharField
from django.test import TestCase, override_settings
from django.utils.encoding import force_text
@@ -416,6 +417,35 @@ class PasswordResetFormTest(TestCase):
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, 'Custom password reset on example.com')

    def test_custom_email_constructor(self):
        template_path = os.path.join(os.path.dirname(__file__), 'templates')
        with self.settings(TEMPLATE_DIRS=(template_path,)):
            data = {'email': 'testclient@example.com'}

            class CustomEmailPasswordResetForm(PasswordResetForm):
                def send_mail(self, subject_template_name, email_template_name,
                              context, from_email, to_email,
                              html_email_template_name=None):
                    EmailMultiAlternatives(
                        "Forgot your password?",
                        "Sorry to hear you forgot your password.",
                        None, [to_email],
                        ['site_monitor@example.com'],
                        headers={'Reply-To': 'webmaster@example.com'},
                        alternatives=[("Really sorry to hear you forgot your password.",
                                       "text/html")]).send()

            form = CustomEmailPasswordResetForm(data)
            self.assertTrue(form.is_valid())
            # Since we're not providing a request object, we must provide a
            # domain_override to prevent the save operation from failing in the
            # potential case where contrib.sites is not installed. Refs #16412.
            form.save(domain_override='example.com')
            self.assertEqual(len(mail.outbox), 1)
            self.assertEqual(mail.outbox[0].subject, 'Forgot your password?')
            self.assertEqual(mail.outbox[0].bcc, ['site_monitor@example.com'])
            self.assertEqual(mail.outbox[0].content_subtype, "plain")

    def test_preserve_username_case(self):
        """
        Preserve the case of the user name (before the @ in the email address)
+3 −0
Original line number Diff line number Diff line
@@ -41,6 +41,9 @@ Minor features
  :meth:`~django.contrib.auth.models.User.has_perm`
  and :meth:`~django.contrib.auth.models.User.has_module_perms`
  to short-circuit permission checking.
* :class:`~django.contrib.auth.forms.PasswordResetForm` now
  has a method :meth:`~django.contrib.auth.forms.PasswordResetForm.send_email`
  that can be overridden to customize the mail to be sent.

:mod:`django.contrib.formtools`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+20 −0
Original line number Diff line number Diff line
@@ -1205,6 +1205,26 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
    A form for generating and emailing a one-time use link to reset a
    user's password.

    .. method:: send_email(subject_template_name, email_template_name, context, from_email, to_email, [html_email_template_name=None])

        .. versionadded:: 1.8

        Uses the arguments to send an ``EmailMultiAlternatives``.
        Can be overridden to customize how the email is sent to the user.

        :param subject_template_name: the template for the subject.
        :param email_template_name: the template for the email body.
        :param context: context passed to the ``subject_template``, ``email_template``,
            and ``html_email_template`` (if it is not ``None``).
        :param from_email: the sender's email.
        :param to_email: the email of the requester.
        :param html_email_template_name: the template for the HTML body;
            defaults to ``None``, in which case a plain text email is sent.

        By default, ``save()`` populates the ``context`` with the
        same variables that :func:`~django.contrib.auth.views.password_reset`
        passes to its email context.

.. class:: SetPasswordForm

    A form that lets a user change his/her password without entering the old