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

This commit is contained in:
Sergey Fedoseev 2016-06-07 13:55:27 +05:00 committed by Tim Graham
parent f6681393d3
commit 21130ce1a9
3 changed files with 75 additions and 0 deletions

View File

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

View File

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

View File

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