diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 423e3429e6..9279c52675 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -209,10 +209,12 @@ class PasswordResetForm(forms.Form): """ UserModel = get_user_model() email = self.cleaned_data["email"] - self.users_cache = UserModel.objects.filter(email__iexact=email, - is_active=True) + self.users_cache = UserModel.objects.filter(email__iexact=email) if not len(self.users_cache): raise forms.ValidationError(self.error_messages['unknown']) + if not any(user.is_active for user in self.users_cache): + # none of the filtered users are active + raise forms.ValidationError(self.error_messages['unknown']) if any((user.password == UNUSABLE_PASSWORD) for user in self.users_cache): raise forms.ValidationError(self.error_messages['unusable']) diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index bd7bf4a162..de4de7cdfd 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -232,6 +232,8 @@ class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) last_login = models.DateTimeField(_('last login'), default=timezone.now) + is_active = True + REQUIRED_FIELDS = [] class Meta: diff --git a/django/contrib/auth/tests/custom_user.py b/django/contrib/auth/tests/custom_user.py index a29ed6a104..710ac8bdd7 100644 --- a/django/contrib/auth/tests/custom_user.py +++ b/django/contrib/auth/tests/custom_user.py @@ -88,3 +88,20 @@ class ExtensionUser(AbstractUser): class Meta: app_label = 'auth' + + +class IsActiveTestUser1(AbstractBaseUser): + """ + This test user class and derivatives test the default is_active behavior + """ + username = models.CharField(max_length=30, unique=True) + + objects = BaseUserManager() + + USERNAME_FIELD = 'username' + + class Meta: + app_label = 'auth' + + # the is_active attr is provided by AbstractBaseUser + diff --git a/django/contrib/auth/tests/models.py b/django/contrib/auth/tests/models.py index 252a0887c8..cb7d8888fe 100644 --- a/django/contrib/auth/tests/models.py +++ b/django/contrib/auth/tests/models.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.contrib.auth import get_user_model from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable, UserManager) from django.contrib.auth.tests.utils import skipIfCustomUser @@ -98,3 +99,34 @@ class UserManagerTestCase(TestCase): self.assertRaisesMessage(ValueError, 'The given username must be set', User.objects.create_user, username='') + +class IsActiveTestCase(TestCase): + """ + Tests the behavior of the guaranteed is_active attribute + """ + + def test_builtin_user_isactive(self): + user = User.objects.create(username='foo', email='foo@bar.com') + # is_active is true by default + self.assertEqual(user.is_active, True) + user.is_active = False + user.save() + user_fetched = User.objects.get(pk=user.pk) + # the is_active flag is saved + self.assertFalse(user_fetched.is_active) + + @override_settings(AUTH_USER_MODEL='auth.IsActiveTestUser1') + def test_is_active_field_default(self): + """ + tests that the default value for is_active is provided + """ + UserModel = get_user_model() + user = UserModel(username='foo') + self.assertEqual(user.is_active, True) + # you can set the attribute - but it will not save + user.is_active = False + # there should be no problem saving - but the attribute is not saved + user.save() + user_fetched = UserModel.objects.get(pk=user.pk) + # the attribute is always true for newly retrieved instance + self.assertEqual(user_fetched.is_active, True) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index d261a3c90b..f7a9084008 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1911,6 +1911,15 @@ password resets. You must then provide some key implementation details: ``REQUIRED_FIELDS`` must contain all required fields on your User model, but should *not* contain the ``USERNAME_FIELD``. + .. attribute:: User.is_active + + A boolean attribute that indicates whether the user is considered + "active". This attribute is provided as an attribute on + ``AbstractBaseUser`` defaulting to ``True``. How you choose to + implement it will depend on the details of your chosen auth backends. + See the documentation of the :attr:`attribute on the builtin user model + ` for details. + .. method:: User.get_full_name(): A longer formal identifier for the user. A common interpretation