Loading django/db/models/fields/related.py +23 −0 Original line number Diff line number Diff line Loading @@ -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: Loading docs/ref/checks.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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. Loading tests/invalid_models_tests/test_relative_fields.py +50 −0 Original line number Diff line number Diff line Loading @@ -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): Loading Loading
django/db/models/fields/related.py +23 −0 Original line number Diff line number Diff line Loading @@ -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: Loading
docs/ref/checks.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
tests/invalid_models_tests/test_relative_fields.py +50 −0 Original line number Diff line number Diff line Loading @@ -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): Loading