Commit 9bc2d766 authored by Anubhav Joshi's avatar Anubhav Joshi Committed by Tim Graham
Browse files

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.
parent 23d68c0f
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -115,7 +115,7 @@ 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: ' % 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:
                            user_data[field_name] = field.clean(raw_value, None)
                        except exceptions.ValidationError as e:
+26 −0
Original line number Diff line number Diff line
@@ -38,6 +38,18 @@ class CustomUserManager(BaseUserManager):
        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):
    email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
    is_active = models.BooleanField(default=True)
@@ -83,6 +95,20 @@ class CustomUser(AbstractBaseUser):
        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
# fields from the AbstractUser class, so they don't clash with the related_name
# that sets.
+58 −2
Original line number Diff line number Diff line
@@ -9,8 +9,8 @@ from django.contrib.auth import models, management
from django.contrib.auth.checks import check_user_model
from django.contrib.auth.management import create_permissions
from django.contrib.auth.management.commands import changepassword, createsuperuser
from django.contrib.auth.models import User
from django.contrib.auth.tests.custom_user import CustomUser
from django.contrib.auth.models import User, Group
from django.contrib.auth.tests.custom_user import CustomUser, CustomUserWithFK, Email
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.contenttypes.models import ContentType
from django.core import checks
@@ -349,6 +349,62 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
        )
        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):
    @override_settings(AUTH_USER_MODEL='auth.CustomUserNonListRequiredFields')
+4 −4
Original line number Diff line number Diff line
@@ -286,9 +286,9 @@ class ConnectionRouter(object):
                    chosen_db = method(model, **hints)
                    if chosen_db:
                        return chosen_db
            try:
                return hints['instance']._state.db or DEFAULT_DB_ALIAS
            except KeyError:
            instance = hints.get('instance')
            if instance is not None and instance._state.db:
                return instance._state.db
            return DEFAULT_DB_ALIAS
        return _route_db

+2 −0
Original line number Diff line number Diff line
@@ -49,6 +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
  :class:`~django.db.models.ForeignKey`\s.

:mod:`django.contrib.formtools`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Loading