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

View File

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

View File

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

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