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):
|
||||
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.")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
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
|
||||
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'
|
||||
|
||||
.. attribute:: User.REQUIRED_FIELDS
|
||||
.. attribute:: User.REQUIRED_FIELDS
|
||||
|
||||
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
|
||||
|
@ -1904,13 +1906,18 @@ password resets. You must then provide some key implementation details:
|
|||
...
|
||||
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
|
||||
would be the full name name of the user, but it can be any string that
|
||||
identifies the user.
|
||||
|
||||
.. method:: User.get_short_name():
|
||||
.. method:: User.get_short_name():
|
||||
|
||||
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
|
||||
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue