tests.py 36 KB

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