Commit 21130ce1 authored by Sergey Fedoseev's avatar Sergey Fedoseev Committed by Tim Graham
Browse files

Fixed #26718 -- Added system check for existence of the fields specified by ForeignKey.to_field.

parent f6681393
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -463,9 +463,32 @@ class ForeignObject(RelatedField):

    def check(self, **kwargs):
        errors = super(ForeignObject, self).check(**kwargs)
        errors.extend(self._check_to_fields_exist())
        errors.extend(self._check_unique_target())
        return errors

    def _check_to_fields_exist(self):
        # Skip nonexistent models.
        if isinstance(self.remote_field.model, six.string_types):
            return []

        errors = []
        for to_field in self.to_fields:
            if to_field:
                try:
                    self.remote_field.model._meta.get_field(to_field)
                except exceptions.FieldDoesNotExist:
                    errors.append(
                        checks.Error(
                            "The to_field '%s' doesn't exist on the related "
                            "model '%s'."
                            % (to_field, self.remote_field.model._meta.label),
                            obj=self,
                            id='fields.E312',
                        )
                    )
        return errors

    def _check_unique_target(self):
        rel_is_string = isinstance(self.remote_field.model, six.string_types)
        if rel_is_string or not self.requires_unique_target:
+2 −0
Original line number Diff line number Diff line
@@ -211,6 +211,8 @@ Related Fields
  add at least a subset of them to a unique_together constraint.
* **fields.E311**: ``<model>`` must set ``unique=True`` because it is
  referenced by a ``ForeignKey``.
* **fields.E312**: The ``to_field`` ``<field name>`` doesn't exist on the
  related model ``<app label>.<model>``.
* **fields.E320**: Field specifies ``on_delete=SET_NULL``, but cannot be null.
* **fields.E321**: The field specifies ``on_delete=SET_DEFAULT``, but has no
  default value.
+50 −0
Original line number Diff line number Diff line
@@ -763,6 +763,56 @@ class RelativeFieldTests(SimpleTestCase):
            errors = Child.check()
            self.assertFalse(errors)

    def test_to_fields_exist(self):
        class Parent(models.Model):
            pass

        class Child(models.Model):
            a = models.PositiveIntegerField()
            b = models.PositiveIntegerField()
            parent = ForeignObject(
                Parent,
                on_delete=models.SET_NULL,
                from_fields=('a', 'b'),
                to_fields=('a', 'b'),
            )

        field = Child._meta.get_field('parent')
        expected = [
            Error(
                "The to_field 'a' doesn't exist on the related model 'invalid_models_tests.Parent'.",
                obj=field,
                id='fields.E312',
            ),
            Error(
                "The to_field 'b' doesn't exist on the related model 'invalid_models_tests.Parent'.",
                obj=field,
                id='fields.E312',
            ),
        ]
        self.assertEqual(field.check(), expected)

    def test_to_fields_not_checked_if_related_model_doesnt_exist(self):
        class Child(models.Model):
            a = models.PositiveIntegerField()
            b = models.PositiveIntegerField()
            parent = ForeignObject(
                'invalid_models_tests.Parent',
                on_delete=models.SET_NULL,
                from_fields=('a', 'b'),
                to_fields=('a', 'b'),
            )

        field = Child._meta.get_field('parent')
        self.assertEqual(field.check(), [
            Error(
                "Field defines a relation with model 'invalid_models_tests.Parent', "
                "which is either not installed, or is abstract.",
                id='fields.E300',
                obj=field,
            ),
        ])


@isolate_apps('invalid_models_tests')
class AccessorClashTests(SimpleTestCase):