Refs #11964 -- Made constraint support check respect required_db_features.

This will notably silence the warnings issued when running the test
suite on MySQL.
This commit is contained in:
Simon Charette 2019-08-10 02:41:18 -04:00 committed by Mariusz Felisiak
parent 2fb872e56f
commit 8b3e1b6e9e
6 changed files with 67 additions and 26 deletions

View File

@ -1813,7 +1813,10 @@ class Model(metaclass=ModelBase):
if not router.allow_migrate_model(db, cls): if not router.allow_migrate_model(db, cls):
continue continue
connection = connections[db] connection = connections[db]
if connection.features.supports_table_check_constraints: if (
connection.features.supports_table_check_constraints or
'supports_table_check_constraints' in cls._meta.required_db_features
):
continue continue
if any(isinstance(constraint, CheckConstraint) for constraint in cls._meta.constraints): if any(isinstance(constraint, CheckConstraint) for constraint in cls._meta.constraints):
errors.append( errors.append(

View File

@ -2,12 +2,13 @@ from django.db import models
class Product(models.Model): class Product(models.Model):
name = models.CharField(max_length=255)
color = models.CharField(max_length=32, null=True)
price = models.IntegerField(null=True) price = models.IntegerField(null=True)
discounted_price = models.IntegerField(null=True) discounted_price = models.IntegerField(null=True)
class Meta: class Meta:
required_db_features = {
'supports_table_check_constraints',
}
constraints = [ constraints = [
models.CheckConstraint( models.CheckConstraint(
check=models.Q(price__gt=models.F('discounted_price')), check=models.Q(price__gt=models.F('discounted_price')),
@ -17,6 +18,15 @@ class Product(models.Model):
check=models.Q(price__gt=0), check=models.Q(price__gt=0),
name='%(app_label)s_%(class)s_price_gt_0', name='%(app_label)s_%(class)s_price_gt_0',
), ),
]
class UniqueConstraintProduct(models.Model):
name = models.CharField(max_length=255)
color = models.CharField(max_length=32, null=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['name', 'color'], name='name_color_uniq'), models.UniqueConstraint(fields=['name', 'color'], name='name_color_uniq'),
models.UniqueConstraint( models.UniqueConstraint(
fields=['name'], fields=['name'],
@ -31,6 +41,9 @@ class AbstractModel(models.Model):
class Meta: class Meta:
abstract = True abstract = True
required_db_features = {
'supports_table_check_constraints',
}
constraints = [ constraints = [
models.CheckConstraint( models.CheckConstraint(
check=models.Q(age__gte=18), check=models.Q(age__gte=18),

View File

@ -3,7 +3,7 @@ from django.db import IntegrityError, connection, models
from django.db.models.constraints import BaseConstraint from django.db.models.constraints import BaseConstraint
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from .models import ChildModel, Product from .models import ChildModel, Product, UniqueConstraintProduct
def get_constraints(table): def get_constraints(table):
@ -69,9 +69,9 @@ class CheckConstraintTests(TestCase):
@skipUnlessDBFeature('supports_table_check_constraints') @skipUnlessDBFeature('supports_table_check_constraints')
def test_database_constraint(self): def test_database_constraint(self):
Product.objects.create(name='Valid', price=10, discounted_price=5) Product.objects.create(price=10, discounted_price=5)
with self.assertRaises(IntegrityError): with self.assertRaises(IntegrityError):
Product.objects.create(name='Invalid', price=10, discounted_price=20) Product.objects.create(price=10, discounted_price=20)
@skipUnlessDBFeature('supports_table_check_constraints', 'can_introspect_check_constraints') @skipUnlessDBFeature('supports_table_check_constraints', 'can_introspect_check_constraints')
def test_name(self): def test_name(self):
@ -92,9 +92,9 @@ class CheckConstraintTests(TestCase):
class UniqueConstraintTests(TestCase): class UniqueConstraintTests(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.p1, cls.p2 = Product.objects.bulk_create([ cls.p1, cls.p2 = UniqueConstraintProduct.objects.bulk_create([
Product(name='p1', color='red'), UniqueConstraintProduct(name='p1', color='red'),
Product(name='p2'), UniqueConstraintProduct(name='p2'),
]) ])
def test_eq(self): def test_eq(self):
@ -177,19 +177,20 @@ class UniqueConstraintTests(TestCase):
def test_database_constraint(self): def test_database_constraint(self):
with self.assertRaises(IntegrityError): with self.assertRaises(IntegrityError):
Product.objects.create(name=self.p1.name, color=self.p1.color) UniqueConstraintProduct.objects.create(name=self.p1.name, color=self.p1.color)
def test_model_validation(self): def test_model_validation(self):
with self.assertRaisesMessage(ValidationError, 'Product with this Name and Color already exists.'): msg = 'Unique constraint product with this Name and Color already exists.'
Product(name=self.p1.name, color=self.p1.color).validate_unique() with self.assertRaisesMessage(ValidationError, msg):
UniqueConstraintProduct(name=self.p1.name, color=self.p1.color).validate_unique()
def test_model_validation_with_condition(self): def test_model_validation_with_condition(self):
"""Partial unique constraints are ignored by Model.validate_unique().""" """Partial unique constraints are ignored by Model.validate_unique()."""
Product(name=self.p1.name, color='blue').validate_unique() UniqueConstraintProduct(name=self.p1.name, color='blue').validate_unique()
Product(name=self.p2.name).validate_unique() UniqueConstraintProduct(name=self.p2.name).validate_unique()
def test_name(self): def test_name(self):
constraints = get_constraints(Product._meta.db_table) constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
expected_name = 'name_color_uniq' expected_name = 'name_color_uniq'
self.assertIn(expected_name, constraints) self.assertIn(expected_name, constraints)

View File

@ -70,14 +70,24 @@ class Comment(models.Model):
article = models.ForeignKey(Article, models.CASCADE, db_index=True) article = models.ForeignKey(Article, models.CASCADE, db_index=True)
email = models.EmailField() email = models.EmailField()
pub_date = models.DateTimeField() pub_date = models.DateTimeField()
up_votes = models.PositiveIntegerField()
body = models.TextField() body = models.TextField()
class Meta: class Meta:
constraints = [ constraints = [
models.CheckConstraint(name='up_votes_gte_0_check', check=models.Q(up_votes__gte=0)),
models.UniqueConstraint(fields=['article', 'email', 'pub_date'], name='article_email_pub_date_uniq'), models.UniqueConstraint(fields=['article', 'email', 'pub_date'], name='article_email_pub_date_uniq'),
] ]
indexes = [ indexes = [
models.Index(fields=['email', 'pub_date'], name='email_pub_date_idx'), models.Index(fields=['email', 'pub_date'], name='email_pub_date_idx'),
] ]
class CheckConstraintModel(models.Model):
up_votes = models.PositiveIntegerField()
class Meta:
required_db_features = {
'supports_table_check_constraints',
}
constraints = [
models.CheckConstraint(name='up_votes_gte_0_check', check=models.Q(up_votes__gte=0)),
]

View File

@ -6,7 +6,8 @@ from django.db.utils import DatabaseError
from django.test import TransactionTestCase, skipUnlessDBFeature from django.test import TransactionTestCase, skipUnlessDBFeature
from .models import ( from .models import (
Article, ArticleReporter, City, Comment, Country, District, Reporter, Article, ArticleReporter, CheckConstraintModel, City, Comment, Country,
District, Reporter,
) )
@ -241,17 +242,20 @@ class IntrospectionTests(TransactionTestCase):
self.assertEqual(details['check'], check) self.assertEqual(details['check'], check)
self.assertEqual(details['foreign_key'], foreign_key) self.assertEqual(details['foreign_key'], foreign_key)
with connection.cursor() as cursor:
constraints = connection.introspection.get_constraints(cursor, Comment._meta.db_table)
# Test custom constraints # Test custom constraints
custom_constraints = { custom_constraints = {
'article_email_pub_date_uniq', 'article_email_pub_date_uniq',
'email_pub_date_idx', 'email_pub_date_idx',
} }
with connection.cursor() as cursor:
constraints = connection.introspection.get_constraints(cursor, Comment._meta.db_table)
if ( if (
connection.features.supports_column_check_constraints and connection.features.supports_column_check_constraints and
connection.features.can_introspect_check_constraints connection.features.can_introspect_check_constraints
): ):
constraints.update(
connection.introspection.get_constraints(cursor, CheckConstraintModel._meta.db_table)
)
custom_constraints.add('up_votes_gte_0_check') custom_constraints.add('up_votes_gte_0_check')
assertDetails(constraints['up_votes_gte_0_check'], ['up_votes'], check=True) assertDetails(constraints['up_votes_gte_0_check'], ['up_votes'], check=True)
assertDetails(constraints['article_email_pub_date_uniq'], ['article_id', 'email', 'pub_date'], unique=True) assertDetails(constraints['article_email_pub_date_uniq'], ['article_id', 'email', 'pub_date'], unique=True)

View File

@ -1191,3 +1191,13 @@ class ConstraintsTests(SimpleTestCase):
) )
expected = [] if connection.features.supports_table_check_constraints else [warn, warn] expected = [] if connection.features.supports_table_check_constraints else [warn, warn]
self.assertCountEqual(errors, expected) self.assertCountEqual(errors, expected)
def test_check_constraints_required_db_features(self):
class Model(models.Model):
age = models.IntegerField()
class Meta:
required_db_features = {'supports_table_check_constraints'}
constraints = [models.CheckConstraint(check=models.Q(age__gte=18), name='is_adult')]
self.assertEqual(Model.check(), [])