Skip to content

Commit

Permalink
Fixed #31410 -- Added system checks for invalid model field names in …
Browse files Browse the repository at this point in the history
…UniqueConstraint.
  • Loading branch information
hramezani authored and felixxm committed May 20, 2020
1 parent 8328811 commit 3c7bf39
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 7 deletions.
6 changes: 6 additions & 0 deletions django/db/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1926,6 +1926,12 @@ def _check_constraints(cls, databases):
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


Expand Down
14 changes: 7 additions & 7 deletions docs/ref/checks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 ``<field name>``.
* **models.E013**: ``indexes/index_together/unique_together`` refers to a
``ManyToManyField`` ``<field name>``, but ``ManyToManyField``\s are not
supported for that option.
* **models.E012**: ``constraints/indexes/index_together/unique_together``
refers to the nonexistent field ``<field name>``.
* **models.E013**: ``constraints/indexes/index_together/unique_together``
refers to a ``ManyToManyField`` ``<field name>``, 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 ``<field name>``.
* **models.E016**: ``indexes/index_together/unique_together`` refers to field
``<field_name>`` which is not local to model ``<model>``.
* **models.E016**: ``constraints/indexes/index_together/unique_together``
refers to field ``<field_name>`` which is not local to model ``<model>``.
* **models.E017**: Proxy model ``<model>`` contains model fields.
* **models.E018**: Autogenerated column name too long for field ``<field>``.
Maximum length is ``<maximum length>`` for database ``<alias>``.
Expand Down
67 changes: 67 additions & 0 deletions tests/invalid_models_tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1501,3 +1501,70 @@ class Meta:
]

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), [])

0 comments on commit 3c7bf39

Please sign in to comment.