From a97111eabf39e50625d7a8acc6cd46f4c5dad0da Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Wed, 29 Jan 2020 14:29:09 +0100 Subject: [PATCH] Fixed 31207 -- Prevented references to non-local remote fields in ForeignKey.to_field. Thanks Simon Charette for the initial patch and review. --- django/db/models/fields/related.py | 15 +++++++++++++++ docs/releases/3.1.txt | 3 +++ tests/model_fields/test_foreignkey.py | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 75b533b0a8..88bc0b5c15 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -923,6 +923,21 @@ class ForeignKey(ForeignObject): }, # '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): return '%s_id' % self.name diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 221cf39dee..40380b6274 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -449,6 +449,9 @@ Miscellaneous decorator now takes precedence over the ``max-age`` directive from the ``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: Features deprecated in 3.1 diff --git a/tests/model_fields/test_foreignkey.py b/tests/model_fields/test_foreignkey.py index 51e76c4052..d30cca9b5c 100644 --- a/tests/model_fields/test_foreignkey.py +++ b/tests/model_fields/test_foreignkey.py @@ -2,6 +2,7 @@ from decimal import Decimal from django.apps import apps from django.core import checks +from django.core.exceptions import FieldError from django.db import models from django.test import TestCase, skipIfDBFeature from django.test.utils import isolate_apps @@ -128,3 +129,21 @@ class ForeignKeyTests(TestCase): with self.assertRaisesMessage(ValueError, 'Cannot resolve output_field'): 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