2018-08-06 10:30:44 +08:00
|
|
|
from django.core.exceptions import ValidationError
|
2018-08-06 09:06:52 +08:00
|
|
|
from django.db import IntegrityError, connection, models
|
2018-08-06 10:12:27 +08:00
|
|
|
from django.db.models.constraints import BaseConstraint
|
|
|
|
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
2016-11-05 21:12:12 +08:00
|
|
|
|
2019-07-05 20:15:41 +08:00
|
|
|
from .models import ChildModel, Product
|
2016-11-05 21:12:12 +08:00
|
|
|
|
|
|
|
|
2018-08-06 09:06:52 +08:00
|
|
|
def get_constraints(table):
|
|
|
|
with connection.cursor() as cursor:
|
|
|
|
return connection.introspection.get_constraints(cursor, table)
|
|
|
|
|
|
|
|
|
2018-08-06 10:12:27 +08:00
|
|
|
class BaseConstraintTests(SimpleTestCase):
|
|
|
|
def test_constraint_sql(self):
|
|
|
|
c = BaseConstraint('name')
|
|
|
|
msg = 'This method must be implemented by a subclass.'
|
|
|
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
|
|
|
c.constraint_sql(None, None)
|
|
|
|
|
2019-01-01 22:39:58 +08:00
|
|
|
def test_create_sql(self):
|
|
|
|
c = BaseConstraint('name')
|
|
|
|
msg = 'This method must be implemented by a subclass.'
|
|
|
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
|
|
|
c.create_sql(None, None)
|
|
|
|
|
|
|
|
def test_remove_sql(self):
|
|
|
|
c = BaseConstraint('name')
|
|
|
|
msg = 'This method must be implemented by a subclass.'
|
|
|
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
|
|
|
c.remove_sql(None, None)
|
|
|
|
|
2018-08-06 10:12:27 +08:00
|
|
|
|
2016-11-05 21:12:12 +08:00
|
|
|
class CheckConstraintTests(TestCase):
|
2019-01-12 06:42:47 +08:00
|
|
|
def test_eq(self):
|
|
|
|
check1 = models.Q(price__gt=models.F('discounted_price'))
|
|
|
|
check2 = models.Q(price__lt=models.F('discounted_price'))
|
|
|
|
self.assertEqual(
|
|
|
|
models.CheckConstraint(check=check1, name='price'),
|
|
|
|
models.CheckConstraint(check=check1, name='price'),
|
|
|
|
)
|
|
|
|
self.assertNotEqual(
|
|
|
|
models.CheckConstraint(check=check1, name='price'),
|
|
|
|
models.CheckConstraint(check=check1, name='price2'),
|
|
|
|
)
|
|
|
|
self.assertNotEqual(
|
|
|
|
models.CheckConstraint(check=check1, name='price'),
|
|
|
|
models.CheckConstraint(check=check2, name='price'),
|
|
|
|
)
|
|
|
|
self.assertNotEqual(models.CheckConstraint(check=check1, name='price'), 1)
|
|
|
|
|
2016-11-05 21:12:12 +08:00
|
|
|
def test_repr(self):
|
2018-08-06 10:15:10 +08:00
|
|
|
check = models.Q(price__gt=models.F('discounted_price'))
|
2016-11-05 21:12:12 +08:00
|
|
|
name = 'price_gt_discounted_price'
|
2018-08-06 10:15:10 +08:00
|
|
|
constraint = models.CheckConstraint(check=check, name=name)
|
2016-11-05 21:12:12 +08:00
|
|
|
self.assertEqual(
|
2018-08-06 10:15:10 +08:00
|
|
|
repr(constraint),
|
|
|
|
"<CheckConstraint: check='{}' name='{}'>".format(check, name),
|
2016-11-05 21:12:12 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
def test_deconstruction(self):
|
2018-08-06 10:15:10 +08:00
|
|
|
check = models.Q(price__gt=models.F('discounted_price'))
|
2016-11-05 21:12:12 +08:00
|
|
|
name = 'price_gt_discounted_price'
|
2018-08-06 10:15:10 +08:00
|
|
|
constraint = models.CheckConstraint(check=check, name=name)
|
|
|
|
path, args, kwargs = constraint.deconstruct()
|
2016-11-05 21:12:12 +08:00
|
|
|
self.assertEqual(path, 'django.db.models.CheckConstraint')
|
|
|
|
self.assertEqual(args, ())
|
2018-08-06 10:15:10 +08:00
|
|
|
self.assertEqual(kwargs, {'check': check, 'name': name})
|
2016-11-05 21:12:12 +08:00
|
|
|
|
|
|
|
@skipUnlessDBFeature('supports_table_check_constraints')
|
2018-09-25 23:14:45 +08:00
|
|
|
def test_database_constraint(self):
|
2016-11-05 21:12:12 +08:00
|
|
|
Product.objects.create(name='Valid', price=10, discounted_price=5)
|
|
|
|
with self.assertRaises(IntegrityError):
|
|
|
|
Product.objects.create(name='Invalid', price=10, discounted_price=20)
|
2018-08-06 09:06:52 +08:00
|
|
|
|
|
|
|
@skipUnlessDBFeature('supports_table_check_constraints')
|
|
|
|
def test_name(self):
|
|
|
|
constraints = get_constraints(Product._meta.db_table)
|
2019-07-05 20:15:41 +08:00
|
|
|
for expected_name in (
|
|
|
|
'price_gt_discounted_price',
|
|
|
|
'constraints_product_price_gt_0',
|
|
|
|
):
|
|
|
|
with self.subTest(expected_name):
|
|
|
|
self.assertIn(expected_name, constraints)
|
|
|
|
|
|
|
|
@skipUnlessDBFeature('supports_table_check_constraints')
|
|
|
|
def test_abstract_name(self):
|
|
|
|
constraints = get_constraints(ChildModel._meta.db_table)
|
|
|
|
self.assertIn('constraints_childmodel_adult', constraints)
|
2018-08-06 10:30:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
class UniqueConstraintTests(TestCase):
|
|
|
|
@classmethod
|
|
|
|
def setUpTestData(cls):
|
2018-12-28 03:21:59 +08:00
|
|
|
cls.p1, cls.p2 = Product.objects.bulk_create([
|
|
|
|
Product(name='p1', color='red'),
|
|
|
|
Product(name='p2'),
|
|
|
|
])
|
2018-08-06 10:30:44 +08:00
|
|
|
|
2019-01-12 06:42:47 +08:00
|
|
|
def test_eq(self):
|
|
|
|
self.assertEqual(
|
|
|
|
models.UniqueConstraint(fields=['foo', 'bar'], name='unique'),
|
|
|
|
models.UniqueConstraint(fields=['foo', 'bar'], name='unique'),
|
|
|
|
)
|
|
|
|
self.assertNotEqual(
|
|
|
|
models.UniqueConstraint(fields=['foo', 'bar'], name='unique'),
|
|
|
|
models.UniqueConstraint(fields=['foo', 'bar'], name='unique2'),
|
|
|
|
)
|
|
|
|
self.assertNotEqual(
|
|
|
|
models.UniqueConstraint(fields=['foo', 'bar'], name='unique'),
|
|
|
|
models.UniqueConstraint(fields=['foo', 'baz'], name='unique'),
|
|
|
|
)
|
|
|
|
self.assertNotEqual(models.UniqueConstraint(fields=['foo', 'bar'], name='unique'), 1)
|
|
|
|
|
2018-12-28 03:21:59 +08:00
|
|
|
def test_eq_with_condition(self):
|
|
|
|
self.assertEqual(
|
|
|
|
models.UniqueConstraint(
|
|
|
|
fields=['foo', 'bar'], name='unique',
|
|
|
|
condition=models.Q(foo=models.F('bar'))
|
|
|
|
),
|
|
|
|
models.UniqueConstraint(
|
|
|
|
fields=['foo', 'bar'], name='unique',
|
|
|
|
condition=models.Q(foo=models.F('bar'))),
|
|
|
|
)
|
|
|
|
self.assertNotEqual(
|
|
|
|
models.UniqueConstraint(
|
|
|
|
fields=['foo', 'bar'],
|
|
|
|
name='unique',
|
|
|
|
condition=models.Q(foo=models.F('bar'))
|
|
|
|
),
|
|
|
|
models.UniqueConstraint(
|
|
|
|
fields=['foo', 'bar'],
|
|
|
|
name='unique',
|
|
|
|
condition=models.Q(foo=models.F('baz'))
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2018-08-06 10:30:44 +08:00
|
|
|
def test_repr(self):
|
|
|
|
fields = ['foo', 'bar']
|
|
|
|
name = 'unique_fields'
|
|
|
|
constraint = models.UniqueConstraint(fields=fields, name=name)
|
|
|
|
self.assertEqual(
|
|
|
|
repr(constraint),
|
|
|
|
"<UniqueConstraint: fields=('foo', 'bar') name='unique_fields'>",
|
|
|
|
)
|
|
|
|
|
2018-12-28 03:21:59 +08:00
|
|
|
def test_repr_with_condition(self):
|
|
|
|
constraint = models.UniqueConstraint(
|
|
|
|
fields=['foo', 'bar'],
|
|
|
|
name='unique_fields',
|
|
|
|
condition=models.Q(foo=models.F('bar')),
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
repr(constraint),
|
|
|
|
"<UniqueConstraint: fields=('foo', 'bar') name='unique_fields' "
|
|
|
|
"condition=(AND: ('foo', F(bar)))>",
|
|
|
|
)
|
|
|
|
|
2018-08-06 10:30:44 +08:00
|
|
|
def test_deconstruction(self):
|
|
|
|
fields = ['foo', 'bar']
|
|
|
|
name = 'unique_fields'
|
2019-01-11 03:06:17 +08:00
|
|
|
constraint = models.UniqueConstraint(fields=fields, name=name)
|
|
|
|
path, args, kwargs = constraint.deconstruct()
|
2018-08-06 10:30:44 +08:00
|
|
|
self.assertEqual(path, 'django.db.models.UniqueConstraint')
|
|
|
|
self.assertEqual(args, ())
|
|
|
|
self.assertEqual(kwargs, {'fields': tuple(fields), 'name': name})
|
|
|
|
|
2018-12-28 03:21:59 +08:00
|
|
|
def test_deconstruction_with_condition(self):
|
|
|
|
fields = ['foo', 'bar']
|
|
|
|
name = 'unique_fields'
|
|
|
|
condition = models.Q(foo=models.F('bar'))
|
|
|
|
constraint = models.UniqueConstraint(fields=fields, name=name, condition=condition)
|
|
|
|
path, args, kwargs = constraint.deconstruct()
|
|
|
|
self.assertEqual(path, 'django.db.models.UniqueConstraint')
|
|
|
|
self.assertEqual(args, ())
|
|
|
|
self.assertEqual(kwargs, {'fields': tuple(fields), 'name': name, 'condition': condition})
|
|
|
|
|
2018-08-06 10:30:44 +08:00
|
|
|
def test_database_constraint(self):
|
|
|
|
with self.assertRaises(IntegrityError):
|
2018-12-28 03:21:59 +08:00
|
|
|
Product.objects.create(name=self.p1.name, color=self.p1.color)
|
2018-08-06 10:30:44 +08:00
|
|
|
|
|
|
|
def test_model_validation(self):
|
2018-12-28 03:21:59 +08:00
|
|
|
with self.assertRaisesMessage(ValidationError, 'Product with this Name and Color already exists.'):
|
|
|
|
Product(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()
|
2018-08-06 10:30:44 +08:00
|
|
|
|
|
|
|
def test_name(self):
|
|
|
|
constraints = get_constraints(Product._meta.db_table)
|
2018-12-28 03:21:59 +08:00
|
|
|
expected_name = 'name_color_uniq'
|
2018-08-06 10:30:44 +08:00
|
|
|
self.assertIn(expected_name, constraints)
|
2018-12-28 03:21:59 +08:00
|
|
|
|
|
|
|
def test_condition_must_be_q(self):
|
|
|
|
with self.assertRaisesMessage(ValueError, 'UniqueConstraint.condition must be a Q instance.'):
|
|
|
|
models.UniqueConstraint(name='uniq', fields=['name'], condition='invalid')
|