浏览代码

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

Credits for the initial patch go to ejucovy;
big thanks to Tim Graham for the review.
Jorge C. Leitão 11 年之前
父节点
当前提交
a00b78b1e2
共有 4 个文件被更改,包括 75 次插入11 次删除
  1. 22 11
      django/contrib/auth/forms.py
  2. 30 0
      django/contrib/auth/tests/test_forms.py
  3. 3 0
      docs/releases/1.8.txt
  4. 20 0
      docs/topics/auth/default.txt

+ 22 - 11
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):

+ 30 - 0
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)

+ 3 - 0
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`
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

+ 20 - 0
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