summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlasdair Nicol <alasdair@thenicols.net>2015-06-05 14:33:04 +0100
committerTim Graham <timograham@gmail.com>2015-06-06 09:33:02 -0400
commit1ea87c8c7974acb5fa795362253c61b38f3cb137 (patch)
tree2192bdc6b85457eb4352563a1d9976f37c3a80cc
parenta391b17ad24bc5f255a3928c23c158c79004c656 (diff)
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.
-rw-r--r--django/contrib/auth/management/commands/createsuperuser.py16
-rw-r--r--docs/topics/auth/customizing.txt17
-rw-r--r--tests/auth_tests/models/invalid_models.py15
-rw-r--r--tests/auth_tests/test_management.py27
4 files changed, 58 insertions, 17 deletions
diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py
index 7d374b863d..1e3b9a8d7c 100644
--- a/django/contrib/auth/management/commands/createsuperuser.py
+++ b/django/contrib/auth/management/commands/createsuperuser.py
@@ -101,14 +101,14 @@ class Command(BaseCommand):
username = self.get_input_data(self.username_field, input_msg, default_username)
if not username:
continue
- try:
- self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
- except self.UserModel.DoesNotExist:
- pass
- else:
- self.stderr.write("Error: That %s is already taken." %
- verbose_field_name)
- username = None
+ if self.username_field.unique:
+ try:
+ self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
+ except self.UserModel.DoesNotExist:
+ pass
+ else:
+ self.stderr.write("Error: That %s is already taken." % verbose_field_name)
+ username = None
for field_name in self.UserModel.REQUIRED_FIELDS:
field = self.UserModel._meta.get_field(field_name)
diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt
index 6d06ef6e25..7745574d5d 100644
--- a/docs/topics/auth/customizing.txt
+++ b/docs/topics/auth/customizing.txt
@@ -477,9 +477,11 @@ Specifying a custom User model
Django expects your custom User model to meet some minimum requirements.
-#. Your model must have a single unique field that can be used for
- identification purposes. This can be a username, an email address,
- or any other unique attribute.
+#. If you use the default authentication backend, then your model must have a
+ single unique field that can be used for identification purposes. This can
+ 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
"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
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. The field *must* be unique (i.e., have ``unique=True``
- set in its definition).
+ 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. The field *must* be unique (i.e., have ``unique=True`` set
+ 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
as the identifying field::
diff --git a/tests/auth_tests/models/invalid_models.py b/tests/auth_tests/models/invalid_models.py
index 46b4819fa4..eaaff2aa15 100644
--- a/tests/auth_tests/models/invalid_models.py
+++ b/tests/auth_tests/models/invalid_models.py
@@ -1,12 +1,23 @@
-from django.contrib.auth.models import AbstractBaseUser
+from django.contrib.auth.models import AbstractBaseUser, UserManager
from django.db import models
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)
+ email = models.EmailField(blank=True)
+ is_staff = models.BooleanField(default=False)
+ is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = 'username'
+ REQUIRED_FIELDS = ['email']
+
+ objects = UserManager()
class Meta:
app_label = 'auth'
diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py
index e9c6105496..f45703fcfd 100644
--- a/tests/auth_tests/test_management.py
+++ b/tests/auth_tests/test_management.py
@@ -305,6 +305,33 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
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):
"""
If the command is not called from a TTY, it should be skipped and a