Commit 1ea87c8c authored by Alasdair Nicol's avatar Alasdair Nicol Committed by Tim Graham
Browse files

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.
parent a391b17a
Loading
Loading
Loading
Loading
+8 −8
Original line number Diff line number Diff line
@@ -101,13 +101,13 @@ class Command(BaseCommand):
                    username = self.get_input_data(self.username_field, input_msg, default_username)
                    if not username:
                        continue
                    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)
                            self.stderr.write("Error: That %s is already taken." % verbose_field_name)
                            username = None

                for field_name in self.UserModel.REQUIRED_FIELDS:
+10 −7
Original line number Diff line number Diff line
@@ -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::
+13 −2
Original line number Diff line number Diff line
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'
+27 −0
Original line number Diff line number Diff line
@@ -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