|
@@ -1534,6 +1534,192 @@ class ConstraintsTests(TestCase):
|
|
|
constraints = [models.CheckConstraint(check=models.Q(age__gte=18), name='is_adult')]
|
|
|
self.assertEqual(Model.check(databases=self.databases), [])
|
|
|
|
|
|
+ def test_check_constraint_pointing_to_missing_field(self):
|
|
|
+ class Model(models.Model):
|
|
|
+ class Meta:
|
|
|
+ required_db_features = {'supports_table_check_constraints'}
|
|
|
+ constraints = [
|
|
|
+ models.CheckConstraint(
|
|
|
+ name='name', check=models.Q(missing_field=2),
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+
|
|
|
+ self.assertEqual(Model.check(databases=self.databases), [
|
|
|
+ Error(
|
|
|
+ "'constraints' refers to the nonexistent field "
|
|
|
+ "'missing_field'.",
|
|
|
+ obj=Model,
|
|
|
+ id='models.E012',
|
|
|
+ ),
|
|
|
+ ] if connection.features.supports_table_check_constraints else [])
|
|
|
+
|
|
|
+ @skipUnlessDBFeature('supports_table_check_constraints')
|
|
|
+ def test_check_constraint_pointing_to_reverse_fk(self):
|
|
|
+ class Model(models.Model):
|
|
|
+ parent = models.ForeignKey('self', models.CASCADE, related_name='parents')
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ constraints = [
|
|
|
+ models.CheckConstraint(name='name', check=models.Q(parents=3)),
|
|
|
+ ]
|
|
|
+
|
|
|
+ self.assertEqual(Model.check(databases=self.databases), [
|
|
|
+ Error(
|
|
|
+ "'constraints' refers to the nonexistent field 'parents'.",
|
|
|
+ obj=Model,
|
|
|
+ id='models.E012',
|
|
|
+ ),
|
|
|
+ ])
|
|
|
+
|
|
|
+ @skipUnlessDBFeature('supports_table_check_constraints')
|
|
|
+ def test_check_constraint_pointing_to_m2m_field(self):
|
|
|
+ class Model(models.Model):
|
|
|
+ m2m = models.ManyToManyField('self')
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ constraints = [
|
|
|
+ models.CheckConstraint(name='name', check=models.Q(m2m=2)),
|
|
|
+ ]
|
|
|
+
|
|
|
+ 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',
|
|
|
+ ),
|
|
|
+ ])
|
|
|
+
|
|
|
+ @skipUnlessDBFeature('supports_table_check_constraints')
|
|
|
+ def test_check_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.CheckConstraint(
|
|
|
+ name='name',
|
|
|
+ check=models.Q(fk_1_id=2) | models.Q(fk_2=2),
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+
|
|
|
+ self.assertEqual(Model.check(databases=self.databases), [])
|
|
|
+
|
|
|
+ @skipUnlessDBFeature('supports_table_check_constraints')
|
|
|
+ def test_check_constraint_pointing_to_pk(self):
|
|
|
+ class Model(models.Model):
|
|
|
+ age = models.SmallIntegerField()
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ constraints = [
|
|
|
+ models.CheckConstraint(
|
|
|
+ name='name',
|
|
|
+ check=models.Q(pk__gt=5) & models.Q(age__gt=models.F('pk')),
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+
|
|
|
+ self.assertEqual(Model.check(databases=self.databases), [])
|
|
|
+
|
|
|
+ @skipUnlessDBFeature('supports_table_check_constraints')
|
|
|
+ def test_check_constraint_pointing_to_non_local_field(self):
|
|
|
+ class Parent(models.Model):
|
|
|
+ field1 = models.IntegerField()
|
|
|
+
|
|
|
+ class Child(Parent):
|
|
|
+ pass
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ constraints = [
|
|
|
+ models.CheckConstraint(name='name', check=models.Q(field1=1)),
|
|
|
+ ]
|
|
|
+
|
|
|
+ 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',
|
|
|
+ ),
|
|
|
+ ])
|
|
|
+
|
|
|
+ @skipUnlessDBFeature('supports_table_check_constraints')
|
|
|
+ def test_check_constraint_pointing_to_joined_fields(self):
|
|
|
+ class Model(models.Model):
|
|
|
+ name = models.CharField(max_length=10)
|
|
|
+ field1 = models.PositiveSmallIntegerField()
|
|
|
+ field2 = models.PositiveSmallIntegerField()
|
|
|
+ field3 = models.PositiveSmallIntegerField()
|
|
|
+ parent = models.ForeignKey('self', models.CASCADE)
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ constraints = [
|
|
|
+ models.CheckConstraint(
|
|
|
+ name='name1', check=models.Q(
|
|
|
+ field1__lt=models.F('parent__field1') + models.F('parent__field2')
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ models.CheckConstraint(
|
|
|
+ name='name2', check=models.Q(name=Lower('parent__name'))
|
|
|
+ ),
|
|
|
+ models.CheckConstraint(
|
|
|
+ name='name3', check=models.Q(parent__field3=models.F('field1'))
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+
|
|
|
+ joined_fields = ['parent__field1', 'parent__field2', 'parent__field3', 'parent__name']
|
|
|
+ errors = Model.check(databases=self.databases)
|
|
|
+ expected_errors = [
|
|
|
+ Error(
|
|
|
+ "'constraints' refers to the joined field '%s'." % field_name,
|
|
|
+ obj=Model,
|
|
|
+ id='models.E041',
|
|
|
+ ) for field_name in joined_fields
|
|
|
+ ]
|
|
|
+ self.assertCountEqual(errors, expected_errors)
|
|
|
+
|
|
|
+ @skipUnlessDBFeature('supports_table_check_constraints')
|
|
|
+ def test_check_constraint_pointing_to_joined_fields_complex_check(self):
|
|
|
+ class Model(models.Model):
|
|
|
+ name = models.PositiveSmallIntegerField()
|
|
|
+ field1 = models.PositiveSmallIntegerField()
|
|
|
+ field2 = models.PositiveSmallIntegerField()
|
|
|
+ parent = models.ForeignKey('self', models.CASCADE)
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ constraints = [
|
|
|
+ models.CheckConstraint(
|
|
|
+ name='name',
|
|
|
+ check=models.Q(
|
|
|
+ (
|
|
|
+ models.Q(name='test') &
|
|
|
+ models.Q(field1__lt=models.F('parent__field1'))
|
|
|
+ ) |
|
|
|
+ (
|
|
|
+ models.Q(name__startswith=Lower('parent__name')) &
|
|
|
+ models.Q(field1__gte=(
|
|
|
+ models.F('parent__field1') + models.F('parent__field2')
|
|
|
+ ))
|
|
|
+ )
|
|
|
+ ) | (models.Q(name='test1'))
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+
|
|
|
+ joined_fields = ['parent__field1', 'parent__field2', 'parent__name']
|
|
|
+ errors = Model.check(databases=self.databases)
|
|
|
+ expected_errors = [
|
|
|
+ Error(
|
|
|
+ "'constraints' refers to the joined field '%s'." % field_name,
|
|
|
+ obj=Model,
|
|
|
+ id='models.E041',
|
|
|
+ ) for field_name in joined_fields
|
|
|
+ ]
|
|
|
+ self.assertCountEqual(errors, expected_errors)
|
|
|
+
|
|
|
def test_unique_constraint_with_condition(self):
|
|
|
class Model(models.Model):
|
|
|
age = models.IntegerField()
|
|
@@ -1578,6 +1764,52 @@ class ConstraintsTests(TestCase):
|
|
|
|
|
|
self.assertEqual(Model.check(databases=self.databases), [])
|
|
|
|
|
|
+ def test_unique_constraint_condition_pointing_to_missing_field(self):
|
|
|
+ class Model(models.Model):
|
|
|
+ age = models.SmallIntegerField()
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ required_db_features = {'supports_partial_indexes'}
|
|
|
+ constraints = [
|
|
|
+ models.UniqueConstraint(
|
|
|
+ name='name',
|
|
|
+ fields=['age'],
|
|
|
+ condition=models.Q(missing_field=2),
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+
|
|
|
+ self.assertEqual(Model.check(databases=self.databases), [
|
|
|
+ Error(
|
|
|
+ "'constraints' refers to the nonexistent field "
|
|
|
+ "'missing_field'.",
|
|
|
+ obj=Model,
|
|
|
+ id='models.E012',
|
|
|
+ ),
|
|
|
+ ] if connection.features.supports_partial_indexes else [])
|
|
|
+
|
|
|
+ def test_unique_constraint_condition_pointing_to_joined_fields(self):
|
|
|
+ class Model(models.Model):
|
|
|
+ age = models.SmallIntegerField()
|
|
|
+ parent = models.ForeignKey('self', models.CASCADE)
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ required_db_features = {'supports_partial_indexes'}
|
|
|
+ constraints = [
|
|
|
+ models.UniqueConstraint(
|
|
|
+ name='name',
|
|
|
+ fields=['age'],
|
|
|
+ condition=models.Q(parent__age__lt=2),
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+
|
|
|
+ self.assertEqual(Model.check(databases=self.databases), [
|
|
|
+ Error(
|
|
|
+ "'constraints' refers to the joined field 'parent__age__lt'.",
|
|
|
+ obj=Model,
|
|
|
+ id='models.E041',
|
|
|
+ )
|
|
|
+ ] if connection.features.supports_partial_indexes else [])
|
|
|
+
|
|
|
def test_deferrable_unique_constraint(self):
|
|
|
class Model(models.Model):
|
|
|
age = models.IntegerField()
|