From 3c7bf39e23fe9168f31132d929c9877c5835859b Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Mon, 4 May 2020 23:27:13 +0200 Subject: [PATCH] Fixed #31410 -- Added system checks for invalid model field names in UniqueConstraint. --- django/db/models/base.py | 6 ++ docs/ref/checks.txt | 14 ++--- tests/invalid_models_tests/test_models.py | 67 +++++++++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 7bb3fa4706..0955a5a579 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1926,6 +1926,12 @@ class Model(metaclass=ModelBase): id='models.W038', ) ) + fields = ( + field + for constraint in cls._meta.constraints if isinstance(constraint, UniqueConstraint) + for field in constraint.fields + ) + errors.extend(cls._check_local_fields(fields, 'constraints')) return errors diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 1259a4a285..09bb799ebe 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -308,17 +308,17 @@ Models * **models.E009**: All ``index_together`` elements must be lists or tuples. * **models.E010**: ``unique_together`` must be a list or tuple. * **models.E011**: All ``unique_together`` elements must be lists or tuples. -* **models.E012**: ``indexes/index_together/unique_together`` refers to the - nonexistent field ````. -* **models.E013**: ``indexes/index_together/unique_together`` refers to a - ``ManyToManyField`` ````, but ``ManyToManyField``\s are not - supported for that option. +* **models.E012**: ``constraints/indexes/index_together/unique_together`` + refers to the nonexistent field ````. +* **models.E013**: ``constraints/indexes/index_together/unique_together`` + refers to a ``ManyToManyField`` ````, but ``ManyToManyField``\s + are not supported for that option. * **models.E014**: ``ordering`` must be a tuple or list (even if you want to order by only one field). * **models.E015**: ``ordering`` refers to the nonexistent field, related field, or lookup ````. -* **models.E016**: ``indexes/index_together/unique_together`` refers to field - ```` which is not local to model ````. +* **models.E016**: ``constraints/indexes/index_together/unique_together`` + refers to field ```` which is not local to model ````. * **models.E017**: Proxy model ```` contains model fields. * **models.E018**: Autogenerated column name too long for field ````. Maximum length is ```` for database ````. diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py index 6c062b2990..7847ee8486 100644 --- a/tests/invalid_models_tests/test_models.py +++ b/tests/invalid_models_tests/test_models.py @@ -1501,3 +1501,70 @@ class ConstraintsTests(TestCase): ] self.assertEqual(Model.check(databases=self.databases), []) + + def test_unique_constraint_pointing_to_missing_field(self): + class Model(models.Model): + class Meta: + constraints = [models.UniqueConstraint(fields=['missing_field'], name='name')] + + self.assertEqual(Model.check(databases=self.databases), [ + Error( + "'constraints' refers to the nonexistent field " + "'missing_field'.", + obj=Model, + id='models.E012', + ), + ]) + + def test_unique_constraint_pointing_to_m2m_field(self): + class Model(models.Model): + m2m = models.ManyToManyField('self') + + class Meta: + constraints = [models.UniqueConstraint(fields=['m2m'], name='name')] + + self.assertEqual(Model.check(databases=self.databases), [ + Error( + "'constraints' refers to a ManyToManyField 'm2m', but " + "ManyToManyFields are not permitted in 'constraints'.", + obj=Model, + id='models.E013', + ), + ]) + + def test_unique_constraint_pointing_to_non_local_field(self): + class Parent(models.Model): + field1 = models.IntegerField() + + class Child(Parent): + field2 = models.IntegerField() + + class Meta: + constraints = [ + models.UniqueConstraint(fields=['field2', 'field1'], name='name'), + ] + + self.assertEqual(Child.check(databases=self.databases), [ + Error( + "'constraints' refers to field 'field1' which is not local to " + "model 'Child'.", + hint='This issue may be caused by multi-table inheritance.', + obj=Child, + id='models.E016', + ), + ]) + + def test_unique_constraint_pointing_to_fk(self): + class Target(models.Model): + pass + + class Model(models.Model): + fk_1 = models.ForeignKey(Target, models.CASCADE, related_name='target_1') + fk_2 = models.ForeignKey(Target, models.CASCADE, related_name='target_2') + + class Meta: + constraints = [ + models.UniqueConstraint(fields=['fk_1_id', 'fk_2'], name='name'), + ] + + self.assertEqual(Model.check(databases=self.databases), [])