Commit 6d88d47b authored by Justin Michalicek's avatar Justin Michalicek Committed by Tim Graham
Browse files

Fixed #20832 -- Enabled HTML password reset email

Added optional html_email_template_name parameter to password_reset view
and PasswordResetForm.
parent 94d7fed7
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -417,6 +417,7 @@ answer newbie questions, and generally made Django that much better:
    Zain Memon
    Christian Metts
    michal@plovarna.cz
    Justin Michalicek <jmichalicek@gmail.com>
    Slawek Mikula <slawek dot mikula at gmail dot com>
    Katie Miller <katie@sub50.com>
    Shawn Milochik <shawn@milochik.com>
+7 −2
Original line number Diff line number Diff line
@@ -230,7 +230,7 @@ class PasswordResetForm(forms.Form):
             subject_template_name='registration/password_reset_subject.txt',
             email_template_name='registration/password_reset_email.html',
             use_https=False, token_generator=default_token_generator,
             from_email=None, request=None):
             from_email=None, request=None, html_email_template_name=None):
        """
        Generates a one-use only link for resetting password and sends to the
        user.
@@ -263,7 +263,12 @@ class PasswordResetForm(forms.Form):
            # Email subject *must not* contain newlines
            subject = ''.join(subject.splitlines())
            email = loader.render_to_string(email_template_name, c)
            send_mail(subject, email, from_email, [user.email])

            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)


class SetPasswordForm(forms.Form):
+1 −0
Original line number Diff line number Diff line
<html><a href="{{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/">Link</a></html>
+55 −0
Original line number Diff line number Diff line
from __future__ import unicode_literals

import os
import re

from django import forms
from django.contrib.auth import get_user_model
@@ -452,6 +453,60 @@ class PasswordResetFormTest(TestCase):
        form.save()
        self.assertEqual(len(mail.outbox), 0)

    @override_settings(
        TEMPLATE_LOADERS=('django.template.loaders.filesystem.Loader',),
        TEMPLATE_DIRS=(
            os.path.join(os.path.dirname(upath(__file__)), 'templates'),
        ),
    )
    def test_save_plaintext_email(self):
        """
        Test the PasswordResetForm.save() method with no html_email_template_name
        parameter passed in.
        Test to ensure original behavior is unchanged after the parameter was added.
        """
        (user, username, email) = self.create_dummy_user()
        form = PasswordResetForm({"email": email})
        self.assertTrue(form.is_valid())
        form.save()
        self.assertEqual(len(mail.outbox), 1)
        message = mail.outbox[0].message()
        self.assertFalse(message.is_multipart())
        self.assertEqual(message.get_content_type(), 'text/plain')
        self.assertEqual(message.get('subject'), 'Custom password reset on example.com')
        self.assertEqual(len(mail.outbox[0].alternatives), 0)
        self.assertEqual(message.get_all('to'), [email])
        self.assertTrue(re.match(r'^http://example.com/reset/[\w+/-]', message.get_payload()))

    @override_settings(
        TEMPLATE_LOADERS=('django.template.loaders.filesystem.Loader',),
        TEMPLATE_DIRS=(
            os.path.join(os.path.dirname(upath(__file__)), 'templates'),
        ),
    )
    def test_save_html_email_template_name(self):
        """
        Test the PasswordResetFOrm.save() method with html_email_template_name
        parameter specified.
        Test to ensure that a multipart email is sent with both text/plain
        and text/html parts.
        """
        (user, username, email) = self.create_dummy_user()
        form = PasswordResetForm({"email": email})
        self.assertTrue(form.is_valid())
        form.save(html_email_template_name='registration/html_password_reset_email.html')
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(len(mail.outbox[0].alternatives), 1)
        message = mail.outbox[0].message()
        self.assertEqual(message.get('subject'), 'Custom password reset on example.com')
        self.assertEqual(len(message.get_payload()), 2)
        self.assertTrue(message.is_multipart())
        self.assertEqual(message.get_payload(0).get_content_type(), 'text/plain')
        self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
        self.assertEqual(message.get_all('to'), [email])
        self.assertTrue(re.match(r'^http://example.com/reset/[\w/-]+', message.get_payload(0).get_payload()))
        self.assertTrue(re.match(r'^<html><a href="http://example.com/reset/[\w/-]+/">Link</a></html>$', message.get_payload(1).get_payload()))


class ReadOnlyPasswordHashTest(TestCase):

+19 −0
Original line number Diff line number Diff line
@@ -128,6 +128,25 @@ class PasswordResetTest(AuthViewsTestCase):
        self.assertEqual(len(mail.outbox), 1)
        self.assertTrue("http://" in mail.outbox[0].body)
        self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
        # optional multipart text/html email has been added.  Make sure original,
        # default functionality is 100% the same
        self.assertFalse(mail.outbox[0].message().is_multipart())

    def test_html_mail_template(self):
        """
        A multipart email with text/plain and text/html is sent
        if the html_email_template parameter is passed to the view
        """
        response = self.client.post('/password_reset/html_email_template/', {'email': 'staffmember@example.com'})
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(mail.outbox), 1)
        message = mail.outbox[0].message()
        self.assertEqual(len(message.get_payload()), 2)
        self.assertTrue(message.is_multipart())
        self.assertEqual(message.get_payload(0).get_content_type(), 'text/plain')
        self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
        self.assertTrue('<html>' not in message.get_payload(0).get_payload())
        self.assertTrue('<html>' in message.get_payload(1).get_payload())

    def test_email_found_custom_from(self):
        "Email is sent if a valid email address is provided for password reset when a custom from_email is provided."
Loading