summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJorge C. Leitão <jorgecarleitao@gmail.com>2014-05-09 08:48:41 +0200
committerTim Graham <timograham@gmail.com>2014-06-10 14:00:52 -0400
commita00b78b1e2ac9bf271d55c1799138a27f5e0d03e (patch)
tree88c7325c2f023e3b8aa3f219c42d478720b40a47
parentd8f19bb3b6f858bef499fdab41948a5a5e8d55aa (diff)
Fixed #17431 -- Added send_mail() method to PasswordResetForm.
Credits for the initial patch go to ejucovy; big thanks to Tim Graham for the review.
-rw-r--r--django/contrib/auth/forms.py33
-rw-r--r--django/contrib/auth/tests/test_forms.py30
-rw-r--r--docs/releases/1.8.txt3
-rw-r--r--docs/topics/auth/default.txt20
4 files changed, 75 insertions, 11 deletions
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index f1a0e095f4..2ff75b268c 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -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):
diff --git a/django/contrib/auth/tests/test_forms.py b/django/contrib/auth/tests/test_forms.py
index 2d02f3a270..1f7f3946d8 100644
--- a/django/contrib/auth/tests/test_forms.py
+++ b/django/contrib/auth/tests/test_forms.py
@@ -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)
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index 26b9d37a88..426e03b87f 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -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`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index 3e7a49662f..b639af3b27 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -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