Fixed #28044 -- Unified the logic for createsuperuser's interactive and --noinput modes.

This commit is contained in:
Dohyeon Kim 2018-05-29 21:41:32 +09:00 committed by Tim Graham
parent 0914a2003b
commit f1f4aeb22e
2 changed files with 109 additions and 73 deletions

View File

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

View File

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