tests.py 41 KB

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