Fixed 31207 -- Prevented references to non-local remote fields in ForeignKey.to_field.

Thanks Simon Charette for the initial patch and review.
This commit is contained in:
Hasan Ramezani 2020-01-29 14:29:09 +01:00 committed by Mariusz Felisiak
parent 4e8d89020c
commit a97111eabf
3 changed files with 37 additions and 0 deletions

View File

@ -923,6 +923,21 @@ class ForeignKey(ForeignObject):
}, # 'pk' is included for backwards compatibility }, # 'pk' is included for backwards compatibility
) )
def resolve_related_fields(self):
related_fields = super().resolve_related_fields()
for from_field, to_field in related_fields:
if to_field and to_field.model != self.remote_field.model._meta.concrete_model:
raise exceptions.FieldError(
"'%s.%s' refers to field '%s' which is not local to model "
"'%s'." % (
self.model._meta.label,
self.name,
to_field.name,
self.remote_field.model._meta.concrete_model._meta.label,
)
)
return related_fields
def get_attname(self): def get_attname(self):
return '%s_id' % self.name return '%s_id' % self.name

View File

@ -449,6 +449,9 @@ Miscellaneous
decorator now takes precedence over the ``max-age`` directive from the decorator now takes precedence over the ``max-age`` directive from the
``Cache-Control`` header. ``Cache-Control`` header.
* Providing a non-local remote field in the :attr:`.ForeignKey.to_field`
argument now raises :class:`~django.core.exceptions.FieldError`.
.. _deprecated-features-3.1: .. _deprecated-features-3.1:
Features deprecated in 3.1 Features deprecated in 3.1

View File

@ -2,6 +2,7 @@ from decimal import Decimal
from django.apps import apps from django.apps import apps
from django.core import checks from django.core import checks
from django.core.exceptions import FieldError
from django.db import models from django.db import models
from django.test import TestCase, skipIfDBFeature from django.test import TestCase, skipIfDBFeature
from django.test.utils import isolate_apps from django.test.utils import isolate_apps
@ -128,3 +129,21 @@ class ForeignKeyTests(TestCase):
with self.assertRaisesMessage(ValueError, 'Cannot resolve output_field'): with self.assertRaisesMessage(ValueError, 'Cannot resolve output_field'):
Foo._meta.get_field('bar').get_col('alias') Foo._meta.get_field('bar').get_col('alias')
@isolate_apps('model_fields')
def test_non_local_to_field(self):
class Parent(models.Model):
key = models.IntegerField(unique=True)
class Child(Parent):
pass
class Related(models.Model):
child = models.ForeignKey(Child, on_delete=models.CASCADE, to_field='key')
msg = (
"'model_fields.Related.child' refers to field 'key' which is not "
"local to model 'model_fields.Child'."
)
with self.assertRaisesMessage(FieldError, msg):
Related._meta.get_field('child').related_fields