Fixed #19822 -- Added validation for uniqueness on USERNAME_FIELD on custom User models.

Thanks to Claude Peroz for the draft patch.
This commit is contained in:
Russell Keith-Magee 2013-02-15 09:00:55 +08:00
parent f179a5198e
commit f5e4a699ca
4 changed files with 49 additions and 2 deletions

View File

@ -144,3 +144,25 @@ class IsActiveTestUser1(AbstractBaseUser):
app_label = 'auth' app_label = 'auth'
# the is_active attr is provided by AbstractBaseUser # 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'

View File

@ -9,6 +9,8 @@ from django.contrib.auth.tests import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core.management import call_command from django.core.management import call_command
from django.core.management.base import CommandError 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 import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils import six from django.utils import six
@ -170,6 +172,22 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
self.assertEqual(CustomUser._default_manager.count(), 0) 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): class PermissionDuplicationTestCase(TestCase):
def setUp(self): def setUp(self):

View File

@ -49,12 +49,16 @@ def get_validation_errors(outfile, app=None):
# No need to perform any other validation checks on a swapped model. # No need to perform any other validation checks on a swapped model.
continue 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): if settings.AUTH_USER_MODEL == '%s.%s' % (opts.app_label, opts.object_name):
# Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS. # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS.
if cls.USERNAME_FIELD in cls.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.') 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. # Model isn't swapped; do field-specific validation.
for f in opts.local_fields: for f in opts.local_fields:
if f.name == 'id' and not f.primary_key and opts.pk.name == 'id': if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':

View File

@ -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 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 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 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:: as the identifying field::
class MyUser(AbstractBaseUser): class MyUser(AbstractBaseUser):