diff --git a/django/contrib/auth/tests/custom_user.py b/django/contrib/auth/tests/custom_user.py index 8cc57d4caf0..0d324f09539 100644 --- a/django/contrib/auth/tests/custom_user.py +++ b/django/contrib/auth/tests/custom_user.py @@ -144,3 +144,25 @@ class IsActiveTestUser1(AbstractBaseUser): 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 42f14d6d5c9..687a5c31cbf 100644 --- a/django/contrib/auth/tests/management.py +++ b/django/contrib/auth/tests/management.py @@ -9,6 +9,8 @@ from django.contrib.auth.tests import CustomUser 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 @@ class CreatesuperuserManagementCommandTestCase(TestCase): 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 f49a3c22325..587d3a0ad7c 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -49,12 +49,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 7f1eff66242..d1ce6eb7dcb 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -492,7 +492,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):