Fixed #28044 -- Unified the logic for createsuperuser's interactive and --noinput modes.
This commit is contained in:
parent
0914a2003b
commit
f1f4aeb22e
|
@ -69,34 +69,19 @@ class Command(BaseCommand):
|
||||||
# instead of raw IDs.
|
# instead of raw IDs.
|
||||||
fake_user_data = {}
|
fake_user_data = {}
|
||||||
verbose_field_name = self.username_field.verbose_name
|
verbose_field_name = self.username_field.verbose_name
|
||||||
|
|
||||||
# Do quick and dirty validation if --noinput
|
|
||||||
if not options['interactive']:
|
|
||||||
try:
|
try:
|
||||||
if not username:
|
if options['interactive']:
|
||||||
raise CommandError("You must use --%s with --noinput." % self.UserModel.USERNAME_FIELD)
|
|
||||||
username = self.username_field.clean(username, None)
|
|
||||||
|
|
||||||
for field_name in self.UserModel.REQUIRED_FIELDS:
|
|
||||||
if options[field_name]:
|
|
||||||
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:
|
|
||||||
raise CommandError('; '.join(e.messages))
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Prompt for username/password, and any other required fields.
|
|
||||||
# Enclose this whole thing in a try/except to catch
|
|
||||||
# KeyboardInterrupt and exit gracefully.
|
|
||||||
default_username = get_default_username()
|
|
||||||
try:
|
|
||||||
|
|
||||||
if hasattr(self.stdin, 'isatty') and not self.stdin.isatty():
|
if hasattr(self.stdin, 'isatty') and not self.stdin.isatty():
|
||||||
raise NotRunningInTTYException("Not running in a TTY")
|
raise NotRunningInTTYException
|
||||||
|
default_username = get_default_username()
|
||||||
# Get a username
|
if username:
|
||||||
|
error_msg = self._validate_username(username, verbose_field_name, database)
|
||||||
|
if error_msg:
|
||||||
|
self.stderr.write(error_msg)
|
||||||
|
username = None
|
||||||
|
elif username == '':
|
||||||
|
raise CommandError('%s cannot be blank.' % capfirst(verbose_field_name))
|
||||||
|
# Prompt for username.
|
||||||
while username is None:
|
while username is None:
|
||||||
input_msg = capfirst(verbose_field_name)
|
input_msg = capfirst(verbose_field_name)
|
||||||
if default_username:
|
if default_username:
|
||||||
|
@ -110,21 +95,29 @@ class Command(BaseCommand):
|
||||||
) if username_rel else ''
|
) if username_rel else ''
|
||||||
)
|
)
|
||||||
username = self.get_input_data(self.username_field, input_msg, default_username)
|
username = self.get_input_data(self.username_field, input_msg, default_username)
|
||||||
if not username:
|
if username:
|
||||||
continue
|
error_msg = self._validate_username(username, verbose_field_name, database)
|
||||||
if self.username_field.unique:
|
if error_msg:
|
||||||
try:
|
self.stderr.write(error_msg)
|
||||||
self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
|
|
||||||
except self.UserModel.DoesNotExist:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.stderr.write("Error: That %s is already taken." % verbose_field_name)
|
|
||||||
username = None
|
username = None
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if username is None:
|
||||||
|
raise CommandError('You must use --%s with --noinput.' % self.UserModel.USERNAME_FIELD)
|
||||||
|
else:
|
||||||
|
error_msg = self._validate_username(username, verbose_field_name, database)
|
||||||
|
if error_msg:
|
||||||
|
raise CommandError(error_msg)
|
||||||
|
|
||||||
if not username:
|
# Prompt for required fields.
|
||||||
raise CommandError('%s cannot be blank.' % capfirst(verbose_field_name))
|
|
||||||
|
|
||||||
for field_name in self.UserModel.REQUIRED_FIELDS:
|
for field_name in self.UserModel.REQUIRED_FIELDS:
|
||||||
|
if not options['interactive']:
|
||||||
|
if options[field_name]:
|
||||||
|
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)
|
||||||
|
else:
|
||||||
field = self.UserModel._meta.get_field(field_name)
|
field = self.UserModel._meta.get_field(field_name)
|
||||||
user_data[field_name] = options[field_name]
|
user_data[field_name] = options[field_name]
|
||||||
while user_data[field_name] is None:
|
while user_data[field_name] is None:
|
||||||
|
@ -142,8 +135,7 @@ class Command(BaseCommand):
|
||||||
# Wrap any foreign keys in fake model instances
|
# Wrap any foreign keys in fake model instances
|
||||||
if field.remote_field:
|
if field.remote_field:
|
||||||
fake_user_data[field_name] = field.remote_field.model(input_value)
|
fake_user_data[field_name] = field.remote_field.model(input_value)
|
||||||
|
# Prompt for a password.
|
||||||
# Get a password
|
|
||||||
while password is None:
|
while password is None:
|
||||||
password = getpass.getpass()
|
password = getpass.getpass()
|
||||||
password2 = getpass.getpass('Password (again): ')
|
password2 = getpass.getpass('Password (again): ')
|
||||||
|
@ -158,7 +150,6 @@ class Command(BaseCommand):
|
||||||
password = None
|
password = None
|
||||||
# Don't validate blank passwords.
|
# Don't validate blank passwords.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validate_password(password2, self.UserModel(**fake_user_data))
|
validate_password(password2, self.UserModel(**fake_user_data))
|
||||||
except exceptions.ValidationError as err:
|
except exceptions.ValidationError as err:
|
||||||
|
@ -166,16 +157,16 @@ class Command(BaseCommand):
|
||||||
response = input('Bypass password validation and create user anyway? [y/N]: ')
|
response = input('Bypass password validation and create user anyway? [y/N]: ')
|
||||||
if response.lower() != 'y':
|
if response.lower() != 'y':
|
||||||
password = None
|
password = None
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.stderr.write("\nOperation cancelled.")
|
self.stderr.write('\nOperation cancelled.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
except exceptions.ValidationError as e:
|
||||||
|
raise CommandError('; '.join(e.messages))
|
||||||
except NotRunningInTTYException:
|
except NotRunningInTTYException:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
"Superuser creation skipped due to not running in a TTY. "
|
'Superuser creation skipped due to not running in a TTY. '
|
||||||
"You can run `manage.py createsuperuser` in your project "
|
'You can run `manage.py createsuperuser` in your project '
|
||||||
"to create one manually."
|
'to create one manually.'
|
||||||
)
|
)
|
||||||
|
|
||||||
if username:
|
if username:
|
||||||
|
@ -200,3 +191,19 @@ class Command(BaseCommand):
|
||||||
val = None
|
val = None
|
||||||
|
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
def _validate_username(self, username, verbose_field_name, database):
|
||||||
|
"""Validate username. If invalid, return a string error message."""
|
||||||
|
if self.username_field.unique:
|
||||||
|
try:
|
||||||
|
self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
|
||||||
|
except self.UserModel.DoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return 'Error: That %s is already taken.' % verbose_field_name
|
||||||
|
if not username:
|
||||||
|
return '%s cannot be blank.' % capfirst(verbose_field_name)
|
||||||
|
try:
|
||||||
|
self.username_field.clean(username, None)
|
||||||
|
except exceptions.ValidationError as e:
|
||||||
|
return '; '.join(e.messages)
|
||||||
|
|
|
@ -568,6 +568,22 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
||||||
|
|
||||||
test(self)
|
test(self)
|
||||||
|
|
||||||
|
def test_blank_username_non_interactive(self):
|
||||||
|
new_io = StringIO()
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
with self.assertRaisesMessage(CommandError, 'Username cannot be blank.'):
|
||||||
|
call_command(
|
||||||
|
'createsuperuser',
|
||||||
|
username='',
|
||||||
|
interactive=False,
|
||||||
|
stdin=MockTTY(),
|
||||||
|
stdout=new_io,
|
||||||
|
stderr=new_io,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(self)
|
||||||
|
|
||||||
def test_password_validation_bypass(self):
|
def test_password_validation_bypass(self):
|
||||||
"""
|
"""
|
||||||
Password validation can be bypassed by entering 'y' at the prompt.
|
Password validation can be bypassed by entering 'y' at the prompt.
|
||||||
|
@ -672,6 +688,19 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
||||||
|
|
||||||
test(self)
|
test(self)
|
||||||
|
|
||||||
|
def test_existing_username_non_interactive(self):
|
||||||
|
"""Creation fails if the username already exists."""
|
||||||
|
User.objects.create(username='janet')
|
||||||
|
new_io = StringIO()
|
||||||
|
with self.assertRaisesMessage(CommandError, "Error: That username is already taken."):
|
||||||
|
call_command(
|
||||||
|
'createsuperuser',
|
||||||
|
username='janet',
|
||||||
|
email='',
|
||||||
|
interactive=False,
|
||||||
|
stdout=new_io,
|
||||||
|
)
|
||||||
|
|
||||||
def test_validation_mismatched_passwords(self):
|
def test_validation_mismatched_passwords(self):
|
||||||
"""
|
"""
|
||||||
Creation should fail if the user enters mismatched passwords.
|
Creation should fail if the user enters mismatched passwords.
|
||||||
|
|
Loading…
Reference in New Issue