Fixed #20832 -- Enabled HTML password reset email
Added optional html_email_template_name parameter to password_reset view and PasswordResetForm.
This commit is contained in:
parent
94d7fed775
commit
6d88d47be6
1
AUTHORS
1
AUTHORS
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<html><a href="{{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/">Link</a></html>
|
|
@ -1,6 +1,7 @@
|
|||
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):
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -67,6 +67,7 @@ urlpatterns = urlpatterns + patterns('',
|
|||
(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
|
||||
(r'^password_reset/custom_redirect/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='/custom/')),
|
||||
(r'^password_reset/custom_redirect/named/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='password_reset')),
|
||||
(r'^password_reset/html_email_template/$', 'django.contrib.auth.views.password_reset', dict(html_email_template_name='registration/html_password_reset_email.html')),
|
||||
(r'^reset/custom/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
|
||||
'django.contrib.auth.views.password_reset_confirm',
|
||||
dict(post_reset_redirect='/custom/')),
|
||||
|
|
|
@ -140,7 +140,8 @@ def password_reset(request, is_admin_site=False,
|
|||
post_reset_redirect=None,
|
||||
from_email=None,
|
||||
current_app=None,
|
||||
extra_context=None):
|
||||
extra_context=None,
|
||||
html_email_template_name=None):
|
||||
if post_reset_redirect is None:
|
||||
post_reset_redirect = reverse('password_reset_done')
|
||||
else:
|
||||
|
@ -155,6 +156,7 @@ def password_reset(request, is_admin_site=False,
|
|||
'email_template_name': email_template_name,
|
||||
'subject_template_name': subject_template_name,
|
||||
'request': request,
|
||||
'html_email_template_name': html_email_template_name,
|
||||
}
|
||||
if is_admin_site:
|
||||
opts = dict(opts, domain_override=request.get_host())
|
||||
|
|
|
@ -118,6 +118,10 @@ Minor features
|
|||
customize the value of :attr:`ModelAdmin.fields
|
||||
<django.contrib.admin.ModelAdmin.fields>`.
|
||||
|
||||
* :func:`django.contrib.auth.views.password_reset` takes an optional
|
||||
``html_email_template_name`` parameter used to send a multipart HTML email
|
||||
for password resets.
|
||||
|
||||
Backwards incompatible changes in 1.7
|
||||
=====================================
|
||||
|
||||
|
|
|
@ -793,7 +793,7 @@ patterns.
|
|||
* ``extra_context``: A dictionary of context data that will be added to the
|
||||
default context data passed to the template.
|
||||
|
||||
.. function:: password_reset(request[, is_admin_site, template_name, email_template_name, password_reset_form, token_generator, post_reset_redirect, from_email, current_app, extra_context])
|
||||
.. function:: password_reset(request[, is_admin_site, template_name, email_template_name, password_reset_form, token_generator, post_reset_redirect, from_email, current_app, extra_context, html_email_template_name])
|
||||
|
||||
Allows a user to reset their password by generating a one-time use link
|
||||
that can be used to reset the password, and sending that link to the
|
||||
|
@ -856,6 +856,14 @@ patterns.
|
|||
* ``extra_context``: A dictionary of context data that will be added to the
|
||||
default context data passed to the template.
|
||||
|
||||
* ``html_email_template_name``: The full name of a template to use
|
||||
for generating a ``text/html`` multipart email with the password reset
|
||||
link. By default, HTML email is not sent.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
``html_email_template_name`` was added.
|
||||
|
||||
**Template context:**
|
||||
|
||||
* ``form``: The form (see ``password_reset_form`` above) for resetting
|
||||
|
|
Loading…
Reference in New Issue