From 0fa339ce71bae6f9cebe341876f6aa41840ca2ff Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Fri, 19 Sep 2025 16:27:51 +0200 Subject: [PATCH] [6.0.x] Fixed #36611, Refs #36580 -- Added system check for multicolumn ForeignObject in Meta.indexes/constraints/unique_together. ForeignObjects with multiple `from_fields` are not supported in these options. Co-authored-by: Jacob Walls Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Backport of 5b51e6f759f2ba993219347435149173c756c478 from main. --- django/db/models/base.py | 14 +++++++ docs/ref/checks.txt | 3 ++ docs/releases/6.0.txt | 4 ++ tests/invalid_models_tests/test_models.py | 48 +++++++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/django/db/models/base.py b/django/db/models/base.py index 36b16c1132..d1321d6540 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -2220,6 +2220,20 @@ class Model(AltersData, metaclass=ModelBase): id="models.E048", ) ) + elif ( + isinstance(field.remote_field, ForeignObjectRel) + and field not in cls._meta.local_concrete_fields + and len(field.from_fields) > 1 + ): + errors.append( + checks.Error( + f"{option!r} refers to a ForeignObject {field_name!r} with " + "multiple 'from_fields', which is not supported for that " + "option.", + obj=cls, + id="models.E049", + ) + ) elif field not in cls._meta.local_fields: errors.append( checks.Error( diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index ab92220ac9..7735eed478 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -443,6 +443,9 @@ Models * **models.E048**: ``constraints/indexes/unique_together`` refers to a ``CompositePrimaryKey`` ````, but ``CompositePrimaryKey``\s are not supported for that option. +* **models.E049**: ``constraints/indexes/unique_together`` refers to a + ``ForeignObject`` ```` with multiple ``from_fields``, which is + not supported for that option. Management Commands ------------------- diff --git a/docs/releases/6.0.txt b/docs/releases/6.0.txt index 56e4bf2315..fd30c66121 100644 --- a/docs/releases/6.0.txt +++ b/docs/releases/6.0.txt @@ -295,6 +295,10 @@ Models don't support it (MySQL and MariaDB), the fields are marked as deferred to trigger a refresh on subsequent accesses. +* Using a :ref:`ForeignObject ` with multiple + ``from_fields`` in Model indexes, constraints, or :attr:`unique_together + ` now emits a system check error. + Pagination ~~~~~~~~~~ diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py index 5780704eae..2a39e250bd 100644 --- a/tests/invalid_models_tests/test_models.py +++ b/tests/invalid_models_tests/test_models.py @@ -166,6 +166,54 @@ class UniqueTogetherTests(SimpleTestCase): ], ) + def test_pointing_to_foreign_object(self): + class Reference(models.Model): + reference_id = models.IntegerField(unique=True) + + class ReferenceTab(models.Model): + reference_id = models.IntegerField() + reference = models.ForeignObject( + Reference, + from_fields=["reference_id"], + to_fields=["reference_id"], + on_delete=models.CASCADE, + ) + + class Meta: + unique_together = [["reference"]] + + self.assertEqual(ReferenceTab.check(), []) + + def test_pointing_to_foreign_object_multi_column(self): + class Reference(models.Model): + reference_id = models.IntegerField(unique=True) + code = models.CharField(max_length=1) + + class ReferenceTabMultiple(models.Model): + reference_id = models.IntegerField() + code = models.CharField(max_length=1) + reference = models.ForeignObject( + Reference, + from_fields=["reference_id", "code"], + to_fields=["reference_id", "code"], + on_delete=models.CASCADE, + ) + + class Meta: + unique_together = [["reference"]] + + self.assertEqual( + ReferenceTabMultiple.check(), + [ + Error( + "'unique_together' refers to a ForeignObject 'reference' with " + "multiple 'from_fields', which is not supported for that option.", + obj=ReferenceTabMultiple, + id="models.E049", + ), + ], + ) + @isolate_apps("invalid_models_tests") class IndexesTests(TestCase):