Commit b3b3db3d authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

Fixed #19067 -- Clarified handling of username in createsuperuser.

Thanks to clelland for the report, and Preston Holmes for the draft patch.
parent c433fcb3
Loading
Loading
Loading
Loading
+48 −42
Original line number Diff line number Diff line
@@ -16,50 +16,56 @@ from django.utils.text import capfirst


class Command(BaseCommand):
    option_list = BaseCommand.option_list + (
        make_option('--username', dest='username', default=None,
            help='Specifies the username for the superuser.'),

    def __init__(self, *args, **kwargs):
        # Options are defined in an __init__ method to support swapping out
        # custom user models in tests.
        super(Command, self).__init__(*args, **kwargs)
        self.UserModel = get_user_model()
        self.username_field = self.UserModel._meta.get_field(self.UserModel.USERNAME_FIELD)

        self.option_list = BaseCommand.option_list + (
            make_option('--%s' % self.UserModel.USERNAME_FIELD, dest=self.UserModel.USERNAME_FIELD, default=None,
                help='Specifies the login for the superuser.'),
            make_option('--noinput', action='store_false', dest='interactive', default=True,
                help=('Tells Django to NOT prompt the user for input of any kind. '
                  'You must use --username with --noinput, along with an option for '
                    'You must use --%s with --noinput, along with an option for '
                    'any other required field. Superusers created with --noinput will '
                  ' not be able to log in until they\'re given a valid password.')),
                    ' not be able to log in until they\'re given a valid password.' %
                    self.UserModel.USERNAME_FIELD)),
            make_option('--database', action='store', dest='database',
                default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".'),
        ) + tuple(
            make_option('--%s' % field, dest=field, default=None,
                help='Specifies the %s for the superuser.' % field)
        for field in get_user_model().REQUIRED_FIELDS
            for field in self.UserModel.REQUIRED_FIELDS
        )

    option_list = BaseCommand.option_list
    help = 'Used to create a superuser.'

    def handle(self, *args, **options):
        username = options.get('username', None)
        username = options.get(self.UserModel.USERNAME_FIELD, None)
        interactive = options.get('interactive')
        verbosity = int(options.get('verbosity', 1))
        database = options.get('database')

        UserModel = get_user_model()

        username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
        other_fields = UserModel.REQUIRED_FIELDS

        # If not provided, create the user with an unusable password
        password = None
        other_data = {}
        user_data = {}

        # Do quick and dirty validation if --noinput
        if not interactive:
            try:
                if not username:
                    raise CommandError("You must use --username with --noinput.")
                username = username_field.clean(username, None)
                    raise CommandError("You must use --%s with --noinput." %
                            self.UserModel.USERNAME_FIELD)
                username = self.username_field.clean(username, None)

                for field_name in other_fields:
                for field_name in self.UserModel.REQUIRED_FIELDS:
                    if options.get(field_name):
                        field = UserModel._meta.get_field(field_name)
                        other_data[field_name] = field.clean(options[field_name], None)
                        field = self.UserModel._meta.get_field(field_name)
                        user_data[field_name] = field.clean(options[field_name], None)
                    else:
                        raise CommandError("You must use --%s with --noinput." % field_name)
            except exceptions.ValidationError as e:
@@ -74,9 +80,8 @@ class Command(BaseCommand):

                # Get a username
                while username is None:
                    username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
                    if not username:
                        input_msg = capfirst(username_field.verbose_name)
                        input_msg = capfirst(self.username_field.verbose_name)
                        if default_username:
                            input_msg += " (leave blank to use '%s')" % default_username
                        raw_value = input(input_msg + ': ')
@@ -84,31 +89,30 @@ class Command(BaseCommand):
                    if default_username and raw_value == '':
                        raw_value = default_username
                    try:
                        username = username_field.clean(raw_value, None)
                        username = self.username_field.clean(raw_value, None)
                    except exceptions.ValidationError as e:
                        self.stderr.write("Error: %s" % '; '.join(e.messages))
                        username = None
                        continue
                    try:
                        UserModel.objects.using(database).get(**{
                                UserModel.USERNAME_FIELD: username
                            })
                    except UserModel.DoesNotExist:
                        self.UserModel.objects.db_manager(database).get_by_natural_key(username)
                    except self.UserModel.DoesNotExist:
                        pass
                    else:
                        self.stderr.write("Error: That username is already taken.")
                        self.stderr.write("Error: That %s is already taken." %
                                self.username_field.verbose_name)
                        username = None

                for field_name in other_fields:
                    field = UserModel._meta.get_field(field_name)
                    other_data[field_name] = options.get(field_name)
                    while other_data[field_name] is None:
                for field_name in self.UserModel.REQUIRED_FIELDS:
                    field = self.UserModel._meta.get_field(field_name)
                    user_data[field_name] = options.get(field_name)
                    while user_data[field_name] is None:
                        raw_value = input(capfirst(field.verbose_name + ': '))
                        try:
                            other_data[field_name] = field.clean(raw_value, None)
                            user_data[field_name] = field.clean(raw_value, None)
                        except exceptions.ValidationError as e:
                            self.stderr.write("Error: %s" % '; '.join(e.messages))
                            other_data[field_name] = None
                            user_data[field_name] = None

                # Get a password
                while password is None:
@@ -128,6 +132,8 @@ class Command(BaseCommand):
                self.stderr.write("\nOperation cancelled.")
                sys.exit(1)

        UserModel.objects.db_manager(database).create_superuser(username=username, password=password, **other_data)
        user_data[self.UserModel.USERNAME_FIELD] = username
        user_data['password'] = password
        self.UserModel.objects.db_manager(database).create_superuser(**user_data)
        if verbosity >= 1:
            self.stdout.write("Superuser created successfully.")
+2 −2
Original line number Diff line number Diff line
@@ -23,8 +23,8 @@ class CustomUserManager(BaseUserManager):
        user.save(using=self._db)
        return user

    def create_superuser(self, username, password, date_of_birth):
        u = self.create_user(username, password=password, date_of_birth=date_of_birth)
    def create_superuser(self, email, password, date_of_birth):
        u = self.create_user(email, password=password, date_of_birth=date_of_birth)
        u.is_admin = True
        u.save(using=self._db)
        return u
+1 −1
Original line number Diff line number Diff line
@@ -138,7 +138,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
        new_io = StringIO()
        call_command("createsuperuser",
            interactive=False,
            username="joe@somewhere.org",
            email="joe@somewhere.org",
            date_of_birth="1976-04-01",
            stdout=new_io,
            skip_validation=True
+60 −50
Original line number Diff line number Diff line
@@ -1878,6 +1878,8 @@ The easiest way to construct a compliant custom User model is to inherit from
implementation of a `User` model, including hashed passwords and tokenized
password resets. You must then provide some key implementation details:

.. class:: models.CustomUser

    .. attribute:: User.USERNAME_FIELD

        A string describing the name of the field on the User model that is
@@ -1904,6 +1906,11 @@ password resets. You must then provide some key implementation details:
                ...
                REQUIRED_FIELDS = ['date_of_birth', 'height']

        .. note::

            ``REQUIRED_FIELDS`` must contain all required fields on your User
            model, but should *not* contain the ``USERNAME_FIELD``.

    .. method:: User.get_full_name():

        A longer formal identifier for the user. A common interpretation
@@ -1918,7 +1925,7 @@ password resets. You must then provide some key implementation details:
        value as :meth:`django.contrib.auth.User.get_full_name()`.

The following methods are available on any subclass of
:class:`~django.contrib.auth.models.AbstractBaseUser`::
:class:`~django.contrib.auth.models.AbstractBaseUser`:

.. class:: models.AbstractBaseUser

@@ -1979,24 +1986,26 @@ defines different fields, you will need to define a custom manager that
extends :class:`~django.contrib.auth.models.BaseUserManager` providing two
additional methods:

.. method:: UserManager.create_user(username, password=None, **other_fields)
.. class:: models.CustomUserManager

    The prototype of `create_user()` should accept all required fields
    as arguments. For example, if your user model defines `username`,
    and `date_of_birth` as required fields, then create_user should be
    defined as::
    .. method:: models.CustomUserManager.create_user(*username_field*, password=None, **other_fields)

        def create_user(self, username, date_of_birth, password=None):
        The prototype of `create_user()` should accept the username field,
        plus all required fields as arguments. For example, if your user model
        uses `email` as the username field, and has `date_of_birth` as a required
        fields, then create_user should be defined as::

            def create_user(self, email, date_of_birth, password=None):
                # create user here

.. method:: UserManager.create_superuser(username, password, **other_fields)
    .. method:: models.CustomUserManager.create_superuser(*username_field*, password, **other_fields)

    The prototype of `create_superuser()` should accept all required fields
    as arguments. For example, if your user model defines `username`,
    and `date_of_birth` as required fields, then create_user should be
    defined as::
        The prototype of `create_user()` should accept the username field,
        plus all required fields as arguments. For example, if your user model
        uses `email` as the username field, and has `date_of_birth` as a required
        fields, then create_superuser should be defined as::

        def create_superuser(self, username, date_of_birth, password):
            def create_superuser(self, email, date_of_birth, password):
                # create superuser here

        Unlike `create_user()`, `create_superuser()` *must* require the caller
@@ -2006,6 +2015,7 @@ additional methods:
utility methods:

.. class:: models.BaseUserManager

    .. method:: models.BaseUserManager.normalize_email(email)

        A classmethod that normalizes email addresses by lowercasing
@@ -2165,12 +2175,12 @@ authentication app::
            user.save(using=self._db)
            return user

        def create_superuser(self, username, date_of_birth, password):
        def create_superuser(self, email, date_of_birth, password):
            """
            Creates and saves a superuser with the given email, date of
            birth and password.
            """
            user = self.create_user(username,
            user = self.create_user(email,
                password=password,
                date_of_birth=date_of_birth
            )
@@ -2223,7 +2233,7 @@ authentication app::
            return self.is_admin

Then, to register this custom User model with Django's admin, the following
code would be required in ``admin.py``::
code would be required in the app's ``admin.py`` file::

    from django import forms
    from django.contrib import admin
@@ -2249,7 +2259,7 @@ code would be required in ``admin.py``::
            password1 = self.cleaned_data.get("password1")
            password2 = self.cleaned_data.get("password2")
            if password1 and password2 and password1 != password2:
                raise forms.ValidationError('Passwords don't match')
                raise forms.ValidationError("Passwords don't match")
            return password2

        def save(self, commit=True):