1. from unittest import mock
    
  2. 
    
  3. from django.core.exceptions import ValidationError
    
  4. from django.db import IntegrityError, connection, models
    
  5. from django.db.models import F
    
  6. from django.db.models.constraints import BaseConstraint, UniqueConstraint
    
  7. from django.db.models.functions import Lower
    
  8. from django.db.transaction import atomic
    
  9. from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
    
  10. 
    
  11. from .models import (
    
  12.     ChildModel,
    
  13.     ChildUniqueConstraintProduct,
    
  14.     Product,
    
  15.     UniqueConstraintConditionProduct,
    
  16.     UniqueConstraintDeferrable,
    
  17.     UniqueConstraintInclude,
    
  18.     UniqueConstraintProduct,
    
  19. )
    
  20. 
    
  21. 
    
  22. def get_constraints(table):
    
  23.     with connection.cursor() as cursor:
    
  24.         return connection.introspection.get_constraints(cursor, table)
    
  25. 
    
  26. 
    
  27. class BaseConstraintTests(SimpleTestCase):
    
  28.     def test_constraint_sql(self):
    
  29.         c = BaseConstraint("name")
    
  30.         msg = "This method must be implemented by a subclass."
    
  31.         with self.assertRaisesMessage(NotImplementedError, msg):
    
  32.             c.constraint_sql(None, None)
    
  33. 
    
  34.     def test_contains_expressions(self):
    
  35.         c = BaseConstraint("name")
    
  36.         self.assertIs(c.contains_expressions, False)
    
  37. 
    
  38.     def test_create_sql(self):
    
  39.         c = BaseConstraint("name")
    
  40.         msg = "This method must be implemented by a subclass."
    
  41.         with self.assertRaisesMessage(NotImplementedError, msg):
    
  42.             c.create_sql(None, None)
    
  43. 
    
  44.     def test_remove_sql(self):
    
  45.         c = BaseConstraint("name")
    
  46.         msg = "This method must be implemented by a subclass."
    
  47.         with self.assertRaisesMessage(NotImplementedError, msg):
    
  48.             c.remove_sql(None, None)
    
  49. 
    
  50.     def test_validate(self):
    
  51.         c = BaseConstraint("name")
    
  52.         msg = "This method must be implemented by a subclass."
    
  53.         with self.assertRaisesMessage(NotImplementedError, msg):
    
  54.             c.validate(None, None)
    
  55. 
    
  56.     def test_default_violation_error_message(self):
    
  57.         c = BaseConstraint("name")
    
  58.         self.assertEqual(
    
  59.             c.get_violation_error_message(), "Constraint “name” is violated."
    
  60.         )
    
  61. 
    
  62.     def test_custom_violation_error_message(self):
    
  63.         c = BaseConstraint(
    
  64.             "base_name", violation_error_message="custom %(name)s message"
    
  65.         )
    
  66.         self.assertEqual(c.get_violation_error_message(), "custom base_name message")
    
  67. 
    
  68.     def test_custom_violation_error_message_clone(self):
    
  69.         constraint = BaseConstraint(
    
  70.             "base_name",
    
  71.             violation_error_message="custom %(name)s message",
    
  72.         ).clone()
    
  73.         self.assertEqual(
    
  74.             constraint.get_violation_error_message(),
    
  75.             "custom base_name message",
    
  76.         )
    
  77. 
    
  78.     def test_deconstruction(self):
    
  79.         constraint = BaseConstraint(
    
  80.             "base_name",
    
  81.             violation_error_message="custom %(name)s message",
    
  82.         )
    
  83.         path, args, kwargs = constraint.deconstruct()
    
  84.         self.assertEqual(path, "django.db.models.BaseConstraint")
    
  85.         self.assertEqual(args, ())
    
  86.         self.assertEqual(
    
  87.             kwargs,
    
  88.             {"name": "base_name", "violation_error_message": "custom %(name)s message"},
    
  89.         )
    
  90. 
    
  91. 
    
  92. class CheckConstraintTests(TestCase):
    
  93.     def test_eq(self):
    
  94.         check1 = models.Q(price__gt=models.F("discounted_price"))
    
  95.         check2 = models.Q(price__lt=models.F("discounted_price"))
    
  96.         self.assertEqual(
    
  97.             models.CheckConstraint(check=check1, name="price"),
    
  98.             models.CheckConstraint(check=check1, name="price"),
    
  99.         )
    
  100.         self.assertEqual(models.CheckConstraint(check=check1, name="price"), mock.ANY)
    
  101.         self.assertNotEqual(
    
  102.             models.CheckConstraint(check=check1, name="price"),
    
  103.             models.CheckConstraint(check=check1, name="price2"),
    
  104.         )
    
  105.         self.assertNotEqual(
    
  106.             models.CheckConstraint(check=check1, name="price"),
    
  107.             models.CheckConstraint(check=check2, name="price"),
    
  108.         )
    
  109.         self.assertNotEqual(models.CheckConstraint(check=check1, name="price"), 1)
    
  110.         self.assertNotEqual(
    
  111.             models.CheckConstraint(check=check1, name="price"),
    
  112.             models.CheckConstraint(
    
  113.                 check=check1, name="price", violation_error_message="custom error"
    
  114.             ),
    
  115.         )
    
  116.         self.assertNotEqual(
    
  117.             models.CheckConstraint(
    
  118.                 check=check1, name="price", violation_error_message="custom error"
    
  119.             ),
    
  120.             models.CheckConstraint(
    
  121.                 check=check1, name="price", violation_error_message="other custom error"
    
  122.             ),
    
  123.         )
    
  124.         self.assertEqual(
    
  125.             models.CheckConstraint(
    
  126.                 check=check1, name="price", violation_error_message="custom error"
    
  127.             ),
    
  128.             models.CheckConstraint(
    
  129.                 check=check1, name="price", violation_error_message="custom error"
    
  130.             ),
    
  131.         )
    
  132. 
    
  133.     def test_repr(self):
    
  134.         constraint = models.CheckConstraint(
    
  135.             check=models.Q(price__gt=models.F("discounted_price")),
    
  136.             name="price_gt_discounted_price",
    
  137.         )
    
  138.         self.assertEqual(
    
  139.             repr(constraint),
    
  140.             "<CheckConstraint: check=(AND: ('price__gt', F(discounted_price))) "
    
  141.             "name='price_gt_discounted_price'>",
    
  142.         )
    
  143. 
    
  144.     def test_invalid_check_types(self):
    
  145.         msg = "CheckConstraint.check must be a Q instance or boolean expression."
    
  146.         with self.assertRaisesMessage(TypeError, msg):
    
  147.             models.CheckConstraint(check=models.F("discounted_price"), name="check")
    
  148. 
    
  149.     def test_deconstruction(self):
    
  150.         check = models.Q(price__gt=models.F("discounted_price"))
    
  151.         name = "price_gt_discounted_price"
    
  152.         constraint = models.CheckConstraint(check=check, name=name)
    
  153.         path, args, kwargs = constraint.deconstruct()
    
  154.         self.assertEqual(path, "django.db.models.CheckConstraint")
    
  155.         self.assertEqual(args, ())
    
  156.         self.assertEqual(kwargs, {"check": check, "name": name})
    
  157. 
    
  158.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  159.     def test_database_constraint(self):
    
  160.         Product.objects.create(price=10, discounted_price=5)
    
  161.         with self.assertRaises(IntegrityError):
    
  162.             Product.objects.create(price=10, discounted_price=20)
    
  163. 
    
  164.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  165.     def test_database_constraint_unicode(self):
    
  166.         Product.objects.create(price=10, discounted_price=5, unit="μg/mL")
    
  167.         with self.assertRaises(IntegrityError):
    
  168.             Product.objects.create(price=10, discounted_price=7, unit="l")
    
  169. 
    
  170.     @skipUnlessDBFeature(
    
  171.         "supports_table_check_constraints", "can_introspect_check_constraints"
    
  172.     )
    
  173.     def test_name(self):
    
  174.         constraints = get_constraints(Product._meta.db_table)
    
  175.         for expected_name in (
    
  176.             "price_gt_discounted_price",
    
  177.             "constraints_product_price_gt_0",
    
  178.         ):
    
  179.             with self.subTest(expected_name):
    
  180.                 self.assertIn(expected_name, constraints)
    
  181. 
    
  182.     @skipUnlessDBFeature(
    
  183.         "supports_table_check_constraints", "can_introspect_check_constraints"
    
  184.     )
    
  185.     def test_abstract_name(self):
    
  186.         constraints = get_constraints(ChildModel._meta.db_table)
    
  187.         self.assertIn("constraints_childmodel_adult", constraints)
    
  188. 
    
  189.     def test_validate(self):
    
  190.         check = models.Q(price__gt=models.F("discounted_price"))
    
  191.         constraint = models.CheckConstraint(check=check, name="price")
    
  192.         # Invalid product.
    
  193.         invalid_product = Product(price=10, discounted_price=42)
    
  194.         with self.assertRaises(ValidationError):
    
  195.             constraint.validate(Product, invalid_product)
    
  196.         with self.assertRaises(ValidationError):
    
  197.             constraint.validate(Product, invalid_product, exclude={"unit"})
    
  198.         # Fields used by the check constraint are excluded.
    
  199.         constraint.validate(Product, invalid_product, exclude={"price"})
    
  200.         constraint.validate(Product, invalid_product, exclude={"discounted_price"})
    
  201.         constraint.validate(
    
  202.             Product,
    
  203.             invalid_product,
    
  204.             exclude={"discounted_price", "price"},
    
  205.         )
    
  206.         # Valid product.
    
  207.         constraint.validate(Product, Product(price=10, discounted_price=5))
    
  208. 
    
  209.     def test_validate_boolean_expressions(self):
    
  210.         constraint = models.CheckConstraint(
    
  211.             check=models.expressions.ExpressionWrapper(
    
  212.                 models.Q(price__gt=500) | models.Q(price__lt=500),
    
  213.                 output_field=models.BooleanField(),
    
  214.             ),
    
  215.             name="price_neq_500_wrap",
    
  216.         )
    
  217.         msg = f"Constraint “{constraint.name}” is violated."
    
  218.         with self.assertRaisesMessage(ValidationError, msg):
    
  219.             constraint.validate(Product, Product(price=500, discounted_price=5))
    
  220.         constraint.validate(Product, Product(price=501, discounted_price=5))
    
  221.         constraint.validate(Product, Product(price=499, discounted_price=5))
    
  222. 
    
  223.     def test_validate_rawsql_expressions_noop(self):
    
  224.         constraint = models.CheckConstraint(
    
  225.             check=models.expressions.RawSQL(
    
  226.                 "price < %s OR price > %s",
    
  227.                 (500, 500),
    
  228.                 output_field=models.BooleanField(),
    
  229.             ),
    
  230.             name="price_neq_500_raw",
    
  231.         )
    
  232.         # RawSQL can not be checked and is always considered valid.
    
  233.         constraint.validate(Product, Product(price=500, discounted_price=5))
    
  234.         constraint.validate(Product, Product(price=501, discounted_price=5))
    
  235.         constraint.validate(Product, Product(price=499, discounted_price=5))
    
  236. 
    
  237.     @skipUnlessDBFeature("supports_comparing_boolean_expr")
    
  238.     def test_validate_nullable_field_with_none(self):
    
  239.         # Nullable fields should be considered valid on None values.
    
  240.         constraint = models.CheckConstraint(
    
  241.             check=models.Q(price__gte=0),
    
  242.             name="positive_price",
    
  243.         )
    
  244.         constraint.validate(Product, Product())
    
  245. 
    
  246.     @skipIfDBFeature("supports_comparing_boolean_expr")
    
  247.     def test_validate_nullable_field_with_isnull(self):
    
  248.         constraint = models.CheckConstraint(
    
  249.             check=models.Q(price__gte=0) | models.Q(price__isnull=True),
    
  250.             name="positive_price",
    
  251.         )
    
  252.         constraint.validate(Product, Product())
    
  253. 
    
  254. 
    
  255. class UniqueConstraintTests(TestCase):
    
  256.     @classmethod
    
  257.     def setUpTestData(cls):
    
  258.         cls.p1 = UniqueConstraintProduct.objects.create(name="p1", color="red")
    
  259.         cls.p2 = UniqueConstraintProduct.objects.create(name="p2")
    
  260. 
    
  261.     def test_eq(self):
    
  262.         self.assertEqual(
    
  263.             models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
    
  264.             models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
    
  265.         )
    
  266.         self.assertEqual(
    
  267.             models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
    
  268.             mock.ANY,
    
  269.         )
    
  270.         self.assertNotEqual(
    
  271.             models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
    
  272.             models.UniqueConstraint(fields=["foo", "bar"], name="unique2"),
    
  273.         )
    
  274.         self.assertNotEqual(
    
  275.             models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
    
  276.             models.UniqueConstraint(fields=["foo", "baz"], name="unique"),
    
  277.         )
    
  278.         self.assertNotEqual(
    
  279.             models.UniqueConstraint(fields=["foo", "bar"], name="unique"), 1
    
  280.         )
    
  281.         self.assertNotEqual(
    
  282.             models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
    
  283.             models.UniqueConstraint(
    
  284.                 fields=["foo", "bar"],
    
  285.                 name="unique",
    
  286.                 violation_error_message="custom error",
    
  287.             ),
    
  288.         )
    
  289.         self.assertNotEqual(
    
  290.             models.UniqueConstraint(
    
  291.                 fields=["foo", "bar"],
    
  292.                 name="unique",
    
  293.                 violation_error_message="custom error",
    
  294.             ),
    
  295.             models.UniqueConstraint(
    
  296.                 fields=["foo", "bar"],
    
  297.                 name="unique",
    
  298.                 violation_error_message="other custom error",
    
  299.             ),
    
  300.         )
    
  301.         self.assertEqual(
    
  302.             models.UniqueConstraint(
    
  303.                 fields=["foo", "bar"],
    
  304.                 name="unique",
    
  305.                 violation_error_message="custom error",
    
  306.             ),
    
  307.             models.UniqueConstraint(
    
  308.                 fields=["foo", "bar"],
    
  309.                 name="unique",
    
  310.                 violation_error_message="custom error",
    
  311.             ),
    
  312.         )
    
  313. 
    
  314.     def test_eq_with_condition(self):
    
  315.         self.assertEqual(
    
  316.             models.UniqueConstraint(
    
  317.                 fields=["foo", "bar"],
    
  318.                 name="unique",
    
  319.                 condition=models.Q(foo=models.F("bar")),
    
  320.             ),
    
  321.             models.UniqueConstraint(
    
  322.                 fields=["foo", "bar"],
    
  323.                 name="unique",
    
  324.                 condition=models.Q(foo=models.F("bar")),
    
  325.             ),
    
  326.         )
    
  327.         self.assertNotEqual(
    
  328.             models.UniqueConstraint(
    
  329.                 fields=["foo", "bar"],
    
  330.                 name="unique",
    
  331.                 condition=models.Q(foo=models.F("bar")),
    
  332.             ),
    
  333.             models.UniqueConstraint(
    
  334.                 fields=["foo", "bar"],
    
  335.                 name="unique",
    
  336.                 condition=models.Q(foo=models.F("baz")),
    
  337.             ),
    
  338.         )
    
  339. 
    
  340.     def test_eq_with_deferrable(self):
    
  341.         constraint_1 = models.UniqueConstraint(
    
  342.             fields=["foo", "bar"],
    
  343.             name="unique",
    
  344.             deferrable=models.Deferrable.DEFERRED,
    
  345.         )
    
  346.         constraint_2 = models.UniqueConstraint(
    
  347.             fields=["foo", "bar"],
    
  348.             name="unique",
    
  349.             deferrable=models.Deferrable.IMMEDIATE,
    
  350.         )
    
  351.         self.assertEqual(constraint_1, constraint_1)
    
  352.         self.assertNotEqual(constraint_1, constraint_2)
    
  353. 
    
  354.     def test_eq_with_include(self):
    
  355.         constraint_1 = models.UniqueConstraint(
    
  356.             fields=["foo", "bar"],
    
  357.             name="include",
    
  358.             include=["baz_1"],
    
  359.         )
    
  360.         constraint_2 = models.UniqueConstraint(
    
  361.             fields=["foo", "bar"],
    
  362.             name="include",
    
  363.             include=["baz_2"],
    
  364.         )
    
  365.         self.assertEqual(constraint_1, constraint_1)
    
  366.         self.assertNotEqual(constraint_1, constraint_2)
    
  367. 
    
  368.     def test_eq_with_opclasses(self):
    
  369.         constraint_1 = models.UniqueConstraint(
    
  370.             fields=["foo", "bar"],
    
  371.             name="opclasses",
    
  372.             opclasses=["text_pattern_ops", "varchar_pattern_ops"],
    
  373.         )
    
  374.         constraint_2 = models.UniqueConstraint(
    
  375.             fields=["foo", "bar"],
    
  376.             name="opclasses",
    
  377.             opclasses=["varchar_pattern_ops", "text_pattern_ops"],
    
  378.         )
    
  379.         self.assertEqual(constraint_1, constraint_1)
    
  380.         self.assertNotEqual(constraint_1, constraint_2)
    
  381. 
    
  382.     def test_eq_with_expressions(self):
    
  383.         constraint = models.UniqueConstraint(
    
  384.             Lower("title"),
    
  385.             F("author"),
    
  386.             name="book_func_uq",
    
  387.         )
    
  388.         same_constraint = models.UniqueConstraint(
    
  389.             Lower("title"),
    
  390.             "author",
    
  391.             name="book_func_uq",
    
  392.         )
    
  393.         another_constraint = models.UniqueConstraint(
    
  394.             Lower("title"),
    
  395.             name="book_func_uq",
    
  396.         )
    
  397.         self.assertEqual(constraint, same_constraint)
    
  398.         self.assertEqual(constraint, mock.ANY)
    
  399.         self.assertNotEqual(constraint, another_constraint)
    
  400. 
    
  401.     def test_repr(self):
    
  402.         fields = ["foo", "bar"]
    
  403.         name = "unique_fields"
    
  404.         constraint = models.UniqueConstraint(fields=fields, name=name)
    
  405.         self.assertEqual(
    
  406.             repr(constraint),
    
  407.             "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields'>",
    
  408.         )
    
  409. 
    
  410.     def test_repr_with_condition(self):
    
  411.         constraint = models.UniqueConstraint(
    
  412.             fields=["foo", "bar"],
    
  413.             name="unique_fields",
    
  414.             condition=models.Q(foo=models.F("bar")),
    
  415.         )
    
  416.         self.assertEqual(
    
  417.             repr(constraint),
    
  418.             "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields' "
    
  419.             "condition=(AND: ('foo', F(bar)))>",
    
  420.         )
    
  421. 
    
  422.     def test_repr_with_deferrable(self):
    
  423.         constraint = models.UniqueConstraint(
    
  424.             fields=["foo", "bar"],
    
  425.             name="unique_fields",
    
  426.             deferrable=models.Deferrable.IMMEDIATE,
    
  427.         )
    
  428.         self.assertEqual(
    
  429.             repr(constraint),
    
  430.             "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields' "
    
  431.             "deferrable=Deferrable.IMMEDIATE>",
    
  432.         )
    
  433. 
    
  434.     def test_repr_with_include(self):
    
  435.         constraint = models.UniqueConstraint(
    
  436.             fields=["foo", "bar"],
    
  437.             name="include_fields",
    
  438.             include=["baz_1", "baz_2"],
    
  439.         )
    
  440.         self.assertEqual(
    
  441.             repr(constraint),
    
  442.             "<UniqueConstraint: fields=('foo', 'bar') name='include_fields' "
    
  443.             "include=('baz_1', 'baz_2')>",
    
  444.         )
    
  445. 
    
  446.     def test_repr_with_opclasses(self):
    
  447.         constraint = models.UniqueConstraint(
    
  448.             fields=["foo", "bar"],
    
  449.             name="opclasses_fields",
    
  450.             opclasses=["text_pattern_ops", "varchar_pattern_ops"],
    
  451.         )
    
  452.         self.assertEqual(
    
  453.             repr(constraint),
    
  454.             "<UniqueConstraint: fields=('foo', 'bar') name='opclasses_fields' "
    
  455.             "opclasses=['text_pattern_ops', 'varchar_pattern_ops']>",
    
  456.         )
    
  457. 
    
  458.     def test_repr_with_expressions(self):
    
  459.         constraint = models.UniqueConstraint(
    
  460.             Lower("title"),
    
  461.             F("author"),
    
  462.             name="book_func_uq",
    
  463.         )
    
  464.         self.assertEqual(
    
  465.             repr(constraint),
    
  466.             "<UniqueConstraint: expressions=(Lower(F(title)), F(author)) "
    
  467.             "name='book_func_uq'>",
    
  468.         )
    
  469. 
    
  470.     def test_deconstruction(self):
    
  471.         fields = ["foo", "bar"]
    
  472.         name = "unique_fields"
    
  473.         constraint = models.UniqueConstraint(fields=fields, name=name)
    
  474.         path, args, kwargs = constraint.deconstruct()
    
  475.         self.assertEqual(path, "django.db.models.UniqueConstraint")
    
  476.         self.assertEqual(args, ())
    
  477.         self.assertEqual(kwargs, {"fields": tuple(fields), "name": name})
    
  478. 
    
  479.     def test_deconstruction_with_condition(self):
    
  480.         fields = ["foo", "bar"]
    
  481.         name = "unique_fields"
    
  482.         condition = models.Q(foo=models.F("bar"))
    
  483.         constraint = models.UniqueConstraint(
    
  484.             fields=fields, name=name, condition=condition
    
  485.         )
    
  486.         path, args, kwargs = constraint.deconstruct()
    
  487.         self.assertEqual(path, "django.db.models.UniqueConstraint")
    
  488.         self.assertEqual(args, ())
    
  489.         self.assertEqual(
    
  490.             kwargs, {"fields": tuple(fields), "name": name, "condition": condition}
    
  491.         )
    
  492. 
    
  493.     def test_deconstruction_with_deferrable(self):
    
  494.         fields = ["foo"]
    
  495.         name = "unique_fields"
    
  496.         constraint = models.UniqueConstraint(
    
  497.             fields=fields,
    
  498.             name=name,
    
  499.             deferrable=models.Deferrable.DEFERRED,
    
  500.         )
    
  501.         path, args, kwargs = constraint.deconstruct()
    
  502.         self.assertEqual(path, "django.db.models.UniqueConstraint")
    
  503.         self.assertEqual(args, ())
    
  504.         self.assertEqual(
    
  505.             kwargs,
    
  506.             {
    
  507.                 "fields": tuple(fields),
    
  508.                 "name": name,
    
  509.                 "deferrable": models.Deferrable.DEFERRED,
    
  510.             },
    
  511.         )
    
  512. 
    
  513.     def test_deconstruction_with_include(self):
    
  514.         fields = ["foo", "bar"]
    
  515.         name = "unique_fields"
    
  516.         include = ["baz_1", "baz_2"]
    
  517.         constraint = models.UniqueConstraint(fields=fields, name=name, include=include)
    
  518.         path, args, kwargs = constraint.deconstruct()
    
  519.         self.assertEqual(path, "django.db.models.UniqueConstraint")
    
  520.         self.assertEqual(args, ())
    
  521.         self.assertEqual(
    
  522.             kwargs,
    
  523.             {
    
  524.                 "fields": tuple(fields),
    
  525.                 "name": name,
    
  526.                 "include": tuple(include),
    
  527.             },
    
  528.         )
    
  529. 
    
  530.     def test_deconstruction_with_opclasses(self):
    
  531.         fields = ["foo", "bar"]
    
  532.         name = "unique_fields"
    
  533.         opclasses = ["varchar_pattern_ops", "text_pattern_ops"]
    
  534.         constraint = models.UniqueConstraint(
    
  535.             fields=fields, name=name, opclasses=opclasses
    
  536.         )
    
  537.         path, args, kwargs = constraint.deconstruct()
    
  538.         self.assertEqual(path, "django.db.models.UniqueConstraint")
    
  539.         self.assertEqual(args, ())
    
  540.         self.assertEqual(
    
  541.             kwargs,
    
  542.             {
    
  543.                 "fields": tuple(fields),
    
  544.                 "name": name,
    
  545.                 "opclasses": opclasses,
    
  546.             },
    
  547.         )
    
  548. 
    
  549.     def test_deconstruction_with_expressions(self):
    
  550.         name = "unique_fields"
    
  551.         constraint = models.UniqueConstraint(Lower("title"), name=name)
    
  552.         path, args, kwargs = constraint.deconstruct()
    
  553.         self.assertEqual(path, "django.db.models.UniqueConstraint")
    
  554.         self.assertEqual(args, (Lower("title"),))
    
  555.         self.assertEqual(kwargs, {"name": name})
    
  556. 
    
  557.     def test_database_constraint(self):
    
  558.         with self.assertRaises(IntegrityError):
    
  559.             UniqueConstraintProduct.objects.create(
    
  560.                 name=self.p1.name, color=self.p1.color
    
  561.             )
    
  562. 
    
  563.     @skipUnlessDBFeature("supports_partial_indexes")
    
  564.     def test_database_constraint_with_condition(self):
    
  565.         UniqueConstraintConditionProduct.objects.create(name="p1")
    
  566.         UniqueConstraintConditionProduct.objects.create(name="p2")
    
  567.         with self.assertRaises(IntegrityError):
    
  568.             UniqueConstraintConditionProduct.objects.create(name="p1")
    
  569. 
    
  570.     def test_model_validation(self):
    
  571.         msg = "Unique constraint product with this Name and Color already exists."
    
  572.         with self.assertRaisesMessage(ValidationError, msg):
    
  573.             UniqueConstraintProduct(
    
  574.                 name=self.p1.name, color=self.p1.color
    
  575.             ).validate_constraints()
    
  576. 
    
  577.     @skipUnlessDBFeature("supports_partial_indexes")
    
  578.     def test_model_validation_with_condition(self):
    
  579.         """
    
  580.         Partial unique constraints are not ignored by
    
  581.         Model.validate_constraints().
    
  582.         """
    
  583.         obj1 = UniqueConstraintConditionProduct.objects.create(name="p1", color="red")
    
  584.         obj2 = UniqueConstraintConditionProduct.objects.create(name="p2")
    
  585.         UniqueConstraintConditionProduct(
    
  586.             name=obj1.name, color="blue"
    
  587.         ).validate_constraints()
    
  588.         msg = "Constraint “name_without_color_uniq” is violated."
    
  589.         with self.assertRaisesMessage(ValidationError, msg):
    
  590.             UniqueConstraintConditionProduct(name=obj2.name).validate_constraints()
    
  591. 
    
  592.     def test_model_validation_constraint_no_code_error(self):
    
  593.         class ValidateNoCodeErrorConstraint(UniqueConstraint):
    
  594.             def validate(self, model, instance, **kwargs):
    
  595.                 raise ValidationError({"name": ValidationError("Already exists.")})
    
  596. 
    
  597.         class NoCodeErrorConstraintModel(models.Model):
    
  598.             name = models.CharField(max_length=255)
    
  599. 
    
  600.             class Meta:
    
  601.                 constraints = [
    
  602.                     ValidateNoCodeErrorConstraint(
    
  603.                         Lower("name"),
    
  604.                         name="custom_validate_no_code_error",
    
  605.                     )
    
  606.                 ]
    
  607. 
    
  608.         msg = "{'name': ['Already exists.']}"
    
  609.         with self.assertRaisesMessage(ValidationError, msg):
    
  610.             NoCodeErrorConstraintModel(name="test").validate_constraints()
    
  611. 
    
  612.     def test_validate(self):
    
  613.         constraint = UniqueConstraintProduct._meta.constraints[0]
    
  614.         msg = "Unique constraint product with this Name and Color already exists."
    
  615.         non_unique_product = UniqueConstraintProduct(
    
  616.             name=self.p1.name, color=self.p1.color
    
  617.         )
    
  618.         with self.assertRaisesMessage(ValidationError, msg):
    
  619.             constraint.validate(UniqueConstraintProduct, non_unique_product)
    
  620.         # Null values are ignored.
    
  621.         constraint.validate(
    
  622.             UniqueConstraintProduct,
    
  623.             UniqueConstraintProduct(name=self.p2.name, color=None),
    
  624.         )
    
  625.         # Existing instances have their existing row excluded.
    
  626.         constraint.validate(UniqueConstraintProduct, self.p1)
    
  627.         # Unique fields are excluded.
    
  628.         constraint.validate(
    
  629.             UniqueConstraintProduct,
    
  630.             non_unique_product,
    
  631.             exclude={"name"},
    
  632.         )
    
  633.         constraint.validate(
    
  634.             UniqueConstraintProduct,
    
  635.             non_unique_product,
    
  636.             exclude={"color"},
    
  637.         )
    
  638.         constraint.validate(
    
  639.             UniqueConstraintProduct,
    
  640.             non_unique_product,
    
  641.             exclude={"name", "color"},
    
  642.         )
    
  643.         # Validation on a child instance.
    
  644.         with self.assertRaisesMessage(ValidationError, msg):
    
  645.             constraint.validate(
    
  646.                 UniqueConstraintProduct,
    
  647.                 ChildUniqueConstraintProduct(name=self.p1.name, color=self.p1.color),
    
  648.             )
    
  649. 
    
  650.     @skipUnlessDBFeature("supports_partial_indexes")
    
  651.     def test_validate_condition(self):
    
  652.         p1 = UniqueConstraintConditionProduct.objects.create(name="p1")
    
  653.         constraint = UniqueConstraintConditionProduct._meta.constraints[0]
    
  654.         msg = "Constraint “name_without_color_uniq” is violated."
    
  655.         with self.assertRaisesMessage(ValidationError, msg):
    
  656.             constraint.validate(
    
  657.                 UniqueConstraintConditionProduct,
    
  658.                 UniqueConstraintConditionProduct(name=p1.name, color=None),
    
  659.             )
    
  660.         # Values not matching condition are ignored.
    
  661.         constraint.validate(
    
  662.             UniqueConstraintConditionProduct,
    
  663.             UniqueConstraintConditionProduct(name=p1.name, color="anything-but-none"),
    
  664.         )
    
  665.         # Existing instances have their existing row excluded.
    
  666.         constraint.validate(UniqueConstraintConditionProduct, p1)
    
  667.         # Unique field is excluded.
    
  668.         constraint.validate(
    
  669.             UniqueConstraintConditionProduct,
    
  670.             UniqueConstraintConditionProduct(name=p1.name, color=None),
    
  671.             exclude={"name"},
    
  672.         )
    
  673. 
    
  674.     def test_validate_expression(self):
    
  675.         constraint = models.UniqueConstraint(Lower("name"), name="name_lower_uniq")
    
  676.         msg = "Constraint “name_lower_uniq” is violated."
    
  677.         with self.assertRaisesMessage(ValidationError, msg):
    
  678.             constraint.validate(
    
  679.                 UniqueConstraintProduct,
    
  680.                 UniqueConstraintProduct(name=self.p1.name.upper()),
    
  681.             )
    
  682.         constraint.validate(
    
  683.             UniqueConstraintProduct,
    
  684.             UniqueConstraintProduct(name="another-name"),
    
  685.         )
    
  686.         # Existing instances have their existing row excluded.
    
  687.         constraint.validate(UniqueConstraintProduct, self.p1)
    
  688.         # Unique field is excluded.
    
  689.         constraint.validate(
    
  690.             UniqueConstraintProduct,
    
  691.             UniqueConstraintProduct(name=self.p1.name.upper()),
    
  692.             exclude={"name"},
    
  693.         )
    
  694. 
    
  695.     def test_validate_ordered_expression(self):
    
  696.         constraint = models.UniqueConstraint(
    
  697.             Lower("name").desc(), name="name_lower_uniq_desc"
    
  698.         )
    
  699.         msg = "Constraint “name_lower_uniq_desc” is violated."
    
  700.         with self.assertRaisesMessage(ValidationError, msg):
    
  701.             constraint.validate(
    
  702.                 UniqueConstraintProduct,
    
  703.                 UniqueConstraintProduct(name=self.p1.name.upper()),
    
  704.             )
    
  705.         constraint.validate(
    
  706.             UniqueConstraintProduct,
    
  707.             UniqueConstraintProduct(name="another-name"),
    
  708.         )
    
  709.         # Existing instances have their existing row excluded.
    
  710.         constraint.validate(UniqueConstraintProduct, self.p1)
    
  711.         # Unique field is excluded.
    
  712.         constraint.validate(
    
  713.             UniqueConstraintProduct,
    
  714.             UniqueConstraintProduct(name=self.p1.name.upper()),
    
  715.             exclude={"name"},
    
  716.         )
    
  717. 
    
  718.     def test_validate_expression_condition(self):
    
  719.         constraint = models.UniqueConstraint(
    
  720.             Lower("name"),
    
  721.             name="name_lower_without_color_uniq",
    
  722.             condition=models.Q(color__isnull=True),
    
  723.         )
    
  724.         non_unique_product = UniqueConstraintProduct(name=self.p2.name.upper())
    
  725.         msg = "Constraint “name_lower_without_color_uniq” is violated."
    
  726.         with self.assertRaisesMessage(ValidationError, msg):
    
  727.             constraint.validate(UniqueConstraintProduct, non_unique_product)
    
  728.         # Values not matching condition are ignored.
    
  729.         constraint.validate(
    
  730.             UniqueConstraintProduct,
    
  731.             UniqueConstraintProduct(name=self.p1.name, color=self.p1.color),
    
  732.         )
    
  733.         # Existing instances have their existing row excluded.
    
  734.         constraint.validate(UniqueConstraintProduct, self.p2)
    
  735.         # Unique field is excluded.
    
  736.         constraint.validate(
    
  737.             UniqueConstraintProduct,
    
  738.             non_unique_product,
    
  739.             exclude={"name"},
    
  740.         )
    
  741.         # Field from a condition is excluded.
    
  742.         constraint.validate(
    
  743.             UniqueConstraintProduct,
    
  744.             non_unique_product,
    
  745.             exclude={"color"},
    
  746.         )
    
  747. 
    
  748.     def test_validate_expression_str(self):
    
  749.         constraint = models.UniqueConstraint("name", name="name_uniq")
    
  750.         msg = "Constraint “name_uniq” is violated."
    
  751.         with self.assertRaisesMessage(ValidationError, msg):
    
  752.             constraint.validate(
    
  753.                 UniqueConstraintProduct,
    
  754.                 UniqueConstraintProduct(name=self.p1.name),
    
  755.             )
    
  756.         constraint.validate(
    
  757.             UniqueConstraintProduct,
    
  758.             UniqueConstraintProduct(name=self.p1.name),
    
  759.             exclude={"name"},
    
  760.         )
    
  761. 
    
  762.     def test_name(self):
    
  763.         constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
    
  764.         expected_name = "name_color_uniq"
    
  765.         self.assertIn(expected_name, constraints)
    
  766. 
    
  767.     def test_condition_must_be_q(self):
    
  768.         with self.assertRaisesMessage(
    
  769.             ValueError, "UniqueConstraint.condition must be a Q instance."
    
  770.         ):
    
  771.             models.UniqueConstraint(name="uniq", fields=["name"], condition="invalid")
    
  772. 
    
  773.     @skipUnlessDBFeature("supports_deferrable_unique_constraints")
    
  774.     def test_initially_deferred_database_constraint(self):
    
  775.         obj_1 = UniqueConstraintDeferrable.objects.create(name="p1", shelf="front")
    
  776.         obj_2 = UniqueConstraintDeferrable.objects.create(name="p2", shelf="back")
    
  777. 
    
  778.         def swap():
    
  779.             obj_1.name, obj_2.name = obj_2.name, obj_1.name
    
  780.             obj_1.save()
    
  781.             obj_2.save()
    
  782. 
    
  783.         swap()
    
  784.         # Behavior can be changed with SET CONSTRAINTS.
    
  785.         with self.assertRaises(IntegrityError):
    
  786.             with atomic(), connection.cursor() as cursor:
    
  787.                 constraint_name = connection.ops.quote_name("name_init_deferred_uniq")
    
  788.                 cursor.execute("SET CONSTRAINTS %s IMMEDIATE" % constraint_name)
    
  789.                 swap()
    
  790. 
    
  791.     @skipUnlessDBFeature("supports_deferrable_unique_constraints")
    
  792.     def test_initially_immediate_database_constraint(self):
    
  793.         obj_1 = UniqueConstraintDeferrable.objects.create(name="p1", shelf="front")
    
  794.         obj_2 = UniqueConstraintDeferrable.objects.create(name="p2", shelf="back")
    
  795.         obj_1.shelf, obj_2.shelf = obj_2.shelf, obj_1.shelf
    
  796.         with self.assertRaises(IntegrityError), atomic():
    
  797.             obj_1.save()
    
  798.         # Behavior can be changed with SET CONSTRAINTS.
    
  799.         with connection.cursor() as cursor:
    
  800.             constraint_name = connection.ops.quote_name("sheld_init_immediate_uniq")
    
  801.             cursor.execute("SET CONSTRAINTS %s DEFERRED" % constraint_name)
    
  802.             obj_1.save()
    
  803.             obj_2.save()
    
  804. 
    
  805.     def test_deferrable_with_condition(self):
    
  806.         message = "UniqueConstraint with conditions cannot be deferred."
    
  807.         with self.assertRaisesMessage(ValueError, message):
    
  808.             models.UniqueConstraint(
    
  809.                 fields=["name"],
    
  810.                 name="name_without_color_unique",
    
  811.                 condition=models.Q(color__isnull=True),
    
  812.                 deferrable=models.Deferrable.DEFERRED,
    
  813.             )
    
  814. 
    
  815.     def test_deferrable_with_include(self):
    
  816.         message = "UniqueConstraint with include fields cannot be deferred."
    
  817.         with self.assertRaisesMessage(ValueError, message):
    
  818.             models.UniqueConstraint(
    
  819.                 fields=["name"],
    
  820.                 name="name_inc_color_color_unique",
    
  821.                 include=["color"],
    
  822.                 deferrable=models.Deferrable.DEFERRED,
    
  823.             )
    
  824. 
    
  825.     def test_deferrable_with_opclasses(self):
    
  826.         message = "UniqueConstraint with opclasses cannot be deferred."
    
  827.         with self.assertRaisesMessage(ValueError, message):
    
  828.             models.UniqueConstraint(
    
  829.                 fields=["name"],
    
  830.                 name="name_text_pattern_ops_unique",
    
  831.                 opclasses=["text_pattern_ops"],
    
  832.                 deferrable=models.Deferrable.DEFERRED,
    
  833.             )
    
  834. 
    
  835.     def test_deferrable_with_expressions(self):
    
  836.         message = "UniqueConstraint with expressions cannot be deferred."
    
  837.         with self.assertRaisesMessage(ValueError, message):
    
  838.             models.UniqueConstraint(
    
  839.                 Lower("name"),
    
  840.                 name="deferred_expression_unique",
    
  841.                 deferrable=models.Deferrable.DEFERRED,
    
  842.             )
    
  843. 
    
  844.     def test_invalid_defer_argument(self):
    
  845.         message = "UniqueConstraint.deferrable must be a Deferrable instance."
    
  846.         with self.assertRaisesMessage(ValueError, message):
    
  847.             models.UniqueConstraint(
    
  848.                 fields=["name"],
    
  849.                 name="name_invalid",
    
  850.                 deferrable="invalid",
    
  851.             )
    
  852. 
    
  853.     @skipUnlessDBFeature(
    
  854.         "supports_table_check_constraints",
    
  855.         "supports_covering_indexes",
    
  856.     )
    
  857.     def test_include_database_constraint(self):
    
  858.         UniqueConstraintInclude.objects.create(name="p1", color="red")
    
  859.         with self.assertRaises(IntegrityError):
    
  860.             UniqueConstraintInclude.objects.create(name="p1", color="blue")
    
  861. 
    
  862.     def test_invalid_include_argument(self):
    
  863.         msg = "UniqueConstraint.include must be a list or tuple."
    
  864.         with self.assertRaisesMessage(ValueError, msg):
    
  865.             models.UniqueConstraint(
    
  866.                 name="uniq_include",
    
  867.                 fields=["field"],
    
  868.                 include="other",
    
  869.             )
    
  870. 
    
  871.     def test_invalid_opclasses_argument(self):
    
  872.         msg = "UniqueConstraint.opclasses must be a list or tuple."
    
  873.         with self.assertRaisesMessage(ValueError, msg):
    
  874.             models.UniqueConstraint(
    
  875.                 name="uniq_opclasses",
    
  876.                 fields=["field"],
    
  877.                 opclasses="jsonb_path_ops",
    
  878.             )
    
  879. 
    
  880.     def test_opclasses_and_fields_same_length(self):
    
  881.         msg = (
    
  882.             "UniqueConstraint.fields and UniqueConstraint.opclasses must have "
    
  883.             "the same number of elements."
    
  884.         )
    
  885.         with self.assertRaisesMessage(ValueError, msg):
    
  886.             models.UniqueConstraint(
    
  887.                 name="uniq_opclasses",
    
  888.                 fields=["field"],
    
  889.                 opclasses=["foo", "bar"],
    
  890.             )
    
  891. 
    
  892.     def test_requires_field_or_expression(self):
    
  893.         msg = (
    
  894.             "At least one field or expression is required to define a unique "
    
  895.             "constraint."
    
  896.         )
    
  897.         with self.assertRaisesMessage(ValueError, msg):
    
  898.             models.UniqueConstraint(name="name")
    
  899. 
    
  900.     def test_expressions_and_fields_mutually_exclusive(self):
    
  901.         msg = "UniqueConstraint.fields and expressions are mutually exclusive."
    
  902.         with self.assertRaisesMessage(ValueError, msg):
    
  903.             models.UniqueConstraint(Lower("field_1"), fields=["field_2"], name="name")
    
  904. 
    
  905.     def test_expressions_with_opclasses(self):
    
  906.         msg = (
    
  907.             "UniqueConstraint.opclasses cannot be used with expressions. Use "
    
  908.             "django.contrib.postgres.indexes.OpClass() instead."
    
  909.         )
    
  910.         with self.assertRaisesMessage(ValueError, msg):
    
  911.             models.UniqueConstraint(
    
  912.                 Lower("field"),
    
  913.                 name="test_func_opclass",
    
  914.                 opclasses=["jsonb_path_ops"],
    
  915.             )
    
  916. 
    
  917.     def test_requires_name(self):
    
  918.         msg = "A unique constraint must be named."
    
  919.         with self.assertRaisesMessage(ValueError, msg):
    
  920.             models.UniqueConstraint(fields=["field"])