Fixed #19067 -- Clarified handling of username in createsuperuser.
Thanks to clelland for the report, and Preston Holmes for the draft patch.
This commit is contained in:
parent
c433fcb3fb
commit
b3b3db3d95
|
@ -16,50 +16,56 @@ from django.utils.text import capfirst
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
option_list = BaseCommand.option_list + (
|
|
||||||
make_option('--username', dest='username', default=None,
|
def __init__(self, *args, **kwargs):
|
||||||
help='Specifies the username for the superuser.'),
|
# 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,
|
make_option('--noinput', action='store_false', dest='interactive', default=True,
|
||||||
help=('Tells Django to NOT prompt the user for input of any kind. '
|
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 '
|
'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',
|
make_option('--database', action='store', dest='database',
|
||||||
default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".'),
|
default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".'),
|
||||||
) + tuple(
|
) + tuple(
|
||||||
make_option('--%s' % field, dest=field, default=None,
|
make_option('--%s' % field, dest=field, default=None,
|
||||||
help='Specifies the %s for the superuser.' % field)
|
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.'
|
help = 'Used to create a superuser.'
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
username = options.get('username', None)
|
username = options.get(self.UserModel.USERNAME_FIELD, None)
|
||||||
interactive = options.get('interactive')
|
interactive = options.get('interactive')
|
||||||
verbosity = int(options.get('verbosity', 1))
|
verbosity = int(options.get('verbosity', 1))
|
||||||
database = options.get('database')
|
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
|
# If not provided, create the user with an unusable password
|
||||||
password = None
|
password = None
|
||||||
other_data = {}
|
user_data = {}
|
||||||
|
|
||||||
# Do quick and dirty validation if --noinput
|
# Do quick and dirty validation if --noinput
|
||||||
if not interactive:
|
if not interactive:
|
||||||
try:
|
try:
|
||||||
if not username:
|
if not username:
|
||||||
raise CommandError("You must use --username with --noinput.")
|
raise CommandError("You must use --%s with --noinput." %
|
||||||
username = username_field.clean(username, None)
|
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):
|
if options.get(field_name):
|
||||||
field = UserModel._meta.get_field(field_name)
|
field = self.UserModel._meta.get_field(field_name)
|
||||||
other_data[field_name] = field.clean(options[field_name], None)
|
user_data[field_name] = field.clean(options[field_name], None)
|
||||||
else:
|
else:
|
||||||
raise CommandError("You must use --%s with --noinput." % field_name)
|
raise CommandError("You must use --%s with --noinput." % field_name)
|
||||||
except exceptions.ValidationError as e:
|
except exceptions.ValidationError as e:
|
||||||
|
@ -74,9 +80,8 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
# Get a username
|
# Get a username
|
||||||
while username is None:
|
while username is None:
|
||||||
username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
|
|
||||||
if not username:
|
if not username:
|
||||||
input_msg = capfirst(username_field.verbose_name)
|
input_msg = capfirst(self.username_field.verbose_name)
|
||||||
if default_username:
|
if default_username:
|
||||||
input_msg += " (leave blank to use '%s')" % default_username
|
input_msg += " (leave blank to use '%s')" % default_username
|
||||||
raw_value = input(input_msg + ': ')
|
raw_value = input(input_msg + ': ')
|
||||||
|
@ -84,31 +89,30 @@ class Command(BaseCommand):
|
||||||
if default_username and raw_value == '':
|
if default_username and raw_value == '':
|
||||||
raw_value = default_username
|
raw_value = default_username
|
||||||
try:
|
try:
|
||||||
username = username_field.clean(raw_value, None)
|
username = self.username_field.clean(raw_value, None)
|
||||||
except exceptions.ValidationError as e:
|
except exceptions.ValidationError as e:
|
||||||
self.stderr.write("Error: %s" % '; '.join(e.messages))
|
self.stderr.write("Error: %s" % '; '.join(e.messages))
|
||||||
username = None
|
username = None
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
UserModel.objects.using(database).get(**{
|
self.UserModel.objects.db_manager(database).get_by_natural_key(username)
|
||||||
UserModel.USERNAME_FIELD: username
|
except self.UserModel.DoesNotExist:
|
||||||
})
|
|
||||||
except UserModel.DoesNotExist:
|
|
||||||
pass
|
pass
|
||||||
else:
|
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
|
username = None
|
||||||
|
|
||||||
for field_name in other_fields:
|
for field_name in self.UserModel.REQUIRED_FIELDS:
|
||||||
field = UserModel._meta.get_field(field_name)
|
field = self.UserModel._meta.get_field(field_name)
|
||||||
other_data[field_name] = options.get(field_name)
|
user_data[field_name] = options.get(field_name)
|
||||||
while other_data[field_name] is None:
|
while user_data[field_name] is None:
|
||||||
raw_value = input(capfirst(field.verbose_name + ': '))
|
raw_value = input(capfirst(field.verbose_name + ': '))
|
||||||
try:
|
try:
|
||||||
other_data[field_name] = field.clean(raw_value, None)
|
user_data[field_name] = field.clean(raw_value, None)
|
||||||
except exceptions.ValidationError as e:
|
except exceptions.ValidationError as e:
|
||||||
self.stderr.write("Error: %s" % '; '.join(e.messages))
|
self.stderr.write("Error: %s" % '; '.join(e.messages))
|
||||||
other_data[field_name] = None
|
user_data[field_name] = None
|
||||||
|
|
||||||
# Get a password
|
# Get a password
|
||||||
while password is None:
|
while password is None:
|
||||||
|
@ -128,6 +132,8 @@ class Command(BaseCommand):
|
||||||
self.stderr.write("\nOperation cancelled.")
|
self.stderr.write("\nOperation cancelled.")
|
||||||
sys.exit(1)
|
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:
|
if verbosity >= 1:
|
||||||
self.stdout.write("Superuser created successfully.")
|
self.stdout.write("Superuser created successfully.")
|
||||||
|
|
|
@ -23,8 +23,8 @@ class CustomUserManager(BaseUserManager):
|
||||||
user.save(using=self._db)
|
user.save(using=self._db)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def create_superuser(self, username, password, date_of_birth):
|
def create_superuser(self, email, password, date_of_birth):
|
||||||
u = self.create_user(username, password=password, date_of_birth=date_of_birth)
|
u = self.create_user(email, password=password, date_of_birth=date_of_birth)
|
||||||
u.is_admin = True
|
u.is_admin = True
|
||||||
u.save(using=self._db)
|
u.save(using=self._db)
|
||||||
return u
|
return u
|
||||||
|
|
|
@ -138,7 +138,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
||||||
new_io = StringIO()
|
new_io = StringIO()
|
||||||
call_command("createsuperuser",
|
call_command("createsuperuser",
|
||||||
interactive=False,
|
interactive=False,
|
||||||
username="joe@somewhere.org",
|
email="joe@somewhere.org",
|
||||||
date_of_birth="1976-04-01",
|
date_of_birth="1976-04-01",
|
||||||
stdout=new_io,
|
stdout=new_io,
|
||||||
skip_validation=True
|
skip_validation=True
|
||||||
|
|
|
@ -1878,7 +1878,9 @@ The easiest way to construct a compliant custom User model is to inherit from
|
||||||
implementation of a `User` model, including hashed passwords and tokenized
|
implementation of a `User` model, including hashed passwords and tokenized
|
||||||
password resets. You must then provide some key implementation details:
|
password resets. You must then provide some key implementation details:
|
||||||
|
|
||||||
.. attribute:: User.USERNAME_FIELD
|
.. class:: models.CustomUser
|
||||||
|
|
||||||
|
.. attribute:: User.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
|
||||||
|
@ -1891,7 +1893,7 @@ password resets. You must then provide some key implementation details:
|
||||||
...
|
...
|
||||||
USERNAME_FIELD = 'identifier'
|
USERNAME_FIELD = 'identifier'
|
||||||
|
|
||||||
.. attribute:: User.REQUIRED_FIELDS
|
.. attribute:: User.REQUIRED_FIELDS
|
||||||
|
|
||||||
A list of the field names that *must* be provided when creating
|
A list of the field names that *must* be provided when creating
|
||||||
a user. For example, here is the partial definition for a User model
|
a user. For example, here is the partial definition for a User model
|
||||||
|
@ -1904,13 +1906,18 @@ password resets. You must then provide some key implementation details:
|
||||||
...
|
...
|
||||||
REQUIRED_FIELDS = ['date_of_birth', 'height']
|
REQUIRED_FIELDS = ['date_of_birth', 'height']
|
||||||
|
|
||||||
.. method:: User.get_full_name():
|
.. 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
|
A longer formal identifier for the user. A common interpretation
|
||||||
would be the full name name of the user, but it can be any string that
|
would be the full name name of the user, but it can be any string that
|
||||||
identifies the user.
|
identifies the user.
|
||||||
|
|
||||||
.. method:: User.get_short_name():
|
.. method:: User.get_short_name():
|
||||||
|
|
||||||
A short, informal identifier for the user. A common interpretation
|
A short, informal identifier for the user. A common interpretation
|
||||||
would be the first name of the user, but it can be any string that
|
would be the first name of the user, but it can be any string that
|
||||||
|
@ -1918,7 +1925,7 @@ password resets. You must then provide some key implementation details:
|
||||||
value as :meth:`django.contrib.auth.User.get_full_name()`.
|
value as :meth:`django.contrib.auth.User.get_full_name()`.
|
||||||
|
|
||||||
The following methods are available on any subclass of
|
The following methods are available on any subclass of
|
||||||
:class:`~django.contrib.auth.models.AbstractBaseUser`::
|
:class:`~django.contrib.auth.models.AbstractBaseUser`:
|
||||||
|
|
||||||
.. class:: 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
|
extends :class:`~django.contrib.auth.models.BaseUserManager` providing two
|
||||||
additional methods:
|
additional methods:
|
||||||
|
|
||||||
.. method:: UserManager.create_user(username, password=None, **other_fields)
|
.. class:: models.CustomUserManager
|
||||||
|
|
||||||
The prototype of `create_user()` should accept all required fields
|
.. method:: models.CustomUserManager.create_user(*username_field*, password=None, **other_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::
|
|
||||||
|
|
||||||
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
|
# 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
|
The prototype of `create_user()` should accept the username field,
|
||||||
as arguments. For example, if your user model defines `username`,
|
plus all required fields as arguments. For example, if your user model
|
||||||
and `date_of_birth` as required fields, then create_user should be
|
uses `email` as the username field, and has `date_of_birth` as a required
|
||||||
defined as::
|
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
|
# create superuser here
|
||||||
|
|
||||||
Unlike `create_user()`, `create_superuser()` *must* require the caller
|
Unlike `create_user()`, `create_superuser()` *must* require the caller
|
||||||
|
@ -2006,6 +2015,7 @@ additional methods:
|
||||||
utility methods:
|
utility methods:
|
||||||
|
|
||||||
.. class:: models.BaseUserManager
|
.. class:: models.BaseUserManager
|
||||||
|
|
||||||
.. method:: models.BaseUserManager.normalize_email(email)
|
.. method:: models.BaseUserManager.normalize_email(email)
|
||||||
|
|
||||||
A classmethod that normalizes email addresses by lowercasing
|
A classmethod that normalizes email addresses by lowercasing
|
||||||
|
@ -2165,12 +2175,12 @@ authentication app::
|
||||||
user.save(using=self._db)
|
user.save(using=self._db)
|
||||||
return user
|
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
|
Creates and saves a superuser with the given email, date of
|
||||||
birth and password.
|
birth and password.
|
||||||
"""
|
"""
|
||||||
user = self.create_user(username,
|
user = self.create_user(email,
|
||||||
password=password,
|
password=password,
|
||||||
date_of_birth=date_of_birth
|
date_of_birth=date_of_birth
|
||||||
)
|
)
|
||||||
|
@ -2223,7 +2233,7 @@ authentication app::
|
||||||
return self.is_admin
|
return self.is_admin
|
||||||
|
|
||||||
Then, to register this custom User model with Django's admin, the following
|
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 import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
@ -2249,7 +2259,7 @@ code would be required in ``admin.py``::
|
||||||
password1 = self.cleaned_data.get("password1")
|
password1 = self.cleaned_data.get("password1")
|
||||||
password2 = self.cleaned_data.get("password2")
|
password2 = self.cleaned_data.get("password2")
|
||||||
if password1 and password2 and password1 != password2:
|
if password1 and password2 and password1 != password2:
|
||||||
raise forms.ValidationError('Passwords don't match')
|
raise forms.ValidationError("Passwords don't match")
|
||||||
return password2
|
return password2
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
|
|
Loading…
Reference in New Issue