Fixed #21755 -- Added ForeignKey support to REQUIRED_FIELDS.
This allows specifying ForeignKeys in REQUIRED_FIELDS when using a custom User model. Thanks cjerdonek and bmispelon for suggestion and timgraham for review.
This commit is contained in:
parent
23d68c0f0d
commit
9bc2d766a0
|
@ -115,7 +115,7 @@ class Command(BaseCommand):
|
||||||
field = self.UserModel._meta.get_field(field_name)
|
field = self.UserModel._meta.get_field(field_name)
|
||||||
user_data[field_name] = options.get(field_name)
|
user_data[field_name] = options.get(field_name)
|
||||||
while user_data[field_name] is None:
|
while user_data[field_name] is None:
|
||||||
raw_value = input(force_str('%s: ' % capfirst(field.verbose_name)))
|
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:
|
try:
|
||||||
user_data[field_name] = field.clean(raw_value, None)
|
user_data[field_name] = field.clean(raw_value, None)
|
||||||
except exceptions.ValidationError as e:
|
except exceptions.ValidationError as e:
|
||||||
|
|
|
@ -38,6 +38,18 @@ class CustomUserManager(BaseUserManager):
|
||||||
return u
|
return u
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserWithFKManager(BaseUserManager):
|
||||||
|
def create_superuser(self, username, email, group, password):
|
||||||
|
user = self.model(username=username, email_id=email, group_id=group)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save(using=self._db)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class Email(models.Model):
|
||||||
|
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
|
||||||
|
|
||||||
|
|
||||||
class CustomUser(AbstractBaseUser):
|
class CustomUser(AbstractBaseUser):
|
||||||
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
|
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
|
||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
|
@ -83,6 +95,20 @@ class CustomUser(AbstractBaseUser):
|
||||||
return self.is_admin
|
return self.is_admin
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserWithFK(AbstractBaseUser):
|
||||||
|
username = models.CharField(max_length=30, unique=True)
|
||||||
|
email = models.ForeignKey(Email, to_field='email')
|
||||||
|
group = models.ForeignKey(Group)
|
||||||
|
|
||||||
|
custom_objects = CustomUserWithFKManager()
|
||||||
|
|
||||||
|
USERNAME_FIELD = 'username'
|
||||||
|
REQUIRED_FIELDS = ['email', 'group']
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = 'auth'
|
||||||
|
|
||||||
|
|
||||||
# At this point, temporarily remove the groups and user_permissions M2M
|
# At this point, temporarily remove the groups and user_permissions M2M
|
||||||
# fields from the AbstractUser class, so they don't clash with the related_name
|
# fields from the AbstractUser class, so they don't clash with the related_name
|
||||||
# that sets.
|
# that sets.
|
||||||
|
|
|
@ -9,8 +9,8 @@ from django.contrib.auth import models, management
|
||||||
from django.contrib.auth.checks import check_user_model
|
from django.contrib.auth.checks import check_user_model
|
||||||
from django.contrib.auth.management import create_permissions
|
from django.contrib.auth.management import create_permissions
|
||||||
from django.contrib.auth.management.commands import changepassword, createsuperuser
|
from django.contrib.auth.management.commands import changepassword, createsuperuser
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User, Group
|
||||||
from django.contrib.auth.tests.custom_user import CustomUser
|
from django.contrib.auth.tests.custom_user import CustomUser, CustomUserWithFK, Email
|
||||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core import checks
|
from django.core import checks
|
||||||
|
@ -349,6 +349,62 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
||||||
)
|
)
|
||||||
self.assertIs(command.stdin, sys.stdin)
|
self.assertIs(command.stdin, sys.stdin)
|
||||||
|
|
||||||
|
@override_settings(AUTH_USER_MODEL='auth.CustomUserWithFK')
|
||||||
|
def test_required_field_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',
|
||||||
|
group=group.pk,
|
||||||
|
stdout=new_io,
|
||||||
|
skip_checks=True,
|
||||||
|
)
|
||||||
|
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.group, group)
|
||||||
|
|
||||||
|
non_existent_email = 'mymail2@gmail.com'
|
||||||
|
with self.assertRaisesMessage(CommandError,
|
||||||
|
'email instance with email %r does not exist.' % non_existent_email):
|
||||||
|
call_command(
|
||||||
|
'createsuperuser',
|
||||||
|
interactive=False,
|
||||||
|
username='user',
|
||||||
|
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):
|
||||||
|
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})
|
||||||
|
def test(self):
|
||||||
|
call_command(
|
||||||
|
'createsuperuser',
|
||||||
|
interactive=True,
|
||||||
|
stdout=new_io,
|
||||||
|
stdin=MockTTY(),
|
||||||
|
skip_checks=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
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.group, group)
|
||||||
|
|
||||||
|
test(self)
|
||||||
|
|
||||||
|
|
||||||
class CustomUserModelValidationTestCase(TestCase):
|
class CustomUserModelValidationTestCase(TestCase):
|
||||||
@override_settings(AUTH_USER_MODEL='auth.CustomUserNonListRequiredFields')
|
@override_settings(AUTH_USER_MODEL='auth.CustomUserNonListRequiredFields')
|
||||||
|
|
|
@ -286,9 +286,9 @@ class ConnectionRouter(object):
|
||||||
chosen_db = method(model, **hints)
|
chosen_db = method(model, **hints)
|
||||||
if chosen_db:
|
if chosen_db:
|
||||||
return chosen_db
|
return chosen_db
|
||||||
try:
|
instance = hints.get('instance')
|
||||||
return hints['instance']._state.db or DEFAULT_DB_ALIAS
|
if instance is not None and instance._state.db:
|
||||||
except KeyError:
|
return instance._state.db
|
||||||
return DEFAULT_DB_ALIAS
|
return DEFAULT_DB_ALIAS
|
||||||
return _route_db
|
return _route_db
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,8 @@ Minor features
|
||||||
* The ``max_length`` of :attr:`Permission.name
|
* The ``max_length`` of :attr:`Permission.name
|
||||||
<django.contrib.auth.models.Permission.name>` has been increased from 50 to
|
<django.contrib.auth.models.Permission.name>` has been increased from 50 to
|
||||||
255 characters. Please run the database migration.
|
255 characters. Please run the database migration.
|
||||||
|
* :attr:`~django.contrib.auth.models.CustomUser.REQUIRED_FIELDS` now supports
|
||||||
|
:class:`~django.db.models.ForeignKey`\s.
|
||||||
|
|
||||||
:mod:`django.contrib.formtools`
|
:mod:`django.contrib.formtools`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -515,8 +515,7 @@ password resets. You must then provide some key implementation details:
|
||||||
will be prompted to supply a value for each of these fields. It must
|
will be prompted to supply a value for each of these fields. It must
|
||||||
include any field for which :attr:`~django.db.models.Field.blank` is
|
include any field for which :attr:`~django.db.models.Field.blank` is
|
||||||
``False`` or undefined and may include additional fields you want
|
``False`` or undefined and may include additional fields you want
|
||||||
prompted for when a user is created interactively. However, it will not
|
prompted for when a user is created interactively.
|
||||||
work for :class:`~django.db.models.ForeignKey` fields.
|
|
||||||
``REQUIRED_FIELDS`` has no effect in other parts of Django, like
|
``REQUIRED_FIELDS`` has no effect in other parts of Django, like
|
||||||
creating a user in the admin.
|
creating a user in the admin.
|
||||||
|
|
||||||
|
@ -536,6 +535,15 @@ password resets. You must then provide some key implementation details:
|
||||||
``User`` model, but should *not* contain the ``USERNAME_FIELD`` or
|
``User`` model, but should *not* contain the ``USERNAME_FIELD`` or
|
||||||
``password`` as these fields will always be prompted for.
|
``password`` as these fields will always be prompted for.
|
||||||
|
|
||||||
|
.. versionadded:: 1.8
|
||||||
|
|
||||||
|
:attr:`REQUIRED_FIELDS` 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:: is_active
|
.. attribute:: is_active
|
||||||
|
|
||||||
A boolean attribute that indicates whether the user is considered
|
A boolean attribute that indicates whether the user is considered
|
||||||
|
|
Loading…
Reference in New Issue