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):
continue
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
if any(isinstance(constraint, CheckConstraint) for constraint in cls._meta.constraints):
errors.append(

View File

@ -2,12 +2,13 @@ from django.db import models
class Product(models.Model):
name = models.CharField(max_length=255)
color = models.CharField(max_length=32, null=True)
price = models.IntegerField(null=True)
discounted_price = models.IntegerField(null=True)
class Meta:
required_db_features = {
'supports_table_check_constraints',
}
constraints = [
models.CheckConstraint(
check=models.Q(price__gt=models.F('discounted_price')),
@ -17,6 +18,15 @@ class Product(models.Model):
check=models.Q(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'],
@ -31,6 +41,9 @@ class AbstractModel(models.Model):
class Meta:
abstract = True
required_db_features = {
'supports_table_check_constraints',
}
constraints = [
models.CheckConstraint(
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.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from .models import ChildModel, Product
from .models import ChildModel, Product, UniqueConstraintProduct
def get_constraints(table):
@ -69,9 +69,9 @@ class CheckConstraintTests(TestCase):
@skipUnlessDBFeature('supports_table_check_constraints')
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):
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')
def test_name(self):
@ -92,9 +92,9 @@ class CheckConstraintTests(TestCase):
class UniqueConstraintTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.p1, cls.p2 = Product.objects.bulk_create([
Product(name='p1', color='red'),
Product(name='p2'),
cls.p1, cls.p2 = UniqueConstraintProduct.objects.bulk_create([
UniqueConstraintProduct(name='p1', color='red'),
UniqueConstraintProduct(name='p2'),
])
def test_eq(self):
@ -177,19 +177,20 @@ class UniqueConstraintTests(TestCase):
def test_database_constraint(self):
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):
with self.assertRaisesMessage(ValidationError, 'Product with this Name and Color already exists.'):
Product(name=self.p1.name, color=self.p1.color).validate_unique()
msg = 'Unique constraint product with this Name and Color already exists.'
with self.assertRaisesMessage(ValidationError, msg):
UniqueConstraintProduct(name=self.p1.name, color=self.p1.color).validate_unique()
def test_model_validation_with_condition(self):
"""Partial unique constraints are ignored by Model.validate_unique()."""
Product(name=self.p1.name, color='blue').validate_unique()
Product(name=self.p2.name).validate_unique()
UniqueConstraintProduct(name=self.p1.name, color='blue').validate_unique()
UniqueConstraintProduct(name=self.p2.name).validate_unique()
def test_name(self):
constraints = get_constraints(Product._meta.db_table)
constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
expected_name = 'name_color_uniq'
self.assertIn(expected_name, constraints)

View File

@ -70,14 +70,24 @@ class Comment(models.Model):
article = models.ForeignKey(Article, models.CASCADE, db_index=True)
email = models.EmailField()
pub_date = models.DateTimeField()
up_votes = models.PositiveIntegerField()
body = models.TextField()
class Meta:
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'),
]
indexes = [
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 .models import (
Article, ArticleReporter, City, Comment, Country, District, Reporter,
Article, ArticleReporter, CheckConstraintModel, City, Comment, Country,
District, Reporter,
)
@ -241,19 +242,22 @@ class IntrospectionTests(TransactionTestCase):
self.assertEqual(details['check'], check)
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
custom_constraints = {
'article_email_pub_date_uniq',
'email_pub_date_idx',
}
if (
connection.features.supports_column_check_constraints and
connection.features.can_introspect_check_constraints
):
custom_constraints.add('up_votes_gte_0_check')
assertDetails(constraints['up_votes_gte_0_check'], ['up_votes'], check=True)
with connection.cursor() as cursor:
constraints = connection.introspection.get_constraints(cursor, Comment._meta.db_table)
if (
connection.features.supports_column_check_constraints and
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')
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['email_pub_date_idx'], ['email', 'pub_date'], index=True)
# Test field constraints

View File

@ -1191,3 +1191,13 @@ class ConstraintsTests(SimpleTestCase):
)
expected = [] if connection.features.supports_table_check_constraints else [warn, warn]
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(), [])