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.
This commit is contained in:
Anubhav Joshi 2014-07-04 00:00:17 +05:30 committed by Tim Graham
parent 136a3ffe21
commit 75ff7b8fb8
6 changed files with 63 additions and 31 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -49,7 +49,8 @@ Minor features
* The ``max_length`` of :attr:`Permission.name
<django.contrib.auth.models.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`

View File

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