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.'),
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 '
'any other required field. Superusers created with --noinput will '
' not be able to log in until they\'re given a valid password.')),
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
)
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 --%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.' %
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 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,47 +1878,54 @@ 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
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
some kind, but it can also be an email address, or any other unique
identifier. In the following example, the field `identifier` is used
as the identifying field::
.. attribute:: User.USERNAME_FIELD
class MyUser(AbstractBaseUser):
identfier = models.CharField(max_length=40, unique=True, db_index=True)
...
USERNAME_FIELD = 'identifier'
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
some kind, but it can also be an email address, or any other unique
identifier. In the following example, the field `identifier` is used
as the identifying field::
.. attribute:: User.REQUIRED_FIELDS
class MyUser(AbstractBaseUser):
identfier = models.CharField(max_length=40, unique=True, db_index=True)
...
USERNAME_FIELD = 'identifier'
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
that defines two required fields - a date of birth and height::
.. attribute:: User.REQUIRED_FIELDS
class MyUser(AbstractBaseUser):
...
date_of_birth = models.DateField()
height = models.FloatField()
...
REQUIRED_FIELDS = ['date_of_birth', 'height']
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
that defines two required fields - a date of birth and height::
.. method:: User.get_full_name():
class MyUser(AbstractBaseUser):
...
date_of_birth = models.DateField()
height = models.FloatField()
...
REQUIRED_FIELDS = ['date_of_birth', 'height']
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.
.. note::
.. method:: User.get_short_name():
``REQUIRED_FIELDS`` must contain all required fields on your User
model, but should *not* contain the ``USERNAME_FIELD``.
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
identifies the user in an informal way. It may also return the same
value as :meth:`django.contrib.auth.User.get_full_name()`.
.. 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():
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
identifies the user in an informal way. It may also return the same
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,33 +1986,36 @@ 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):
# create user here
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::
.. method:: UserManager.create_superuser(username, password, **other_fields)
def create_user(self, email, date_of_birth, password=None):
# create user here
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::
.. method:: models.CustomUserManager.create_superuser(*username_field*, password, **other_fields)
def create_superuser(self, username, date_of_birth, password):
# create superuser here
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::
Unlike `create_user()`, `create_superuser()` *must* require the caller
to provider a password.
def create_superuser(self, email, date_of_birth, password):
# create superuser here
Unlike `create_user()`, `create_superuser()` *must* require the caller
to provider a password.
:class:`~django.contrib.auth.models.BaseUserManager` provides the following
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):