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

Credits for the initial patch go to ejucovy;
big thanks to Tim Graham for the review.
This commit is contained in:
Jorge C. Leitão 2014-05-09 08:48:41 +02:00 committed by Tim Graham
parent d8f19bb3b6
commit a00b78b1e2
4 changed files with 75 additions and 11 deletions

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
from collections import OrderedDict from collections import OrderedDict
from django import forms from django import forms
from django.core.mail import EmailMultiAlternatives
from django.forms.utils import flatatt from django.forms.utils import flatatt
from django.template import loader from django.template import loader
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
@ -230,6 +231,23 @@ class AuthenticationForm(forms.Form):
class PasswordResetForm(forms.Form): class PasswordResetForm(forms.Form):
email = forms.EmailField(label=_("Email"), max_length=254) 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, def save(self, domain_override=None,
subject_template_name='registration/password_reset_subject.txt', subject_template_name='registration/password_reset_subject.txt',
email_template_name='registration/password_reset_email.html', 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 Generates a one-use only link for resetting password and sends to the
user. user.
""" """
from django.core.mail import send_mail
UserModel = get_user_model() UserModel = get_user_model()
email = self.cleaned_data["email"] email = self.cleaned_data["email"]
active_users = UserModel._default_manager.filter( active_users = UserModel._default_manager.filter(
@ -255,7 +272,7 @@ class PasswordResetForm(forms.Form):
domain = current_site.domain domain = current_site.domain
else: else:
site_name = domain = domain_override site_name = domain = domain_override
c = { context = {
'email': user.email, 'email': user.email,
'domain': domain, 'domain': domain,
'site_name': site_name, 'site_name': site_name,
@ -264,16 +281,10 @@ class PasswordResetForm(forms.Form):
'token': token_generator.make_token(user), 'token': token_generator.make_token(user),
'protocol': 'https' if use_https else 'http', '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: self.send_mail(subject_template_name, email_template_name,
html_email = loader.render_to_string(html_email_template_name, c) context, from_email, user.email,
else: html_email_template_name=html_email_template_name)
html_email = None
send_mail(subject, email, from_email, [user.email], html_message=html_email)
class SetPasswordForm(forms.Form): class SetPasswordForm(forms.Form):

View File

@ -11,6 +11,7 @@ from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget) ReadOnlyPasswordHashField, ReadOnlyPasswordHashWidget)
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core import mail from django.core import mail
from django.core.mail import EmailMultiAlternatives
from django.forms.fields import Field, CharField from django.forms.fields import Field, CharField
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.utils.encoding import force_text from django.utils.encoding import force_text
@ -416,6 +417,35 @@ class PasswordResetFormTest(TestCase):
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, 'Custom password reset on example.com') 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): def test_preserve_username_case(self):
""" """
Preserve the case of the user name (before the @ in the email address) Preserve the case of the user name (before the @ in the email address)

View File

@ -41,6 +41,9 @@ Minor features
:meth:`~django.contrib.auth.models.User.has_perm` :meth:`~django.contrib.auth.models.User.has_perm`
and :meth:`~django.contrib.auth.models.User.has_module_perms` and :meth:`~django.contrib.auth.models.User.has_module_perms`
to short-circuit permission checking. 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` :mod:`django.contrib.formtools`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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 A form for generating and emailing a one-time use link to reset a
user's password. 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 .. class:: SetPasswordForm
A form that lets a user change his/her password without entering the old A form that lets a user change his/her password without entering the old