mirror of https://github.com/django/django.git
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:
parent
2fb872e56f
commit
8b3e1b6e9e
|
@ -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(
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)),
|
||||||
|
]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(), [])
|
||||||
|
|
Loading…
Reference in New Issue