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:
Russell Keith-Magee 2012-10-13 13:36:07 +08:00
parent c433fcb3fb
commit b3b3db3d95
4 changed files with 111 additions and 95 deletions

View File

@ -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.")

View File

@ -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

View File

@ -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

View File

@ -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):