1
0
mirror of https://github.com/django/django.git synced 2025-08-16 23:09:13 +00:00

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,6 +1878,8 @@ 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:
.. class:: models.CustomUser
.. attribute:: User.USERNAME_FIELD .. 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
@ -1904,6 +1906,11 @@ password resets. You must then provide some key implementation details:
... ...
REQUIRED_FIELDS = ['date_of_birth', 'height'] REQUIRED_FIELDS = ['date_of_birth', 'height']
.. note::
``REQUIRED_FIELDS`` must contain all required fields on your User
model, but should *not* contain the ``USERNAME_FIELD``.
.. method:: User.get_full_name(): .. 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
@ -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):