From 75ff7b8fb8335e181ad05e1d4244b4295cc7a105 Mon Sep 17 00:00:00 2001 From: Anubhav Joshi Date: Fri, 4 Jul 2014 00:00:17 +0530 Subject: [PATCH] Fixed #21832 -- Updated prompt, tests, and docs to show that USERNAME_FIELD supports FK after 9bc2d76. Also added get_input_data() hook in createsuperuser. Thanks Chris Jerdonek and Tim Graham for review. --- .../management/commands/createsuperuser.py | 45 +++++++++++-------- django/contrib/auth/tests/custom_user.py | 6 +-- django/contrib/auth/tests/test_management.py | 21 +++++---- docs/ref/django-admin.txt | 10 +++++ docs/releases/1.8.txt | 3 +- docs/topics/auth/customizing.txt | 9 ++++ 6 files changed, 63 insertions(+), 31 deletions(-) diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index de0b46770a..9faf29e5c6 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -87,20 +87,14 @@ class Command(BaseCommand): # Get a username verbose_field_name = self.username_field.verbose_name while username is None: + input_msg = capfirst(verbose_field_name) + if default_username: + input_msg += " (leave blank to use '%s')" % default_username + username_rel = self.username_field.rel + input_msg = force_str('%s%s: ' % (input_msg, + ' (%s.%s)' % (username_rel.to._meta.object_name, username_rel.field_name) if username_rel else '')) + username = self.get_input_data(self.username_field, input_msg, default_username) if not username: - input_msg = capfirst(verbose_field_name) - if default_username: - input_msg = "%s (leave blank to use '%s')" % ( - input_msg, default_username) - raw_value = input(force_str('%s: ' % input_msg)) - - if default_username and raw_value == '': - raw_value = default_username - try: - 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: self.UserModel._default_manager.db_manager(database).get_by_natural_key(username) @@ -115,12 +109,9 @@ class Command(BaseCommand): 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(force_str('%s%s: ' % (capfirst(field.verbose_name), ' (%s.%s)' % (field.rel.to._meta.object_name, field.rel.field_name) if field.rel else ''))) - try: - user_data[field_name] = field.clean(raw_value, None) - except exceptions.ValidationError as e: - self.stderr.write("Error: %s" % '; '.join(e.messages)) - user_data[field_name] = None + message = force_str('%s%s: ' % (capfirst(field.verbose_name), + ' (%s.%s)' % (field.rel.to._meta.object_name, field.rel.field_name) if field.rel else '')) + user_data[field_name] = self.get_input_data(field, message) # Get a password while password is None: @@ -153,3 +144,19 @@ class Command(BaseCommand): 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 + validation exceptions. + """ + raw_value = input(message) + if default and raw_value == '': + raw_value = default + try: + val = field.clean(raw_value, None) + except exceptions.ValidationError as e: + self.stderr.write("Error: %s" % '; '.join(e.messages)) + val = None + + return val diff --git a/django/contrib/auth/tests/custom_user.py b/django/contrib/auth/tests/custom_user.py index cfa998ad2f..77e8477167 100644 --- a/django/contrib/auth/tests/custom_user.py +++ b/django/contrib/auth/tests/custom_user.py @@ -40,7 +40,7 @@ class CustomUserManager(BaseUserManager): class CustomUserWithFKManager(BaseUserManager): def create_superuser(self, username, email, group, password): - user = self.model(username=username, email_id=email, group_id=group) + user = self.model(username_id=username, email_id=email, group_id=group) user.set_password(password) user.save(using=self._db) return user @@ -96,8 +96,8 @@ class CustomUser(AbstractBaseUser): class CustomUserWithFK(AbstractBaseUser): - username = models.CharField(max_length=30, unique=True) - email = models.ForeignKey(Email, to_field='email') + username = models.ForeignKey(Email, related_name='primary') + email = models.ForeignKey(Email, to_field='email', related_name='secondary') group = models.ForeignKey(Group) custom_objects = CustomUserWithFKManager() diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py index e774d3a071..c3cdc9cc39 100644 --- a/django/contrib/auth/tests/test_management.py +++ b/django/contrib/auth/tests/test_management.py @@ -350,15 +350,15 @@ class CreatesuperuserManagementCommandTestCase(TestCase): self.assertIs(command.stdin, sys.stdin) @override_settings(AUTH_USER_MODEL='auth.CustomUserWithFK') - def test_required_field_with_fk(self): + def test_fields_with_fk(self): new_io = six.StringIO() group = Group.objects.create(name='mygroup') email = Email.objects.create(email='mymail@gmail.com') call_command( 'createsuperuser', interactive=False, - username='user', - email='mymail@gmail.com', + username=email.pk, + email=email.email, group=group.pk, stdout=new_io, skip_checks=True, @@ -366,7 +366,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase): command_output = new_io.getvalue().strip() self.assertEqual(command_output, 'Superuser created successfully.') u = CustomUserWithFK._default_manager.get(email=email) - self.assertEqual(u.username, "user") + self.assertEqual(u.username, email) self.assertEqual(u.group, group) non_existent_email = 'mymail2@gmail.com' @@ -375,19 +375,24 @@ class CreatesuperuserManagementCommandTestCase(TestCase): call_command( 'createsuperuser', interactive=False, - username='user', + username=email.pk, email=non_existent_email, stdout=new_io, skip_checks=True, ) @override_settings(AUTH_USER_MODEL='auth.CustomUserWithFK') - def test_required_fields_with_fk_interactive(self): + def test_fields_with_fk_interactive(self): new_io = six.StringIO() group = Group.objects.create(name='mygroup') email = Email.objects.create(email='mymail@gmail.com') - @mock_inputs({'password': "nopasswd", 'username': "user", 'email': "mymail@gmail.com", 'group': group.pk}) + @mock_inputs({ + 'password': 'nopasswd', + 'username (email.id)': email.pk, + 'email (email.email)': email.email, + 'group (group.id)': group.pk, + }) def test(self): call_command( 'createsuperuser', @@ -400,7 +405,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase): command_output = new_io.getvalue().strip() self.assertEqual(command_output, 'Superuser created successfully.') u = CustomUserWithFK._default_manager.get(email=email) - self.assertEqual(u.username, 'user') + self.assertEqual(u.username, email) self.assertEqual(u.group, group) test(self) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index e67276613a..f6fd81e242 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1478,6 +1478,16 @@ it when running interactively. Use the ``--database`` option to specify the database into which the superuser object will be saved. +.. versionadded:: 1.8 + +You can subclass the management command and override ``get_input_data()`` if you +want to customize data input and validation. Consult the source code for +details on the existing implementation and the method's parameters. For example, +it could be useful if you have a ``ForeignKey`` in +:attr:`~django.contrib.auth.models.CustomUser.REQUIRED_FIELDS` and want to +allow creating an instance instead of entering the primary key of an existing +instance. + ``django.contrib.gis`` ---------------------- diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 631809f5e2..d2c2141415 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -49,7 +49,8 @@ Minor features * The ``max_length`` of :attr:`Permission.name ` has been increased from 50 to 255 characters. Please run the database migration. -* :attr:`~django.contrib.auth.models.CustomUser.REQUIRED_FIELDS` now supports +* :attr:`~django.contrib.auth.models.CustomUser.USERNAME_FIELD` and + :attr:`~django.contrib.auth.models.CustomUser.REQUIRED_FIELDS` now supports :class:`~django.db.models.ForeignKey`\s. :mod:`django.contrib.formtools` diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index 0b0582775a..be065e4757 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -508,6 +508,15 @@ password resets. You must then provide some key implementation details: ... USERNAME_FIELD = 'identifier' + .. versionadded:: 1.8 + + :attr:`USERNAME_FIELD` now supports + :class:`~django.db.models.ForeignKey`\s. Since there is no way to pass + model instances during the :djadmin:`createsuperuser` prompt, expect the + user to enter the value of :attr:`~django.db.models.ForeignKey.to_field` + value (the :attr:`~django.db.models.Field.primary_key` by default) of an + existing instance. + .. attribute:: REQUIRED_FIELDS A list of the field names that will be prompted for when creating a