From bc6746ac303ab625f2bc6fc878bd63661c784a59 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 15 Feb 2013 09:00:55 +0800 Subject: [PATCH] [1.5.x] Fixed #19822 -- Added validation for uniqueness on USERNAME_FIELD on custom User models. Thanks to Claude Peroz for the draft patch. (cherry picked from commit f5e4a699ca0f58818acbdf9081164060cee910fa) --- django/contrib/auth/tests/custom_user.py | 22 ++++++++++++++++++++++ django/contrib/auth/tests/management.py | 18 ++++++++++++++++++ django/core/management/validation.py | 6 +++++- docs/topics/auth/customizing.txt | 5 ++++- 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/django/contrib/auth/tests/custom_user.py b/django/contrib/auth/tests/custom_user.py index 8cc57d4caf053..0d324f095392c 100644 --- a/django/contrib/auth/tests/custom_user.py +++ b/django/contrib/auth/tests/custom_user.py @@ -144,3 +144,25 @@ class Meta: app_label = 'auth' # the is_active attr is provided by AbstractBaseUser + + +class CustomUserNonUniqueUsername(AbstractBaseUser): + "A user with a non-unique username" + username = models.CharField(max_length=30) + + USERNAME_FIELD = 'username' + + class Meta: + app_label = 'auth' + + +class CustomUserBadRequiredFields(AbstractBaseUser): + "A user with a non-unique username" + username = models.CharField(max_length=30, unique=True) + date_of_birth = models.DateField() + + USERNAME_FIELD = 'username' + REQUIRED_FIELDS = ['username', 'date_of_birth'] + + class Meta: + app_label = 'auth' diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py index 42f14d6d5c978..687a5c31cbf2e 100644 --- a/django/contrib/auth/tests/management.py +++ b/django/contrib/auth/tests/management.py @@ -9,6 +9,8 @@ from django.contrib.auth.tests.utils import skipIfCustomUser from django.core.management import call_command from django.core.management.base import CommandError +from django.core.management.validation import get_validation_errors +from django.db.models.loading import get_app from django.test import TestCase from django.test.utils import override_settings from django.utils import six @@ -170,6 +172,22 @@ def test_swappable_user_missing_required_field(self): self.assertEqual(CustomUser._default_manager.count(), 0) +class CustomUserModelValidationTestCase(TestCase): + @override_settings(AUTH_USER_MODEL='auth.CustomUserBadRequiredFields') + def test_username_not_in_required_fields(self): + "USERNAME_FIELD should not appear in REQUIRED_FIELDS." + new_io = StringIO() + get_validation_errors(new_io, get_app('auth')) + self.assertIn("The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.", new_io.getvalue()) + + @override_settings(AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername') + def test_username_non_unique(self): + "A non-unique USERNAME_FIELD should raise a model validation error." + new_io = StringIO() + get_validation_errors(new_io, get_app('auth')) + self.assertIn("The USERNAME_FIELD must be unique. Add unique=True to the field parameters.", new_io.getvalue()) + + class PermissionDuplicationTestCase(TestCase): def setUp(self): diff --git a/django/core/management/validation.py b/django/core/management/validation.py index c0452c5aa6d22..86a0df28fccf5 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -50,12 +50,16 @@ def get_validation_errors(outfile, app=None): # No need to perform any other validation checks on a swapped model. continue - # This is the current User model. Check known validation problems with User models + # If this is the current User model, check known validation problems with User models if settings.AUTH_USER_MODEL == '%s.%s' % (opts.app_label, opts.object_name): # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS. if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS: e.add(opts, 'The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.') + # Check that the username field is unique + if not opts.get_field(cls.USERNAME_FIELD).unique: + e.add(opts, 'The USERNAME_FIELD must be unique. Add unique=True to the field parameters.') + # Model isn't swapped; do field-specific validation. for f in opts.local_fields: if f.name == 'id' and not f.primary_key and opts.pk.name == 'id': diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index e61780ed79938..9c36bf26e5ead 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -486,7 +486,10 @@ password resets. You must then provide some key implementation details: A string describing the name of the field on the User model that is used as the unique identifier. This will usually be a username of some kind, but it can also be an email address, or any other unique - identifier. In the following example, the field `identifier` is used + identifier. The field *must* be unique (i.e., have ``unique=True`` + set in it's definition). + + In the following example, the field `identifier` is used as the identifying field:: class MyUser(AbstractBaseUser):