diff --git a/django/db/models/base.py b/django/db/models/base.py index 36b16c11320c..d1321d654088 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -2220,6 +2220,20 @@ def _check_local_fields(cls, fields, option): 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/django/db/models/indexes.py b/django/db/models/indexes.py index 176f719fe568..a408506527b6 100644 --- a/django/db/models/indexes.py +++ b/django/db/models/indexes.py @@ -114,7 +114,12 @@ def check(self, model, connection): ) errors.extend( model._check_local_fields( - {*self.fields, *self.include, *references}, "indexes" + { + *[field for field, _ in self.fields_orders], + *self.include, + *references, + }, + "indexes", ) ) # Database-feature checks: diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index ab92220ac993..7735eed47898 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 56e4bf2315e2..fd30c66121ee 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 faa0db9da7bd..2a39e250bdb1 100644 --- a/tests/invalid_models_tests/test_models.py +++ b/tests/invalid_models_tests/test_models.py @@ -166,6 +166,54 @@ class Meta: ], ) + 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): @@ -185,6 +233,15 @@ class Meta: ], ) + def test_pointing_to_desc_field(self): + class Model(models.Model): + name = models.CharField(max_length=100) + + class Meta: + indexes = [models.Index(fields=["-name"], name="index_name")] + + self.assertEqual(Model.check(databases=self.databases), []) + def test_pointing_to_m2m_field(self): class Model(models.Model): m2m = models.ManyToManyField("self")