Fixed #24910 -- Added createsuperuser support for non-unique USERNAME_FIELDs
Clarified docs to say that a non-unique USERNAME_FIELD is permissable as long as the custom auth backend can support it.
This commit is contained in:
parent
a391b17ad2
commit
1ea87c8c79
|
@ -101,14 +101,14 @@ class Command(BaseCommand):
|
||||||
username = self.get_input_data(self.username_field, input_msg, default_username)
|
username = self.get_input_data(self.username_field, input_msg, default_username)
|
||||||
if not username:
|
if not username:
|
||||||
continue
|
continue
|
||||||
try:
|
if self.username_field.unique:
|
||||||
self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
|
try:
|
||||||
except self.UserModel.DoesNotExist:
|
self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
|
||||||
pass
|
except self.UserModel.DoesNotExist:
|
||||||
else:
|
pass
|
||||||
self.stderr.write("Error: That %s is already taken." %
|
else:
|
||||||
verbose_field_name)
|
self.stderr.write("Error: That %s is already taken." % verbose_field_name)
|
||||||
username = None
|
username = None
|
||||||
|
|
||||||
for field_name in self.UserModel.REQUIRED_FIELDS:
|
for field_name in self.UserModel.REQUIRED_FIELDS:
|
||||||
field = self.UserModel._meta.get_field(field_name)
|
field = self.UserModel._meta.get_field(field_name)
|
||||||
|
|
|
@ -477,9 +477,11 @@ Specifying a custom User model
|
||||||
|
|
||||||
Django expects your custom User model to meet some minimum requirements.
|
Django expects your custom User model to meet some minimum requirements.
|
||||||
|
|
||||||
#. Your model must have a single unique field that can be used for
|
#. If you use the default authentication backend, then your model must have a
|
||||||
identification purposes. This can be a username, an email address,
|
single unique field that can be used for identification purposes. This can
|
||||||
or any other unique attribute.
|
be a username, an email address, or any other unique attribute. A non-unique
|
||||||
|
username field is allowed if you use a custom authentication backend that
|
||||||
|
can support it.
|
||||||
|
|
||||||
#. Your model must provide a way to address the user in a "short" and
|
#. Your model must provide a way to address the user in a "short" and
|
||||||
"long" form. The most common interpretation of this would be to use
|
"long" form. The most common interpretation of this would be to use
|
||||||
|
@ -506,10 +508,11 @@ password resets. You must then provide some key implementation details:
|
||||||
.. attribute:: USERNAME_FIELD
|
.. attribute:: USERNAME_FIELD
|
||||||
|
|
||||||
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
|
||||||
some kind, but it can also be an email address, or any other unique
|
kind, but it can also be an email address, or any other unique
|
||||||
identifier. The field *must* be unique (i.e., have ``unique=True``
|
identifier. The field *must* be unique (i.e., have ``unique=True`` set
|
||||||
set in its definition).
|
in its definition), unless you use a custom authentication backend that
|
||||||
|
can support non-unique usernames.
|
||||||
|
|
||||||
In the following example, the field ``identifier`` is used
|
In the following example, the field ``identifier`` is used
|
||||||
as the identifying field::
|
as the identifying field::
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
from django.contrib.auth.models import AbstractBaseUser
|
from django.contrib.auth.models import AbstractBaseUser, UserManager
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class CustomUserNonUniqueUsername(AbstractBaseUser):
|
class CustomUserNonUniqueUsername(AbstractBaseUser):
|
||||||
"A user with a non-unique username"
|
"""
|
||||||
|
A user with a non-unique username.
|
||||||
|
|
||||||
|
This model is not invalid if it is used with a custom authentication
|
||||||
|
backend which supports non-unique usernames.
|
||||||
|
"""
|
||||||
username = models.CharField(max_length=30)
|
username = models.CharField(max_length=30)
|
||||||
|
email = models.EmailField(blank=True)
|
||||||
|
is_staff = models.BooleanField(default=False)
|
||||||
|
is_superuser = models.BooleanField(default=False)
|
||||||
|
|
||||||
USERNAME_FIELD = 'username'
|
USERNAME_FIELD = 'username'
|
||||||
|
REQUIRED_FIELDS = ['email']
|
||||||
|
|
||||||
|
objects = UserManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'auth'
|
app_label = 'auth'
|
||||||
|
|
|
@ -305,6 +305,33 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
||||||
|
|
||||||
self.assertEqual(CustomUser._default_manager.count(), 0)
|
self.assertEqual(CustomUser._default_manager.count(), 0)
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername',
|
||||||
|
AUTHENTICATION_BACKENDS=['my.custom.backend'],
|
||||||
|
)
|
||||||
|
def test_swappable_user_username_non_unique(self):
|
||||||
|
@mock_inputs({
|
||||||
|
'username': 'joe',
|
||||||
|
'password': 'nopasswd',
|
||||||
|
})
|
||||||
|
def createsuperuser():
|
||||||
|
new_io = six.StringIO()
|
||||||
|
call_command(
|
||||||
|
"createsuperuser",
|
||||||
|
interactive=True,
|
||||||
|
email="joe@somewhere.org",
|
||||||
|
stdout=new_io,
|
||||||
|
stdin=MockTTY(),
|
||||||
|
)
|
||||||
|
command_output = new_io.getvalue().strip()
|
||||||
|
self.assertEqual(command_output, 'Superuser created successfully.')
|
||||||
|
|
||||||
|
for i in range(2):
|
||||||
|
createsuperuser()
|
||||||
|
|
||||||
|
users = CustomUserNonUniqueUsername.objects.filter(username="joe")
|
||||||
|
self.assertEqual(users.count(), 2)
|
||||||
|
|
||||||
def test_skip_if_not_in_TTY(self):
|
def test_skip_if_not_in_TTY(self):
|
||||||
"""
|
"""
|
||||||
If the command is not called from a TTY, it should be skipped and a
|
If the command is not called from a TTY, it should be skipped and a
|
||||||
|
|
Loading…
Reference in New Issue