tests.py 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130
  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_eq_with_nulls_distinct(self):
  463. constraint_1 = models.UniqueConstraint(
  464. Lower("title"),
  465. nulls_distinct=False,
  466. name="book_func_nulls_distinct_uq",
  467. )
  468. constraint_2 = models.UniqueConstraint(
  469. Lower("title"),
  470. nulls_distinct=True,
  471. name="book_func_nulls_distinct_uq",
  472. )
  473. constraint_3 = models.UniqueConstraint(
  474. Lower("title"),
  475. name="book_func_nulls_distinct_uq",
  476. )
  477. self.assertEqual(constraint_1, constraint_1)
  478. self.assertEqual(constraint_1, mock.ANY)
  479. self.assertNotEqual(constraint_1, constraint_2)
  480. self.assertNotEqual(constraint_1, constraint_3)
  481. self.assertNotEqual(constraint_2, constraint_3)
  482. def test_repr(self):
  483. fields = ["foo", "bar"]
  484. name = "unique_fields"
  485. constraint = models.UniqueConstraint(fields=fields, name=name)
  486. self.assertEqual(
  487. repr(constraint),
  488. "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields'>",
  489. )
  490. def test_repr_with_condition(self):
  491. constraint = models.UniqueConstraint(
  492. fields=["foo", "bar"],
  493. name="unique_fields",
  494. condition=models.Q(foo=models.F("bar")),
  495. )
  496. self.assertEqual(
  497. repr(constraint),
  498. "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields' "
  499. "condition=(AND: ('foo', F(bar)))>",
  500. )
  501. def test_repr_with_deferrable(self):
  502. constraint = models.UniqueConstraint(
  503. fields=["foo", "bar"],
  504. name="unique_fields",
  505. deferrable=models.Deferrable.IMMEDIATE,
  506. )
  507. self.assertEqual(
  508. repr(constraint),
  509. "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields' "
  510. "deferrable=Deferrable.IMMEDIATE>",
  511. )
  512. def test_repr_with_include(self):
  513. constraint = models.UniqueConstraint(
  514. fields=["foo", "bar"],
  515. name="include_fields",
  516. include=["baz_1", "baz_2"],
  517. )
  518. self.assertEqual(
  519. repr(constraint),
  520. "<UniqueConstraint: fields=('foo', 'bar') name='include_fields' "
  521. "include=('baz_1', 'baz_2')>",
  522. )
  523. def test_repr_with_opclasses(self):
  524. constraint = models.UniqueConstraint(
  525. fields=["foo", "bar"],
  526. name="opclasses_fields",
  527. opclasses=["text_pattern_ops", "varchar_pattern_ops"],
  528. )
  529. self.assertEqual(
  530. repr(constraint),
  531. "<UniqueConstraint: fields=('foo', 'bar') name='opclasses_fields' "
  532. "opclasses=['text_pattern_ops', 'varchar_pattern_ops']>",
  533. )
  534. def test_repr_with_nulls_distinct(self):
  535. constraint = models.UniqueConstraint(
  536. fields=["foo", "bar"],
  537. name="nulls_distinct_fields",
  538. nulls_distinct=False,
  539. )
  540. self.assertEqual(
  541. repr(constraint),
  542. "<UniqueConstraint: fields=('foo', 'bar') name='nulls_distinct_fields' "
  543. "nulls_distinct=False>",
  544. )
  545. def test_repr_with_expressions(self):
  546. constraint = models.UniqueConstraint(
  547. Lower("title"),
  548. F("author"),
  549. name="book_func_uq",
  550. )
  551. self.assertEqual(
  552. repr(constraint),
  553. "<UniqueConstraint: expressions=(Lower(F(title)), F(author)) "
  554. "name='book_func_uq'>",
  555. )
  556. def test_repr_with_violation_error_message(self):
  557. constraint = models.UniqueConstraint(
  558. models.F("baz__lower"),
  559. name="unique_lower_baz",
  560. violation_error_message="BAZ",
  561. )
  562. self.assertEqual(
  563. repr(constraint),
  564. (
  565. "<UniqueConstraint: expressions=(F(baz__lower),) "
  566. "name='unique_lower_baz' violation_error_message='BAZ'>"
  567. ),
  568. )
  569. def test_repr_with_violation_error_code(self):
  570. constraint = models.UniqueConstraint(
  571. models.F("baz__lower"),
  572. name="unique_lower_baz",
  573. violation_error_code="baz",
  574. )
  575. self.assertEqual(
  576. repr(constraint),
  577. (
  578. "<UniqueConstraint: expressions=(F(baz__lower),) "
  579. "name='unique_lower_baz' violation_error_code='baz'>"
  580. ),
  581. )
  582. def test_deconstruction(self):
  583. fields = ["foo", "bar"]
  584. name = "unique_fields"
  585. constraint = models.UniqueConstraint(fields=fields, name=name)
  586. path, args, kwargs = constraint.deconstruct()
  587. self.assertEqual(path, "django.db.models.UniqueConstraint")
  588. self.assertEqual(args, ())
  589. self.assertEqual(kwargs, {"fields": tuple(fields), "name": name})
  590. def test_deconstruction_with_condition(self):
  591. fields = ["foo", "bar"]
  592. name = "unique_fields"
  593. condition = models.Q(foo=models.F("bar"))
  594. constraint = models.UniqueConstraint(
  595. fields=fields, name=name, condition=condition
  596. )
  597. path, args, kwargs = constraint.deconstruct()
  598. self.assertEqual(path, "django.db.models.UniqueConstraint")
  599. self.assertEqual(args, ())
  600. self.assertEqual(
  601. kwargs, {"fields": tuple(fields), "name": name, "condition": condition}
  602. )
  603. def test_deconstruction_with_deferrable(self):
  604. fields = ["foo"]
  605. name = "unique_fields"
  606. constraint = models.UniqueConstraint(
  607. fields=fields,
  608. name=name,
  609. deferrable=models.Deferrable.DEFERRED,
  610. )
  611. path, args, kwargs = constraint.deconstruct()
  612. self.assertEqual(path, "django.db.models.UniqueConstraint")
  613. self.assertEqual(args, ())
  614. self.assertEqual(
  615. kwargs,
  616. {
  617. "fields": tuple(fields),
  618. "name": name,
  619. "deferrable": models.Deferrable.DEFERRED,
  620. },
  621. )
  622. def test_deconstruction_with_include(self):
  623. fields = ["foo", "bar"]
  624. name = "unique_fields"
  625. include = ["baz_1", "baz_2"]
  626. constraint = models.UniqueConstraint(fields=fields, name=name, include=include)
  627. path, args, kwargs = constraint.deconstruct()
  628. self.assertEqual(path, "django.db.models.UniqueConstraint")
  629. self.assertEqual(args, ())
  630. self.assertEqual(
  631. kwargs,
  632. {
  633. "fields": tuple(fields),
  634. "name": name,
  635. "include": tuple(include),
  636. },
  637. )
  638. def test_deconstruction_with_opclasses(self):
  639. fields = ["foo", "bar"]
  640. name = "unique_fields"
  641. opclasses = ["varchar_pattern_ops", "text_pattern_ops"]
  642. constraint = models.UniqueConstraint(
  643. fields=fields, name=name, opclasses=opclasses
  644. )
  645. path, args, kwargs = constraint.deconstruct()
  646. self.assertEqual(path, "django.db.models.UniqueConstraint")
  647. self.assertEqual(args, ())
  648. self.assertEqual(
  649. kwargs,
  650. {
  651. "fields": tuple(fields),
  652. "name": name,
  653. "opclasses": opclasses,
  654. },
  655. )
  656. def test_deconstruction_with_nulls_distinct(self):
  657. fields = ["foo", "bar"]
  658. name = "unique_fields"
  659. constraint = models.UniqueConstraint(
  660. fields=fields, name=name, nulls_distinct=True
  661. )
  662. path, args, kwargs = constraint.deconstruct()
  663. self.assertEqual(path, "django.db.models.UniqueConstraint")
  664. self.assertEqual(args, ())
  665. self.assertEqual(
  666. kwargs,
  667. {
  668. "fields": tuple(fields),
  669. "name": name,
  670. "nulls_distinct": True,
  671. },
  672. )
  673. def test_deconstruction_with_expressions(self):
  674. name = "unique_fields"
  675. constraint = models.UniqueConstraint(Lower("title"), name=name)
  676. path, args, kwargs = constraint.deconstruct()
  677. self.assertEqual(path, "django.db.models.UniqueConstraint")
  678. self.assertEqual(args, (Lower("title"),))
  679. self.assertEqual(kwargs, {"name": name})
  680. def test_database_constraint(self):
  681. with self.assertRaises(IntegrityError):
  682. UniqueConstraintProduct.objects.create(
  683. name=self.p1.name, color=self.p1.color
  684. )
  685. @skipUnlessDBFeature("supports_partial_indexes")
  686. def test_database_constraint_with_condition(self):
  687. UniqueConstraintConditionProduct.objects.create(name="p1")
  688. UniqueConstraintConditionProduct.objects.create(name="p2")
  689. with self.assertRaises(IntegrityError):
  690. UniqueConstraintConditionProduct.objects.create(name="p1")
  691. def test_model_validation(self):
  692. msg = "Unique constraint product with this Name and Color already exists."
  693. with self.assertRaisesMessage(ValidationError, msg):
  694. UniqueConstraintProduct(
  695. name=self.p1.name, color=self.p1.color
  696. ).validate_constraints()
  697. @skipUnlessDBFeature("supports_partial_indexes")
  698. def test_model_validation_with_condition(self):
  699. """
  700. Partial unique constraints are not ignored by
  701. Model.validate_constraints().
  702. """
  703. obj1 = UniqueConstraintConditionProduct.objects.create(name="p1", color="red")
  704. obj2 = UniqueConstraintConditionProduct.objects.create(name="p2")
  705. UniqueConstraintConditionProduct(
  706. name=obj1.name, color="blue"
  707. ).validate_constraints()
  708. msg = "Constraint “name_without_color_uniq” is violated."
  709. with self.assertRaisesMessage(ValidationError, msg):
  710. UniqueConstraintConditionProduct(name=obj2.name).validate_constraints()
  711. def test_model_validation_constraint_no_code_error(self):
  712. class ValidateNoCodeErrorConstraint(UniqueConstraint):
  713. def validate(self, model, instance, **kwargs):
  714. raise ValidationError({"name": ValidationError("Already exists.")})
  715. class NoCodeErrorConstraintModel(models.Model):
  716. name = models.CharField(max_length=255)
  717. class Meta:
  718. constraints = [
  719. ValidateNoCodeErrorConstraint(
  720. Lower("name"),
  721. name="custom_validate_no_code_error",
  722. )
  723. ]
  724. msg = "{'name': ['Already exists.']}"
  725. with self.assertRaisesMessage(ValidationError, msg):
  726. NoCodeErrorConstraintModel(name="test").validate_constraints()
  727. def test_validate(self):
  728. constraint = UniqueConstraintProduct._meta.constraints[0]
  729. msg = "Unique constraint product with this Name and Color already exists."
  730. non_unique_product = UniqueConstraintProduct(
  731. name=self.p1.name, color=self.p1.color
  732. )
  733. with self.assertRaisesMessage(ValidationError, msg) as cm:
  734. constraint.validate(UniqueConstraintProduct, non_unique_product)
  735. self.assertEqual(cm.exception.code, "unique_together")
  736. # Null values are ignored.
  737. constraint.validate(
  738. UniqueConstraintProduct,
  739. UniqueConstraintProduct(name=self.p2.name, color=None),
  740. )
  741. # Existing instances have their existing row excluded.
  742. constraint.validate(UniqueConstraintProduct, self.p1)
  743. # Unique fields are excluded.
  744. constraint.validate(
  745. UniqueConstraintProduct,
  746. non_unique_product,
  747. exclude={"name"},
  748. )
  749. constraint.validate(
  750. UniqueConstraintProduct,
  751. non_unique_product,
  752. exclude={"color"},
  753. )
  754. constraint.validate(
  755. UniqueConstraintProduct,
  756. non_unique_product,
  757. exclude={"name", "color"},
  758. )
  759. # Validation on a child instance.
  760. with self.assertRaisesMessage(ValidationError, msg):
  761. constraint.validate(
  762. UniqueConstraintProduct,
  763. ChildUniqueConstraintProduct(name=self.p1.name, color=self.p1.color),
  764. )
  765. @skipUnlessDBFeature("supports_partial_indexes")
  766. def test_validate_condition(self):
  767. p1 = UniqueConstraintConditionProduct.objects.create(name="p1")
  768. constraint = UniqueConstraintConditionProduct._meta.constraints[0]
  769. msg = "Constraint “name_without_color_uniq” is violated."
  770. with self.assertRaisesMessage(ValidationError, msg):
  771. constraint.validate(
  772. UniqueConstraintConditionProduct,
  773. UniqueConstraintConditionProduct(name=p1.name, color=None),
  774. )
  775. # Values not matching condition are ignored.
  776. constraint.validate(
  777. UniqueConstraintConditionProduct,
  778. UniqueConstraintConditionProduct(name=p1.name, color="anything-but-none"),
  779. )
  780. # Existing instances have their existing row excluded.
  781. constraint.validate(UniqueConstraintConditionProduct, p1)
  782. # Unique field is excluded.
  783. constraint.validate(
  784. UniqueConstraintConditionProduct,
  785. UniqueConstraintConditionProduct(name=p1.name, color=None),
  786. exclude={"name"},
  787. )
  788. @skipUnlessDBFeature("supports_partial_indexes")
  789. def test_validate_conditon_custom_error(self):
  790. p1 = UniqueConstraintConditionProduct.objects.create(name="p1")
  791. constraint = models.UniqueConstraint(
  792. fields=["name"],
  793. name="name_without_color_uniq",
  794. condition=models.Q(color__isnull=True),
  795. violation_error_code="custom_code",
  796. violation_error_message="Custom message",
  797. )
  798. msg = "Custom message"
  799. with self.assertRaisesMessage(ValidationError, msg) as cm:
  800. constraint.validate(
  801. UniqueConstraintConditionProduct,
  802. UniqueConstraintConditionProduct(name=p1.name, color=None),
  803. )
  804. self.assertEqual(cm.exception.code, "custom_code")
  805. def test_validate_expression(self):
  806. constraint = models.UniqueConstraint(Lower("name"), name="name_lower_uniq")
  807. msg = "Constraint “name_lower_uniq” is violated."
  808. with self.assertRaisesMessage(ValidationError, msg):
  809. constraint.validate(
  810. UniqueConstraintProduct,
  811. UniqueConstraintProduct(name=self.p1.name.upper()),
  812. )
  813. constraint.validate(
  814. UniqueConstraintProduct,
  815. UniqueConstraintProduct(name="another-name"),
  816. )
  817. # Existing instances have their existing row excluded.
  818. constraint.validate(UniqueConstraintProduct, self.p1)
  819. # Unique field is excluded.
  820. constraint.validate(
  821. UniqueConstraintProduct,
  822. UniqueConstraintProduct(name=self.p1.name.upper()),
  823. exclude={"name"},
  824. )
  825. def test_validate_ordered_expression(self):
  826. constraint = models.UniqueConstraint(
  827. Lower("name").desc(), name="name_lower_uniq_desc"
  828. )
  829. msg = "Constraint “name_lower_uniq_desc” is violated."
  830. with self.assertRaisesMessage(ValidationError, msg):
  831. constraint.validate(
  832. UniqueConstraintProduct,
  833. UniqueConstraintProduct(name=self.p1.name.upper()),
  834. )
  835. constraint.validate(
  836. UniqueConstraintProduct,
  837. UniqueConstraintProduct(name="another-name"),
  838. )
  839. # Existing instances have their existing row excluded.
  840. constraint.validate(UniqueConstraintProduct, self.p1)
  841. # Unique field is excluded.
  842. constraint.validate(
  843. UniqueConstraintProduct,
  844. UniqueConstraintProduct(name=self.p1.name.upper()),
  845. exclude={"name"},
  846. )
  847. def test_validate_expression_condition(self):
  848. constraint = models.UniqueConstraint(
  849. Lower("name"),
  850. name="name_lower_without_color_uniq",
  851. condition=models.Q(color__isnull=True),
  852. )
  853. non_unique_product = UniqueConstraintProduct(name=self.p2.name.upper())
  854. msg = "Constraint “name_lower_without_color_uniq” is violated."
  855. with self.assertRaisesMessage(ValidationError, msg):
  856. constraint.validate(UniqueConstraintProduct, non_unique_product)
  857. # Values not matching condition are ignored.
  858. constraint.validate(
  859. UniqueConstraintProduct,
  860. UniqueConstraintProduct(name=self.p1.name, color=self.p1.color),
  861. )
  862. # Existing instances have their existing row excluded.
  863. constraint.validate(UniqueConstraintProduct, self.p2)
  864. # Unique field is excluded.
  865. constraint.validate(
  866. UniqueConstraintProduct,
  867. non_unique_product,
  868. exclude={"name"},
  869. )
  870. # Field from a condition is excluded.
  871. constraint.validate(
  872. UniqueConstraintProduct,
  873. non_unique_product,
  874. exclude={"color"},
  875. )
  876. def test_validate_expression_str(self):
  877. constraint = models.UniqueConstraint("name", name="name_uniq")
  878. msg = "Constraint “name_uniq” is violated."
  879. with self.assertRaisesMessage(ValidationError, msg):
  880. constraint.validate(
  881. UniqueConstraintProduct,
  882. UniqueConstraintProduct(name=self.p1.name),
  883. )
  884. constraint.validate(
  885. UniqueConstraintProduct,
  886. UniqueConstraintProduct(name=self.p1.name),
  887. exclude={"name"},
  888. )
  889. def test_name(self):
  890. constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
  891. expected_name = "name_color_uniq"
  892. self.assertIn(expected_name, constraints)
  893. def test_condition_must_be_q(self):
  894. with self.assertRaisesMessage(
  895. ValueError, "UniqueConstraint.condition must be a Q instance."
  896. ):
  897. models.UniqueConstraint(name="uniq", fields=["name"], condition="invalid")
  898. @skipUnlessDBFeature("supports_deferrable_unique_constraints")
  899. def test_initially_deferred_database_constraint(self):
  900. obj_1 = UniqueConstraintDeferrable.objects.create(name="p1", shelf="front")
  901. obj_2 = UniqueConstraintDeferrable.objects.create(name="p2", shelf="back")
  902. def swap():
  903. obj_1.name, obj_2.name = obj_2.name, obj_1.name
  904. obj_1.save()
  905. obj_2.save()
  906. swap()
  907. # Behavior can be changed with SET CONSTRAINTS.
  908. with self.assertRaises(IntegrityError):
  909. with atomic(), connection.cursor() as cursor:
  910. constraint_name = connection.ops.quote_name("name_init_deferred_uniq")
  911. cursor.execute("SET CONSTRAINTS %s IMMEDIATE" % constraint_name)
  912. swap()
  913. @skipUnlessDBFeature("supports_deferrable_unique_constraints")
  914. def test_initially_immediate_database_constraint(self):
  915. obj_1 = UniqueConstraintDeferrable.objects.create(name="p1", shelf="front")
  916. obj_2 = UniqueConstraintDeferrable.objects.create(name="p2", shelf="back")
  917. obj_1.shelf, obj_2.shelf = obj_2.shelf, obj_1.shelf
  918. with self.assertRaises(IntegrityError), atomic():
  919. obj_1.save()
  920. # Behavior can be changed with SET CONSTRAINTS.
  921. with connection.cursor() as cursor:
  922. constraint_name = connection.ops.quote_name("sheld_init_immediate_uniq")
  923. cursor.execute("SET CONSTRAINTS %s DEFERRED" % constraint_name)
  924. obj_1.save()
  925. obj_2.save()
  926. def test_deferrable_with_condition(self):
  927. message = "UniqueConstraint with conditions cannot be deferred."
  928. with self.assertRaisesMessage(ValueError, message):
  929. models.UniqueConstraint(
  930. fields=["name"],
  931. name="name_without_color_unique",
  932. condition=models.Q(color__isnull=True),
  933. deferrable=models.Deferrable.DEFERRED,
  934. )
  935. def test_deferrable_with_include(self):
  936. message = "UniqueConstraint with include fields cannot be deferred."
  937. with self.assertRaisesMessage(ValueError, message):
  938. models.UniqueConstraint(
  939. fields=["name"],
  940. name="name_inc_color_color_unique",
  941. include=["color"],
  942. deferrable=models.Deferrable.DEFERRED,
  943. )
  944. def test_deferrable_with_opclasses(self):
  945. message = "UniqueConstraint with opclasses cannot be deferred."
  946. with self.assertRaisesMessage(ValueError, message):
  947. models.UniqueConstraint(
  948. fields=["name"],
  949. name="name_text_pattern_ops_unique",
  950. opclasses=["text_pattern_ops"],
  951. deferrable=models.Deferrable.DEFERRED,
  952. )
  953. def test_deferrable_with_expressions(self):
  954. message = "UniqueConstraint with expressions cannot be deferred."
  955. with self.assertRaisesMessage(ValueError, message):
  956. models.UniqueConstraint(
  957. Lower("name"),
  958. name="deferred_expression_unique",
  959. deferrable=models.Deferrable.DEFERRED,
  960. )
  961. def test_invalid_defer_argument(self):
  962. message = "UniqueConstraint.deferrable must be a Deferrable instance."
  963. with self.assertRaisesMessage(ValueError, message):
  964. models.UniqueConstraint(
  965. fields=["name"],
  966. name="name_invalid",
  967. deferrable="invalid",
  968. )
  969. @skipUnlessDBFeature(
  970. "supports_table_check_constraints",
  971. "supports_covering_indexes",
  972. )
  973. def test_include_database_constraint(self):
  974. UniqueConstraintInclude.objects.create(name="p1", color="red")
  975. with self.assertRaises(IntegrityError):
  976. UniqueConstraintInclude.objects.create(name="p1", color="blue")
  977. def test_invalid_include_argument(self):
  978. msg = "UniqueConstraint.include must be a list or tuple."
  979. with self.assertRaisesMessage(ValueError, msg):
  980. models.UniqueConstraint(
  981. name="uniq_include",
  982. fields=["field"],
  983. include="other",
  984. )
  985. def test_invalid_opclasses_argument(self):
  986. msg = "UniqueConstraint.opclasses must be a list or tuple."
  987. with self.assertRaisesMessage(ValueError, msg):
  988. models.UniqueConstraint(
  989. name="uniq_opclasses",
  990. fields=["field"],
  991. opclasses="jsonb_path_ops",
  992. )
  993. def test_invalid_nulls_distinct_argument(self):
  994. msg = "UniqueConstraint.nulls_distinct must be a bool."
  995. with self.assertRaisesMessage(ValueError, msg):
  996. models.UniqueConstraint(
  997. name="uniq_opclasses", fields=["field"], nulls_distinct="NULLS DISTINCT"
  998. )
  999. def test_opclasses_and_fields_same_length(self):
  1000. msg = (
  1001. "UniqueConstraint.fields and UniqueConstraint.opclasses must have "
  1002. "the same number of elements."
  1003. )
  1004. with self.assertRaisesMessage(ValueError, msg):
  1005. models.UniqueConstraint(
  1006. name="uniq_opclasses",
  1007. fields=["field"],
  1008. opclasses=["foo", "bar"],
  1009. )
  1010. def test_requires_field_or_expression(self):
  1011. msg = (
  1012. "At least one field or expression is required to define a unique "
  1013. "constraint."
  1014. )
  1015. with self.assertRaisesMessage(ValueError, msg):
  1016. models.UniqueConstraint(name="name")
  1017. def test_expressions_and_fields_mutually_exclusive(self):
  1018. msg = "UniqueConstraint.fields and expressions are mutually exclusive."
  1019. with self.assertRaisesMessage(ValueError, msg):
  1020. models.UniqueConstraint(Lower("field_1"), fields=["field_2"], name="name")
  1021. def test_expressions_with_opclasses(self):
  1022. msg = (
  1023. "UniqueConstraint.opclasses cannot be used with expressions. Use "
  1024. "django.contrib.postgres.indexes.OpClass() instead."
  1025. )
  1026. with self.assertRaisesMessage(ValueError, msg):
  1027. models.UniqueConstraint(
  1028. Lower("field"),
  1029. name="test_func_opclass",
  1030. opclasses=["jsonb_path_ops"],
  1031. )
  1032. def test_requires_name(self):
  1033. msg = "A unique constraint must be named."
  1034. with self.assertRaisesMessage(ValueError, msg):
  1035. models.UniqueConstraint(fields=["field"])