summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlevental <levental@fh-brandenburg.de>2016-09-19 14:55:18 +0200
committerTim Graham <timograham@gmail.com>2016-09-27 11:59:00 -0400
commit617e36dc1ede2a311dfd03f18432b31cbfe4c0f7 (patch)
tree38fd43fc044ff923321b444c292686798dfb17bd
parentf7e91cac689b28fc32ca52cdeac258ec0d58b4fc (diff)
Fixed #20705 -- Allowed using PasswordResetForm with user models with an email field not named 'email'.
-rw-r--r--django/contrib/auth/base_user.py7
-rw-r--r--django/contrib/auth/forms.py11
-rw-r--r--django/contrib/auth/models.py1
-rw-r--r--docs/releases/1.11.txt5
-rw-r--r--docs/topics/auth/customizing.txt23
-rw-r--r--tests/auth_tests/models/with_custom_email_field.py23
-rw-r--r--tests/auth_tests/test_forms.py12
-rw-r--r--tests/auth_tests/test_models.py11
8 files changed, 86 insertions, 7 deletions
diff --git a/django/contrib/auth/base_user.py b/django/contrib/auth/base_user.py
index de069659e1..9ad5cde87f 100644
--- a/django/contrib/auth/base_user.py
+++ b/django/contrib/auth/base_user.py
@@ -138,5 +138,12 @@ class AbstractBaseUser(models.Model):
return salted_hmac(key_salt, self.password).hexdigest()
@classmethod
+ def get_email_field_name(cls):
+ try:
+ return cls.EMAIL_FIELD
+ except AttributeError:
+ return 'email'
+
+ @classmethod
def normalize_username(cls, username):
return unicodedata.normalize('NFKC', force_text(username))
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 7a6a8d74aa..dbdc08db64 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -254,8 +254,11 @@ class PasswordResetForm(forms.Form):
that prevent inactive users and users with unusable passwords from
resetting their password.
"""
- active_users = get_user_model()._default_manager.filter(
- email__iexact=email, is_active=True)
+ UserModel = get_user_model()
+ active_users = UserModel._default_manager.filter(**{
+ '%s__iexact' % UserModel.get_email_field_name(): email,
+ 'is_active': True,
+ })
return (u for u in active_users if u.has_usable_password())
def save(self, domain_override=None,
@@ -277,7 +280,7 @@ class PasswordResetForm(forms.Form):
else:
site_name = domain = domain_override
context = {
- 'email': user.email,
+ 'email': email,
'domain': domain,
'site_name': site_name,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
@@ -289,7 +292,7 @@ class PasswordResetForm(forms.Form):
context.update(extra_email_context)
self.send_mail(
subject_template_name, email_template_name, context, from_email,
- user.email, html_email_template_name=html_email_template_name,
+ email, html_email_template_name=html_email_template_name,
)
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index e62307a0a7..eef38c17ee 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -333,6 +333,7 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin):
objects = UserManager()
+ EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index ded12408b9..411c6fb3ab 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -120,6 +120,11 @@ Minor features
* The :func:`~django.contrib.auth.signals.user_login_failed` signal now
receives a ``request`` argument.
+* :class:`~django.contrib.auth.forms.PasswordResetForm` supports custom user
+ models that use an email field named something other than ``'email'``.
+ Set :attr:`CustomUser.EMAIL_FIELD
+ <django.contrib.auth.models.CustomUser.EMAIL_FIELD>` to the name of the field.
+
:mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt
index 068671c259..f3b53f17f5 100644
--- a/docs/topics/auth/customizing.txt
+++ b/docs/topics/auth/customizing.txt
@@ -544,6 +544,14 @@ password resets. You must then provide some key implementation details:
value (the :attr:`~django.db.models.Field.primary_key` by default) of an
existing instance.
+ .. attribute:: EMAIL_FIELD
+
+ .. versionadded:: 1.11
+
+ A string describing the name of the email field on the ``User`` model.
+ This value is returned by
+ :meth:`~models.AbstractBaseUser.get_email_field_name`.
+
.. attribute:: REQUIRED_FIELDS
A list of the field names that will be prompted for when creating a
@@ -623,6 +631,14 @@ The following attributes and methods are available on any subclass of
override this method, be sure to call ``super()`` to retain the
normalization.
+ .. classmethod:: get_email_field_name()
+
+ .. versionadded:: 1.11
+
+ Returns the name of the email field specified by the
+ :attr:`~models.CustomUser.EMAIL_FIELD` attribute. Defaults to
+ ``'email'`` if ``EMAIL_FIELD`` isn't specified.
+
.. classmethod:: normalize_username(username)
.. versionadded:: 1.10
@@ -807,9 +823,10 @@ The following forms make assumptions about the user model and can be used as-is
if those assumptions are met:
* :class:`~django.contrib.auth.forms.PasswordResetForm`: Assumes that the user
- model has a field named ``email`` that can be used to identify the user and a
- boolean field named ``is_active`` to prevent password resets for inactive
- users.
+ model has a field that stores the user's email address with the name returned
+ by :meth:`~models.AbstractBaseUser.get_email_field_name` (``email`` by
+ default) that can be used to identify the user and a boolean field named
+ ``is_active`` to prevent password resets for inactive users.
Finally, the following forms are tied to
:class:`~django.contrib.auth.models.User` and need to be rewritten or extended
diff --git a/tests/auth_tests/models/with_custom_email_field.py b/tests/auth_tests/models/with_custom_email_field.py
new file mode 100644
index 0000000000..a98b02b8f1
--- /dev/null
+++ b/tests/auth_tests/models/with_custom_email_field.py
@@ -0,0 +1,23 @@
+from django.contrib.auth.base_user import AbstractBaseUser
+from django.contrib.auth.models import BaseUserManager
+from django.db import models
+
+
+class CustomEmailFieldUserManager(BaseUserManager):
+ def create_user(self, username, password, email):
+ user = self.model(username=username)
+ user.set_password(password)
+ user.email_address = email
+ user.save(using=self._db)
+ return user
+
+
+class CustomEmailField(AbstractBaseUser):
+ username = models.CharField(max_length=255)
+ password = models.CharField(max_length=255)
+ email_address = models.EmailField()
+ is_active = models.BooleanField(default=True)
+
+ EMAIL_FIELD = 'email_address'
+
+ objects = CustomEmailFieldUserManager()
diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
index c0e2961424..8d656bd6f9 100644
--- a/tests/auth_tests/test_forms.py
+++ b/tests/auth_tests/test_forms.py
@@ -26,6 +26,7 @@ from django.utils.translation import ugettext as _
from .models.custom_user import (
CustomUser, CustomUserWithoutIsActiveField, ExtensionUser,
)
+from .models.with_custom_email_field import CustomEmailField
from .models.with_integer_username import IntegerUsernameUser
from .settings import AUTH_TEMPLATES
@@ -812,6 +813,17 @@ class PasswordResetFormTest(TestDataMixin, TestCase):
message.get_payload(1).get_payload()
))
+ @override_settings(AUTH_USER_MODEL='auth_tests.CustomEmailField')
+ def test_custom_email_field(self):
+ email = 'test@mail.com'
+ CustomEmailField.objects.create_user('test name', 'test password', email)
+ form = PasswordResetForm({'email': email})
+ self.assertTrue(form.is_valid())
+ form.save()
+ self.assertEqual(form.cleaned_data['email'], email)
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].to, [email])
+
class ReadOnlyPasswordHashTest(SimpleTestCase):
diff --git a/tests/auth_tests/test_models.py b/tests/auth_tests/test_models.py
index a92f882de0..c939f3437c 100644
--- a/tests/auth_tests/test_models.py
+++ b/tests/auth_tests/test_models.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from django.conf.global_settings import PASSWORD_HASHERS
from django.contrib.auth import get_user_model
+from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.hashers import get_hasher
from django.contrib.auth.models import (
AbstractUser, Group, Permission, User, UserManager,
@@ -12,6 +13,8 @@ from django.core import mail
from django.db.models.signals import post_save
from django.test import TestCase, mock, override_settings
+from .models.with_custom_email_field import CustomEmailField
+
class NaturalKeysTestCase(TestCase):
@@ -160,6 +163,14 @@ class AbstractBaseUserTests(TestCase):
self.assertNotEqual(username, ohm_username)
self.assertEqual(username, 'iamtheΩ') # U+03A9 GREEK CAPITAL LETTER OMEGA
+ def test_default_email(self):
+ user = AbstractBaseUser()
+ self.assertEqual(user.get_email_field_name(), 'email')
+
+ def test_custom_email(self):
+ user = CustomEmailField()
+ self.assertEqual(user.get_email_field_name(), 'email_address')
+
class AbstractUserTestCase(TestCase):
def test_email_user(self):