diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index fb3cda7662..541711606c 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -108,6 +108,11 @@ class Command(BaseCommand): if error_msg: raise CommandError(error_msg) + user_data[self.UserModel.USERNAME_FIELD] = username + fake_user_data[self.UserModel.USERNAME_FIELD] = ( + self.username_field.remote_field.model(username) if self.username_field.remote_field + else username + ) # Prompt for required fields. for field_name in self.UserModel.REQUIRED_FIELDS: if not options['interactive']: @@ -134,28 +139,33 @@ class Command(BaseCommand): # Wrap any foreign keys in fake model instances if field.remote_field: fake_user_data[field_name] = field.remote_field.model(input_value) - # Prompt for a password. - while password is None: - password = getpass.getpass() - password2 = getpass.getpass('Password (again): ') - if password != password2: - self.stderr.write("Error: Your passwords didn't match.") - password = None - # Don't validate passwords that don't match. - continue - if password.strip() == '': - self.stderr.write("Error: Blank passwords aren't allowed.") + if options['interactive']: + # Prompt for a password. + while password is None: + password = getpass.getpass() + password2 = getpass.getpass('Password (again): ') + if password != password2: + self.stderr.write("Error: Your passwords didn't match.") + password = None + # Don't validate passwords that don't match. + continue + if password.strip() == '': + self.stderr.write("Error: Blank passwords aren't allowed.") + password = None + # Don't validate blank passwords. + continue + try: + validate_password(password2, self.UserModel(**fake_user_data)) + except exceptions.ValidationError as err: + self.stderr.write('\n'.join(err.messages)) + response = input('Bypass password validation and create user anyway? [y/N]: ') + if response.lower() != 'y': password = None - # Don't validate blank passwords. - continue - try: - validate_password(password2, self.UserModel(**fake_user_data)) - except exceptions.ValidationError as err: - self.stderr.write('\n'.join(err.messages)) - response = input('Bypass password validation and create user anyway? [y/N]: ') - if response.lower() != 'y': - password = None + user_data['password'] = password + self.UserModel._default_manager.db_manager(database).create_superuser(**user_data) + if options['verbosity'] >= 1: + self.stdout.write("Superuser created successfully.") except KeyboardInterrupt: self.stderr.write('\nOperation cancelled.') sys.exit(1) @@ -168,13 +178,6 @@ class Command(BaseCommand): 'to create one manually.' ) - if username: - user_data[self.UserModel.USERNAME_FIELD] = username - user_data['password'] = password - self.UserModel._default_manager.db_manager(database).create_superuser(**user_data) - if options['verbosity'] >= 1: - self.stdout.write("Superuser created successfully.") - def get_input_data(self, field, message, default=None): """ Override this method if you want to customize data inputs or diff --git a/tests/auth_tests/models/custom_user.py b/tests/auth_tests/models/custom_user.py index ad8b4c1212..a46f1d5a9c 100644 --- a/tests/auth_tests/models/custom_user.py +++ b/tests/auth_tests/models/custom_user.py @@ -9,7 +9,7 @@ from django.db import models # that every user provide a date of birth. This lets us test # changes in username datatype, and non-text required fields. class CustomUserManager(BaseUserManager): - def create_user(self, email, date_of_birth, password=None): + def create_user(self, email, date_of_birth, password=None, **fields): """ Creates and saves a User with the given email and password. """ @@ -19,14 +19,15 @@ class CustomUserManager(BaseUserManager): user = self.model( email=self.normalize_email(email), date_of_birth=date_of_birth, + **fields ) user.set_password(password) user.save(using=self._db) return user - def create_superuser(self, email, password, date_of_birth): - u = self.create_user(email, password=password, date_of_birth=date_of_birth) + def create_superuser(self, email, password, date_of_birth, **fields): + u = self.create_user(email, password=password, date_of_birth=date_of_birth, **fields) u.is_admin = True u.save(using=self._db) return u @@ -37,11 +38,12 @@ class CustomUser(AbstractBaseUser): is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) date_of_birth = models.DateField() + first_name = models.CharField(max_length=50) custom_objects = CustomUserManager() USERNAME_FIELD = 'email' - REQUIRED_FIELDS = ['date_of_birth'] + REQUIRED_FIELDS = ['date_of_birth', 'first_name'] def __str__(self): return self.email diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py index da0ec73626..0c9ad81f3f 100644 --- a/tests/auth_tests/test_management.py +++ b/tests/auth_tests/test_management.py @@ -29,6 +29,8 @@ MOCK_INPUT_KEY_TO_PROMPTS = { # @mock_inputs dict key: [expected prompt messages], 'bypass': ['Bypass password validation and create user anyway? [y/N]: '], 'email': ['Email address: '], + 'date_of_birth': ['Date of birth: '], + 'first_name': ['First name: '], 'username': ['Username: ', lambda: "Username (leave blank to use '%s'): " % get_default_username()], } @@ -327,6 +329,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase): interactive=False, email="joe@somewhere.org", date_of_birth="1976-04-01", + first_name='Joe', stdout=new_io, ) command_output = new_io.getvalue().strip() @@ -552,6 +555,85 @@ class CreatesuperuserManagementCommandTestCase(TestCase): test(self) + @override_settings(AUTH_PASSWORD_VALIDATORS=[ + {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, + ]) + def test_validate_password_against_username(self): + new_io = StringIO() + username = 'supremelycomplex' + + def bad_then_good_password(index=[0]): + """Return username the first two times, then a valid password.""" + index[0] += 1 + if index[0] <= 2: + return username + return 'superduperunguessablepassword' + + @mock_inputs({ + 'password': bad_then_good_password, + 'username': username, + 'email': '', + 'bypass': 'n', + }) + def test(self): + call_command( + 'createsuperuser', + interactive=True, + stdin=MockTTY(), + stdout=new_io, + stderr=new_io, + ) + self.assertEqual( + new_io.getvalue().strip(), + 'The password is too similar to the username.\n' + 'Superuser created successfully.' + ) + + test(self) + + @override_settings( + AUTH_USER_MODEL='auth_tests.CustomUser', + AUTH_PASSWORD_VALIDATORS=[ + {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, + ] + ) + def test_validate_password_against_required_fields(self): + new_io = StringIO() + username = 'josephine' + + # Returns the username the first two times it's called, then a valid + # password. + def bad_then_good_password(index=[0]): + """Return username the first two times, then a valid password.""" + index[0] += 1 + if index[0] <= 2: + return username + return 'superduperunguessablepassword' + + @mock_inputs({ + 'password': bad_then_good_password, + 'username': username, + 'first_name': 'josephine', + 'date_of_birth': '1970-01-01', + 'email': 'joey@example.com', + 'bypass': 'n', + }) + def test(self): + call_command( + 'createsuperuser', + interactive=True, + stdin=MockTTY(), + stdout=new_io, + stderr=new_io, + ) + self.assertEqual( + new_io.getvalue().strip(), + "The password is too similar to the first name.\n" + "Superuser created successfully." + ) + + test(self) + def test_blank_username(self): """Creation fails if --username is blank.""" new_io = StringIO()