Loading django/contrib/auth/forms.py +22 −11 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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', Loading @@ -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( Loading @@ -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, Loading @@ -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): Loading django/contrib/auth/tests/test_forms.py +30 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading docs/releases/1.8.txt +3 −0 Original line number Diff line number Diff line Loading @@ -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` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Loading docs/topics/auth/default.txt +20 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading
django/contrib/auth/forms.py +22 −11 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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', Loading @@ -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( Loading @@ -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, Loading @@ -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): Loading
django/contrib/auth/tests/test_forms.py +30 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading
docs/releases/1.8.txt +3 −0 Original line number Diff line number Diff line Loading @@ -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` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Loading
docs/topics/auth/default.txt +20 −0 Original line number Diff line number Diff line Loading @@ -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 Loading