tests.py 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441
  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 Abs, Lower, Sqrt, Upper
  7. from django.db.transaction import atomic
  8. from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
  9. from .models import (
  10. ChildModel,
  11. ChildUniqueConstraintProduct,
  12. GeneratedFieldStoredProduct,
  13. GeneratedFieldVirtualProduct,
  14. JSONFieldModel,
  15. ModelWithDatabaseDefault,
  16. Product,
  17. UniqueConstraintConditionProduct,
  18. UniqueConstraintDeferrable,
  19. UniqueConstraintInclude,
  20. UniqueConstraintProduct,
  21. )
  22. def get_constraints(table):
  23. with connection.cursor() as cursor:
  24. return connection.introspection.get_constraints(cursor, table)
  25. class BaseConstraintTests(SimpleTestCase):
  26. def test_constraint_sql(self):
  27. c = BaseConstraint(name="name")
  28. msg = "This method must be implemented by a subclass."
  29. with self.assertRaisesMessage(NotImplementedError, msg):
  30. c.constraint_sql(None, None)
  31. def test_contains_expressions(self):
  32. c = BaseConstraint(name="name")
  33. self.assertIs(c.contains_expressions, False)
  34. def test_create_sql(self):
  35. c = BaseConstraint(name="name")
  36. msg = "This method must be implemented by a subclass."
  37. with self.assertRaisesMessage(NotImplementedError, msg):
  38. c.create_sql(None, None)
  39. def test_remove_sql(self):
  40. c = BaseConstraint(name="name")
  41. msg = "This method must be implemented by a subclass."
  42. with self.assertRaisesMessage(NotImplementedError, msg):
  43. c.remove_sql(None, None)
  44. def test_validate(self):
  45. c = BaseConstraint(name="name")
  46. msg = "This method must be implemented by a subclass."
  47. with self.assertRaisesMessage(NotImplementedError, msg):
  48. c.validate(None, None)
  49. def test_default_violation_error_message(self):
  50. c = BaseConstraint(name="name")
  51. self.assertEqual(
  52. c.get_violation_error_message(), "Constraint “name” is violated."
  53. )
  54. def test_custom_violation_error_message(self):
  55. c = BaseConstraint(
  56. name="base_name", violation_error_message="custom %(name)s message"
  57. )
  58. self.assertEqual(c.get_violation_error_message(), "custom base_name message")
  59. def test_custom_violation_error_message_clone(self):
  60. constraint = BaseConstraint(
  61. name="base_name",
  62. violation_error_message="custom %(name)s message",
  63. ).clone()
  64. self.assertEqual(
  65. constraint.get_violation_error_message(),
  66. "custom base_name message",
  67. )
  68. def test_custom_violation_code_message(self):
  69. c = BaseConstraint(name="base_name", violation_error_code="custom_code")
  70. self.assertEqual(c.violation_error_code, "custom_code")
  71. def test_deconstruction(self):
  72. constraint = BaseConstraint(
  73. name="base_name",
  74. violation_error_message="custom %(name)s message",
  75. violation_error_code="custom_code",
  76. )
  77. path, args, kwargs = constraint.deconstruct()
  78. self.assertEqual(path, "django.db.models.BaseConstraint")
  79. self.assertEqual(args, ())
  80. self.assertEqual(
  81. kwargs,
  82. {
  83. "name": "base_name",
  84. "violation_error_message": "custom %(name)s message",
  85. "violation_error_code": "custom_code",
  86. },
  87. )
  88. def test_name_required(self):
  89. msg = (
  90. "BaseConstraint.__init__() missing 1 required keyword-only argument: 'name'"
  91. )
  92. with self.assertRaisesMessage(TypeError, msg):
  93. BaseConstraint()
  94. class CheckConstraintTests(TestCase):
  95. def test_eq(self):
  96. check1 = models.Q(price__gt=models.F("discounted_price"))
  97. check2 = models.Q(price__lt=models.F("discounted_price"))
  98. self.assertEqual(
  99. models.CheckConstraint(condition=check1, name="price"),
  100. models.CheckConstraint(condition=check1, name="price"),
  101. )
  102. self.assertEqual(
  103. models.CheckConstraint(condition=check1, name="price"), mock.ANY
  104. )
  105. self.assertNotEqual(
  106. models.CheckConstraint(condition=check1, name="price"),
  107. models.CheckConstraint(condition=check1, name="price2"),
  108. )
  109. self.assertNotEqual(
  110. models.CheckConstraint(condition=check1, name="price"),
  111. models.CheckConstraint(condition=check2, name="price"),
  112. )
  113. self.assertNotEqual(models.CheckConstraint(condition=check1, name="price"), 1)
  114. self.assertNotEqual(
  115. models.CheckConstraint(condition=check1, name="price"),
  116. models.CheckConstraint(
  117. condition=check1, name="price", violation_error_message="custom error"
  118. ),
  119. )
  120. self.assertNotEqual(
  121. models.CheckConstraint(
  122. condition=check1, name="price", violation_error_message="custom error"
  123. ),
  124. models.CheckConstraint(
  125. condition=check1,
  126. name="price",
  127. violation_error_message="other custom error",
  128. ),
  129. )
  130. self.assertEqual(
  131. models.CheckConstraint(
  132. condition=check1, name="price", violation_error_message="custom error"
  133. ),
  134. models.CheckConstraint(
  135. condition=check1, name="price", violation_error_message="custom error"
  136. ),
  137. )
  138. self.assertNotEqual(
  139. models.CheckConstraint(condition=check1, name="price"),
  140. models.CheckConstraint(
  141. condition=check1, name="price", violation_error_code="custom_code"
  142. ),
  143. )
  144. self.assertEqual(
  145. models.CheckConstraint(
  146. condition=check1, name="price", violation_error_code="custom_code"
  147. ),
  148. models.CheckConstraint(
  149. condition=check1, name="price", violation_error_code="custom_code"
  150. ),
  151. )
  152. def test_repr(self):
  153. constraint = models.CheckConstraint(
  154. condition=models.Q(price__gt=models.F("discounted_price")),
  155. name="price_gt_discounted_price",
  156. )
  157. self.assertEqual(
  158. repr(constraint),
  159. "<CheckConstraint: condition=(AND: ('price__gt', F(discounted_price))) "
  160. "name='price_gt_discounted_price'>",
  161. )
  162. def test_repr_with_violation_error_message(self):
  163. constraint = models.CheckConstraint(
  164. condition=models.Q(price__lt=1),
  165. name="price_lt_one",
  166. violation_error_message="More than 1",
  167. )
  168. self.assertEqual(
  169. repr(constraint),
  170. "<CheckConstraint: condition=(AND: ('price__lt', 1)) name='price_lt_one' "
  171. "violation_error_message='More than 1'>",
  172. )
  173. def test_repr_with_violation_error_code(self):
  174. constraint = models.CheckConstraint(
  175. condition=models.Q(price__lt=1),
  176. name="price_lt_one",
  177. violation_error_code="more_than_one",
  178. )
  179. self.assertEqual(
  180. repr(constraint),
  181. "<CheckConstraint: condition=(AND: ('price__lt', 1)) name='price_lt_one' "
  182. "violation_error_code='more_than_one'>",
  183. )
  184. def test_invalid_check_types(self):
  185. msg = "CheckConstraint.condition must be a Q instance or boolean expression."
  186. with self.assertRaisesMessage(TypeError, msg):
  187. models.CheckConstraint(condition=models.F("discounted_price"), name="check")
  188. def test_deconstruction(self):
  189. check = models.Q(price__gt=models.F("discounted_price"))
  190. name = "price_gt_discounted_price"
  191. constraint = models.CheckConstraint(condition=check, name=name)
  192. path, args, kwargs = constraint.deconstruct()
  193. self.assertEqual(path, "django.db.models.CheckConstraint")
  194. self.assertEqual(args, ())
  195. self.assertEqual(kwargs, {"condition": check, "name": name})
  196. @skipUnlessDBFeature("supports_table_check_constraints")
  197. def test_database_constraint(self):
  198. Product.objects.create(price=10, discounted_price=5)
  199. with self.assertRaises(IntegrityError):
  200. Product.objects.create(price=10, discounted_price=20)
  201. @skipUnlessDBFeature("supports_table_check_constraints")
  202. def test_database_constraint_unicode(self):
  203. Product.objects.create(price=10, discounted_price=5, unit="μg/mL")
  204. with self.assertRaises(IntegrityError):
  205. Product.objects.create(price=10, discounted_price=7, unit="l")
  206. @skipUnlessDBFeature(
  207. "supports_table_check_constraints", "can_introspect_check_constraints"
  208. )
  209. def test_name(self):
  210. constraints = get_constraints(Product._meta.db_table)
  211. for expected_name in (
  212. "price_gt_discounted_price",
  213. "constraints_product_price_gt_0",
  214. ):
  215. with self.subTest(expected_name):
  216. self.assertIn(expected_name, constraints)
  217. @skipUnlessDBFeature(
  218. "supports_table_check_constraints", "can_introspect_check_constraints"
  219. )
  220. def test_abstract_name(self):
  221. constraints = get_constraints(ChildModel._meta.db_table)
  222. self.assertIn("constraints_childmodel_adult", constraints)
  223. def test_validate(self):
  224. check = models.Q(price__gt=models.F("discounted_price"))
  225. constraint = models.CheckConstraint(condition=check, name="price")
  226. # Invalid product.
  227. invalid_product = Product(price=10, discounted_price=42)
  228. with self.assertRaises(ValidationError):
  229. constraint.validate(Product, invalid_product)
  230. with self.assertRaises(ValidationError):
  231. constraint.validate(Product, invalid_product, exclude={"unit"})
  232. # Fields used by the check constraint are excluded.
  233. constraint.validate(Product, invalid_product, exclude={"price"})
  234. constraint.validate(Product, invalid_product, exclude={"discounted_price"})
  235. constraint.validate(
  236. Product,
  237. invalid_product,
  238. exclude={"discounted_price", "price"},
  239. )
  240. # Valid product.
  241. constraint.validate(Product, Product(price=10, discounted_price=5))
  242. def test_validate_custom_error(self):
  243. check = models.Q(price__gt=models.F("discounted_price"))
  244. constraint = models.CheckConstraint(
  245. condition=check,
  246. name="price",
  247. violation_error_message="discount is fake",
  248. violation_error_code="fake_discount",
  249. )
  250. # Invalid product.
  251. invalid_product = Product(price=10, discounted_price=42)
  252. msg = "discount is fake"
  253. with self.assertRaisesMessage(ValidationError, msg) as cm:
  254. constraint.validate(Product, invalid_product)
  255. self.assertEqual(cm.exception.code, "fake_discount")
  256. def test_validate_boolean_expressions(self):
  257. constraint = models.CheckConstraint(
  258. condition=models.expressions.ExpressionWrapper(
  259. models.Q(price__gt=500) | models.Q(price__lt=500),
  260. output_field=models.BooleanField(),
  261. ),
  262. name="price_neq_500_wrap",
  263. )
  264. msg = f"Constraint “{constraint.name}” is violated."
  265. with self.assertRaisesMessage(ValidationError, msg):
  266. constraint.validate(Product, Product(price=500, discounted_price=5))
  267. constraint.validate(Product, Product(price=501, discounted_price=5))
  268. constraint.validate(Product, Product(price=499, discounted_price=5))
  269. def test_validate_rawsql_expressions_noop(self):
  270. constraint = models.CheckConstraint(
  271. condition=models.expressions.RawSQL(
  272. "price < %s OR price > %s",
  273. (500, 500),
  274. output_field=models.BooleanField(),
  275. ),
  276. name="price_neq_500_raw",
  277. )
  278. # RawSQL can not be checked and is always considered valid.
  279. constraint.validate(Product, Product(price=500, discounted_price=5))
  280. constraint.validate(Product, Product(price=501, discounted_price=5))
  281. constraint.validate(Product, Product(price=499, discounted_price=5))
  282. @skipUnlessDBFeature("supports_comparing_boolean_expr")
  283. def test_validate_nullable_field_with_none(self):
  284. # Nullable fields should be considered valid on None values.
  285. constraint = models.CheckConstraint(
  286. condition=models.Q(price__gte=0),
  287. name="positive_price",
  288. )
  289. constraint.validate(Product, Product())
  290. @skipIfDBFeature("supports_comparing_boolean_expr")
  291. def test_validate_nullable_field_with_isnull(self):
  292. constraint = models.CheckConstraint(
  293. condition=models.Q(price__gte=0) | models.Q(price__isnull=True),
  294. name="positive_price",
  295. )
  296. constraint.validate(Product, Product())
  297. @skipUnlessDBFeature("supports_json_field")
  298. def test_validate_nullable_jsonfield(self):
  299. is_null_constraint = models.CheckConstraint(
  300. condition=models.Q(data__isnull=True),
  301. name="nullable_data",
  302. )
  303. is_not_null_constraint = models.CheckConstraint(
  304. condition=models.Q(data__isnull=False),
  305. name="nullable_data",
  306. )
  307. is_null_constraint.validate(JSONFieldModel, JSONFieldModel(data=None))
  308. msg = f"Constraint “{is_null_constraint.name}” is violated."
  309. with self.assertRaisesMessage(ValidationError, msg):
  310. is_null_constraint.validate(JSONFieldModel, JSONFieldModel(data={}))
  311. msg = f"Constraint “{is_not_null_constraint.name}” is violated."
  312. with self.assertRaisesMessage(ValidationError, msg):
  313. is_not_null_constraint.validate(JSONFieldModel, JSONFieldModel(data=None))
  314. is_not_null_constraint.validate(JSONFieldModel, JSONFieldModel(data={}))
  315. def test_validate_pk_field(self):
  316. constraint_with_pk = models.CheckConstraint(
  317. condition=~models.Q(pk=models.F("age")),
  318. name="pk_not_age_check",
  319. )
  320. constraint_with_pk.validate(ChildModel, ChildModel(pk=1, age=2))
  321. msg = f"Constraint “{constraint_with_pk.name}” is violated."
  322. with self.assertRaisesMessage(ValidationError, msg):
  323. constraint_with_pk.validate(ChildModel, ChildModel(pk=1, age=1))
  324. with self.assertRaisesMessage(ValidationError, msg):
  325. constraint_with_pk.validate(ChildModel, ChildModel(id=1, age=1))
  326. constraint_with_pk.validate(ChildModel, ChildModel(pk=1, age=1), exclude={"pk"})
  327. @skipUnlessDBFeature("supports_json_field")
  328. def test_validate_jsonfield_exact(self):
  329. data = {"release": "5.0.2", "version": "stable"}
  330. json_exact_constraint = models.CheckConstraint(
  331. condition=models.Q(data__version="stable"),
  332. name="only_stable_version",
  333. )
  334. json_exact_constraint.validate(JSONFieldModel, JSONFieldModel(data=data))
  335. data = {"release": "5.0.2", "version": "not stable"}
  336. msg = f"Constraint “{json_exact_constraint.name}” is violated."
  337. with self.assertRaisesMessage(ValidationError, msg):
  338. json_exact_constraint.validate(JSONFieldModel, JSONFieldModel(data=data))
  339. @skipUnlessDBFeature("supports_stored_generated_columns")
  340. def test_validate_generated_field_stored(self):
  341. self.assertGeneratedFieldIsValidated(model=GeneratedFieldStoredProduct)
  342. @skipUnlessDBFeature("supports_virtual_generated_columns")
  343. def test_validate_generated_field_virtual(self):
  344. self.assertGeneratedFieldIsValidated(model=GeneratedFieldVirtualProduct)
  345. def assertGeneratedFieldIsValidated(self, model):
  346. constraint = models.CheckConstraint(
  347. condition=models.Q(rebate__range=(0, 100)), name="bounded_rebate"
  348. )
  349. constraint.validate(model, model(price=50, discounted_price=20))
  350. invalid_product = model(price=1200, discounted_price=500)
  351. msg = f"Constraint “{constraint.name}” is violated."
  352. with self.assertRaisesMessage(ValidationError, msg):
  353. constraint.validate(model, invalid_product)
  354. # Excluding referenced or generated fields should skip validation.
  355. constraint.validate(model, invalid_product, exclude={"price"})
  356. constraint.validate(model, invalid_product, exclude={"rebate"})
  357. def test_database_default(self):
  358. models.CheckConstraint(
  359. condition=models.Q(field_with_db_default="field_with_db_default"),
  360. name="check_field_with_db_default",
  361. ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())
  362. # Ensure that a check also does not silently pass with either
  363. # FieldError or DatabaseError when checking with a db_default.
  364. with self.assertRaises(ValidationError):
  365. models.CheckConstraint(
  366. condition=models.Q(
  367. field_with_db_default="field_with_db_default", field="field"
  368. ),
  369. name="check_field_with_db_default_2",
  370. ).validate(
  371. ModelWithDatabaseDefault, ModelWithDatabaseDefault(field="not-field")
  372. )
  373. with self.assertRaises(ValidationError):
  374. models.CheckConstraint(
  375. condition=models.Q(field_with_db_default="field_with_db_default"),
  376. name="check_field_with_db_default",
  377. ).validate(
  378. ModelWithDatabaseDefault,
  379. ModelWithDatabaseDefault(field_with_db_default="other value"),
  380. )
  381. class UniqueConstraintTests(TestCase):
  382. @classmethod
  383. def setUpTestData(cls):
  384. cls.p1 = UniqueConstraintProduct.objects.create(name="p1", color="red")
  385. cls.p2 = UniqueConstraintProduct.objects.create(name="p2")
  386. def test_eq(self):
  387. self.assertEqual(
  388. models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
  389. models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
  390. )
  391. self.assertEqual(
  392. models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
  393. mock.ANY,
  394. )
  395. self.assertNotEqual(
  396. models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
  397. models.UniqueConstraint(fields=["foo", "bar"], name="unique2"),
  398. )
  399. self.assertNotEqual(
  400. models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
  401. models.UniqueConstraint(fields=["foo", "baz"], name="unique"),
  402. )
  403. self.assertNotEqual(
  404. models.UniqueConstraint(fields=["foo", "bar"], name="unique"), 1
  405. )
  406. self.assertNotEqual(
  407. models.UniqueConstraint(fields=["foo", "bar"], name="unique"),
  408. models.UniqueConstraint(
  409. fields=["foo", "bar"],
  410. name="unique",
  411. violation_error_message="custom error",
  412. ),
  413. )
  414. self.assertNotEqual(
  415. models.UniqueConstraint(
  416. fields=["foo", "bar"],
  417. name="unique",
  418. violation_error_message="custom error",
  419. ),
  420. models.UniqueConstraint(
  421. fields=["foo", "bar"],
  422. name="unique",
  423. violation_error_message="other custom error",
  424. ),
  425. )
  426. self.assertEqual(
  427. models.UniqueConstraint(
  428. fields=["foo", "bar"],
  429. name="unique",
  430. violation_error_message="custom error",
  431. ),
  432. models.UniqueConstraint(
  433. fields=["foo", "bar"],
  434. name="unique",
  435. violation_error_message="custom error",
  436. ),
  437. )
  438. self.assertNotEqual(
  439. models.UniqueConstraint(
  440. fields=["foo", "bar"],
  441. name="unique",
  442. violation_error_code="custom_error",
  443. ),
  444. models.UniqueConstraint(
  445. fields=["foo", "bar"],
  446. name="unique",
  447. violation_error_code="other_custom_error",
  448. ),
  449. )
  450. self.assertEqual(
  451. models.UniqueConstraint(
  452. fields=["foo", "bar"],
  453. name="unique",
  454. violation_error_code="custom_error",
  455. ),
  456. models.UniqueConstraint(
  457. fields=["foo", "bar"],
  458. name="unique",
  459. violation_error_code="custom_error",
  460. ),
  461. )
  462. def test_eq_with_condition(self):
  463. self.assertEqual(
  464. models.UniqueConstraint(
  465. fields=["foo", "bar"],
  466. name="unique",
  467. condition=models.Q(foo=models.F("bar")),
  468. ),
  469. models.UniqueConstraint(
  470. fields=["foo", "bar"],
  471. name="unique",
  472. condition=models.Q(foo=models.F("bar")),
  473. ),
  474. )
  475. self.assertNotEqual(
  476. models.UniqueConstraint(
  477. fields=["foo", "bar"],
  478. name="unique",
  479. condition=models.Q(foo=models.F("bar")),
  480. ),
  481. models.UniqueConstraint(
  482. fields=["foo", "bar"],
  483. name="unique",
  484. condition=models.Q(foo=models.F("baz")),
  485. ),
  486. )
  487. def test_eq_with_deferrable(self):
  488. constraint_1 = models.UniqueConstraint(
  489. fields=["foo", "bar"],
  490. name="unique",
  491. deferrable=models.Deferrable.DEFERRED,
  492. )
  493. constraint_2 = models.UniqueConstraint(
  494. fields=["foo", "bar"],
  495. name="unique",
  496. deferrable=models.Deferrable.IMMEDIATE,
  497. )
  498. self.assertEqual(constraint_1, constraint_1)
  499. self.assertNotEqual(constraint_1, constraint_2)
  500. def test_eq_with_include(self):
  501. constraint_1 = models.UniqueConstraint(
  502. fields=["foo", "bar"],
  503. name="include",
  504. include=["baz_1"],
  505. )
  506. constraint_2 = models.UniqueConstraint(
  507. fields=["foo", "bar"],
  508. name="include",
  509. include=["baz_2"],
  510. )
  511. self.assertEqual(constraint_1, constraint_1)
  512. self.assertNotEqual(constraint_1, constraint_2)
  513. def test_eq_with_opclasses(self):
  514. constraint_1 = models.UniqueConstraint(
  515. fields=["foo", "bar"],
  516. name="opclasses",
  517. opclasses=["text_pattern_ops", "varchar_pattern_ops"],
  518. )
  519. constraint_2 = models.UniqueConstraint(
  520. fields=["foo", "bar"],
  521. name="opclasses",
  522. opclasses=["varchar_pattern_ops", "text_pattern_ops"],
  523. )
  524. self.assertEqual(constraint_1, constraint_1)
  525. self.assertNotEqual(constraint_1, constraint_2)
  526. def test_eq_with_expressions(self):
  527. constraint = models.UniqueConstraint(
  528. Lower("title"),
  529. F("author"),
  530. name="book_func_uq",
  531. )
  532. same_constraint = models.UniqueConstraint(
  533. Lower("title"),
  534. "author",
  535. name="book_func_uq",
  536. )
  537. another_constraint = models.UniqueConstraint(
  538. Lower("title"),
  539. name="book_func_uq",
  540. )
  541. self.assertEqual(constraint, same_constraint)
  542. self.assertEqual(constraint, mock.ANY)
  543. self.assertNotEqual(constraint, another_constraint)
  544. def test_eq_with_nulls_distinct(self):
  545. constraint_1 = models.UniqueConstraint(
  546. Lower("title"),
  547. nulls_distinct=False,
  548. name="book_func_nulls_distinct_uq",
  549. )
  550. constraint_2 = models.UniqueConstraint(
  551. Lower("title"),
  552. nulls_distinct=True,
  553. name="book_func_nulls_distinct_uq",
  554. )
  555. constraint_3 = models.UniqueConstraint(
  556. Lower("title"),
  557. name="book_func_nulls_distinct_uq",
  558. )
  559. self.assertEqual(constraint_1, constraint_1)
  560. self.assertEqual(constraint_1, mock.ANY)
  561. self.assertNotEqual(constraint_1, constraint_2)
  562. self.assertNotEqual(constraint_1, constraint_3)
  563. self.assertNotEqual(constraint_2, constraint_3)
  564. def test_repr(self):
  565. fields = ["foo", "bar"]
  566. name = "unique_fields"
  567. constraint = models.UniqueConstraint(fields=fields, name=name)
  568. self.assertEqual(
  569. repr(constraint),
  570. "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields'>",
  571. )
  572. def test_repr_with_condition(self):
  573. constraint = models.UniqueConstraint(
  574. fields=["foo", "bar"],
  575. name="unique_fields",
  576. condition=models.Q(foo=models.F("bar")),
  577. )
  578. self.assertEqual(
  579. repr(constraint),
  580. "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields' "
  581. "condition=(AND: ('foo', F(bar)))>",
  582. )
  583. def test_repr_with_deferrable(self):
  584. constraint = models.UniqueConstraint(
  585. fields=["foo", "bar"],
  586. name="unique_fields",
  587. deferrable=models.Deferrable.IMMEDIATE,
  588. )
  589. self.assertEqual(
  590. repr(constraint),
  591. "<UniqueConstraint: fields=('foo', 'bar') name='unique_fields' "
  592. "deferrable=Deferrable.IMMEDIATE>",
  593. )
  594. def test_repr_with_include(self):
  595. constraint = models.UniqueConstraint(
  596. fields=["foo", "bar"],
  597. name="include_fields",
  598. include=["baz_1", "baz_2"],
  599. )
  600. self.assertEqual(
  601. repr(constraint),
  602. "<UniqueConstraint: fields=('foo', 'bar') name='include_fields' "
  603. "include=('baz_1', 'baz_2')>",
  604. )
  605. def test_repr_with_opclasses(self):
  606. constraint = models.UniqueConstraint(
  607. fields=["foo", "bar"],
  608. name="opclasses_fields",
  609. opclasses=["text_pattern_ops", "varchar_pattern_ops"],
  610. )
  611. self.assertEqual(
  612. repr(constraint),
  613. "<UniqueConstraint: fields=('foo', 'bar') name='opclasses_fields' "
  614. "opclasses=['text_pattern_ops', 'varchar_pattern_ops']>",
  615. )
  616. def test_repr_with_nulls_distinct(self):
  617. constraint = models.UniqueConstraint(
  618. fields=["foo", "bar"],
  619. name="nulls_distinct_fields",
  620. nulls_distinct=False,
  621. )
  622. self.assertEqual(
  623. repr(constraint),
  624. "<UniqueConstraint: fields=('foo', 'bar') name='nulls_distinct_fields' "
  625. "nulls_distinct=False>",
  626. )
  627. def test_repr_with_expressions(self):
  628. constraint = models.UniqueConstraint(
  629. Lower("title"),
  630. F("author"),
  631. name="book_func_uq",
  632. )
  633. self.assertEqual(
  634. repr(constraint),
  635. "<UniqueConstraint: expressions=(Lower(F(title)), F(author)) "
  636. "name='book_func_uq'>",
  637. )
  638. def test_repr_with_violation_error_message(self):
  639. constraint = models.UniqueConstraint(
  640. models.F("baz__lower"),
  641. name="unique_lower_baz",
  642. violation_error_message="BAZ",
  643. )
  644. self.assertEqual(
  645. repr(constraint),
  646. (
  647. "<UniqueConstraint: expressions=(F(baz__lower),) "
  648. "name='unique_lower_baz' violation_error_message='BAZ'>"
  649. ),
  650. )
  651. def test_repr_with_violation_error_code(self):
  652. constraint = models.UniqueConstraint(
  653. models.F("baz__lower"),
  654. name="unique_lower_baz",
  655. violation_error_code="baz",
  656. )
  657. self.assertEqual(
  658. repr(constraint),
  659. (
  660. "<UniqueConstraint: expressions=(F(baz__lower),) "
  661. "name='unique_lower_baz' violation_error_code='baz'>"
  662. ),
  663. )
  664. def test_deconstruction(self):
  665. fields = ["foo", "bar"]
  666. name = "unique_fields"
  667. constraint = models.UniqueConstraint(fields=fields, name=name)
  668. path, args, kwargs = constraint.deconstruct()
  669. self.assertEqual(path, "django.db.models.UniqueConstraint")
  670. self.assertEqual(args, ())
  671. self.assertEqual(kwargs, {"fields": tuple(fields), "name": name})
  672. def test_deconstruction_with_condition(self):
  673. fields = ["foo", "bar"]
  674. name = "unique_fields"
  675. condition = models.Q(foo=models.F("bar"))
  676. constraint = models.UniqueConstraint(
  677. fields=fields, name=name, condition=condition
  678. )
  679. path, args, kwargs = constraint.deconstruct()
  680. self.assertEqual(path, "django.db.models.UniqueConstraint")
  681. self.assertEqual(args, ())
  682. self.assertEqual(
  683. kwargs, {"fields": tuple(fields), "name": name, "condition": condition}
  684. )
  685. def test_deconstruction_with_deferrable(self):
  686. fields = ["foo"]
  687. name = "unique_fields"
  688. constraint = models.UniqueConstraint(
  689. fields=fields,
  690. name=name,
  691. deferrable=models.Deferrable.DEFERRED,
  692. )
  693. path, args, kwargs = constraint.deconstruct()
  694. self.assertEqual(path, "django.db.models.UniqueConstraint")
  695. self.assertEqual(args, ())
  696. self.assertEqual(
  697. kwargs,
  698. {
  699. "fields": tuple(fields),
  700. "name": name,
  701. "deferrable": models.Deferrable.DEFERRED,
  702. },
  703. )
  704. def test_deconstruction_with_include(self):
  705. fields = ["foo", "bar"]
  706. name = "unique_fields"
  707. include = ["baz_1", "baz_2"]
  708. constraint = models.UniqueConstraint(fields=fields, name=name, include=include)
  709. path, args, kwargs = constraint.deconstruct()
  710. self.assertEqual(path, "django.db.models.UniqueConstraint")
  711. self.assertEqual(args, ())
  712. self.assertEqual(
  713. kwargs,
  714. {
  715. "fields": tuple(fields),
  716. "name": name,
  717. "include": tuple(include),
  718. },
  719. )
  720. def test_deconstruction_with_opclasses(self):
  721. fields = ["foo", "bar"]
  722. name = "unique_fields"
  723. opclasses = ["varchar_pattern_ops", "text_pattern_ops"]
  724. constraint = models.UniqueConstraint(
  725. fields=fields, name=name, opclasses=opclasses
  726. )
  727. path, args, kwargs = constraint.deconstruct()
  728. self.assertEqual(path, "django.db.models.UniqueConstraint")
  729. self.assertEqual(args, ())
  730. self.assertEqual(
  731. kwargs,
  732. {
  733. "fields": tuple(fields),
  734. "name": name,
  735. "opclasses": opclasses,
  736. },
  737. )
  738. def test_deconstruction_with_nulls_distinct(self):
  739. fields = ["foo", "bar"]
  740. name = "unique_fields"
  741. constraint = models.UniqueConstraint(
  742. fields=fields, name=name, nulls_distinct=True
  743. )
  744. path, args, kwargs = constraint.deconstruct()
  745. self.assertEqual(path, "django.db.models.UniqueConstraint")
  746. self.assertEqual(args, ())
  747. self.assertEqual(
  748. kwargs,
  749. {
  750. "fields": tuple(fields),
  751. "name": name,
  752. "nulls_distinct": True,
  753. },
  754. )
  755. def test_deconstruction_with_expressions(self):
  756. name = "unique_fields"
  757. constraint = models.UniqueConstraint(Lower("title"), name=name)
  758. path, args, kwargs = constraint.deconstruct()
  759. self.assertEqual(path, "django.db.models.UniqueConstraint")
  760. self.assertEqual(args, (Lower("title"),))
  761. self.assertEqual(kwargs, {"name": name})
  762. def test_database_constraint(self):
  763. with self.assertRaises(IntegrityError):
  764. UniqueConstraintProduct.objects.create(
  765. name=self.p1.name, color=self.p1.color
  766. )
  767. @skipUnlessDBFeature("supports_partial_indexes")
  768. def test_database_constraint_with_condition(self):
  769. UniqueConstraintConditionProduct.objects.create(name="p1")
  770. UniqueConstraintConditionProduct.objects.create(name="p2")
  771. with self.assertRaises(IntegrityError):
  772. UniqueConstraintConditionProduct.objects.create(name="p1")
  773. def test_model_validation(self):
  774. msg = "Unique constraint product with this Name and Color already exists."
  775. with self.assertRaisesMessage(ValidationError, msg):
  776. UniqueConstraintProduct(
  777. name=self.p1.name, color=self.p1.color
  778. ).validate_constraints()
  779. @skipUnlessDBFeature("supports_partial_indexes")
  780. def test_model_validation_with_condition(self):
  781. """
  782. Partial unique constraints are not ignored by
  783. Model.validate_constraints().
  784. """
  785. obj1 = UniqueConstraintConditionProduct.objects.create(name="p1", color="red")
  786. obj2 = UniqueConstraintConditionProduct.objects.create(name="p2")
  787. UniqueConstraintConditionProduct(
  788. name=obj1.name, color="blue"
  789. ).validate_constraints()
  790. msg = "Constraint “name_without_color_uniq” is violated."
  791. with self.assertRaisesMessage(ValidationError, msg):
  792. UniqueConstraintConditionProduct(name=obj2.name).validate_constraints()
  793. def test_model_validation_constraint_no_code_error(self):
  794. class ValidateNoCodeErrorConstraint(UniqueConstraint):
  795. def validate(self, model, instance, **kwargs):
  796. raise ValidationError({"name": ValidationError("Already exists.")})
  797. class NoCodeErrorConstraintModel(models.Model):
  798. name = models.CharField(max_length=255)
  799. class Meta:
  800. constraints = [
  801. ValidateNoCodeErrorConstraint(
  802. Lower("name"),
  803. name="custom_validate_no_code_error",
  804. )
  805. ]
  806. msg = "{'name': ['Already exists.']}"
  807. with self.assertRaisesMessage(ValidationError, msg):
  808. NoCodeErrorConstraintModel(name="test").validate_constraints()
  809. def test_validate(self):
  810. constraint = UniqueConstraintProduct._meta.constraints[0]
  811. msg = "Unique constraint product with this Name and Color already exists."
  812. non_unique_product = UniqueConstraintProduct(
  813. name=self.p1.name, color=self.p1.color
  814. )
  815. with self.assertRaisesMessage(ValidationError, msg) as cm:
  816. constraint.validate(UniqueConstraintProduct, non_unique_product)
  817. self.assertEqual(cm.exception.code, "unique_together")
  818. # Null values are ignored.
  819. constraint.validate(
  820. UniqueConstraintProduct,
  821. UniqueConstraintProduct(name=self.p2.name, color=None),
  822. )
  823. # Existing instances have their existing row excluded.
  824. constraint.validate(UniqueConstraintProduct, self.p1)
  825. # Unique fields are excluded.
  826. constraint.validate(
  827. UniqueConstraintProduct,
  828. non_unique_product,
  829. exclude={"name"},
  830. )
  831. constraint.validate(
  832. UniqueConstraintProduct,
  833. non_unique_product,
  834. exclude={"color"},
  835. )
  836. constraint.validate(
  837. UniqueConstraintProduct,
  838. non_unique_product,
  839. exclude={"name", "color"},
  840. )
  841. # Validation on a child instance.
  842. with self.assertRaisesMessage(ValidationError, msg):
  843. constraint.validate(
  844. UniqueConstraintProduct,
  845. ChildUniqueConstraintProduct(name=self.p1.name, color=self.p1.color),
  846. )
  847. def test_validate_unique_custom_code_and_message(self):
  848. product = UniqueConstraintProduct.objects.create(
  849. name="test", color="red", age=42
  850. )
  851. code = "custom_code"
  852. message = "Custom message"
  853. multiple_fields_constraint = models.UniqueConstraint(
  854. fields=["color", "age"],
  855. name="color_age_uniq",
  856. violation_error_code=code,
  857. violation_error_message=message,
  858. )
  859. single_field_constraint = models.UniqueConstraint(
  860. fields=["color"],
  861. name="color_uniq",
  862. violation_error_code=code,
  863. violation_error_message=message,
  864. )
  865. with self.assertRaisesMessage(ValidationError, message) as cm:
  866. multiple_fields_constraint.validate(
  867. UniqueConstraintProduct,
  868. UniqueConstraintProduct(
  869. name="new-test", color=product.color, age=product.age
  870. ),
  871. )
  872. self.assertEqual(cm.exception.code, code)
  873. with self.assertRaisesMessage(ValidationError, message) as cm:
  874. single_field_constraint.validate(
  875. UniqueConstraintProduct,
  876. UniqueConstraintProduct(name="new-test", color=product.color),
  877. )
  878. self.assertEqual(cm.exception.code, code)
  879. @skipUnlessDBFeature("supports_table_check_constraints")
  880. def test_validate_fields_unattached(self):
  881. Product.objects.create(price=42)
  882. constraint = models.UniqueConstraint(fields=["price"], name="uniq_prices")
  883. msg = "Product with this Price already exists."
  884. with self.assertRaisesMessage(ValidationError, msg):
  885. constraint.validate(Product, Product(price=42))
  886. @skipUnlessDBFeature("supports_partial_indexes")
  887. def test_validate_condition(self):
  888. p1 = UniqueConstraintConditionProduct.objects.create(name="p1")
  889. constraint = UniqueConstraintConditionProduct._meta.constraints[0]
  890. msg = "Constraint “name_without_color_uniq” is violated."
  891. with self.assertRaisesMessage(ValidationError, msg):
  892. constraint.validate(
  893. UniqueConstraintConditionProduct,
  894. UniqueConstraintConditionProduct(name=p1.name, color=None),
  895. )
  896. # Values not matching condition are ignored.
  897. constraint.validate(
  898. UniqueConstraintConditionProduct,
  899. UniqueConstraintConditionProduct(name=p1.name, color="anything-but-none"),
  900. )
  901. # Existing instances have their existing row excluded.
  902. constraint.validate(UniqueConstraintConditionProduct, p1)
  903. # Unique field is excluded.
  904. constraint.validate(
  905. UniqueConstraintConditionProduct,
  906. UniqueConstraintConditionProduct(name=p1.name, color=None),
  907. exclude={"name"},
  908. )
  909. @skipUnlessDBFeature("supports_partial_indexes")
  910. def test_validate_condition_custom_error(self):
  911. p1 = UniqueConstraintConditionProduct.objects.create(name="p1")
  912. constraint = models.UniqueConstraint(
  913. fields=["name"],
  914. name="name_without_color_uniq",
  915. condition=models.Q(color__isnull=True),
  916. violation_error_code="custom_code",
  917. violation_error_message="Custom message",
  918. )
  919. msg = "Custom message"
  920. with self.assertRaisesMessage(ValidationError, msg) as cm:
  921. constraint.validate(
  922. UniqueConstraintConditionProduct,
  923. UniqueConstraintConditionProduct(name=p1.name, color=None),
  924. )
  925. self.assertEqual(cm.exception.code, "custom_code")
  926. def test_validate_expression(self):
  927. constraint = models.UniqueConstraint(Lower("name"), name="name_lower_uniq")
  928. msg = "Constraint “name_lower_uniq” is violated."
  929. with self.assertRaisesMessage(ValidationError, msg):
  930. constraint.validate(
  931. UniqueConstraintProduct,
  932. UniqueConstraintProduct(name=self.p1.name.upper()),
  933. )
  934. constraint.validate(
  935. UniqueConstraintProduct,
  936. UniqueConstraintProduct(name="another-name"),
  937. )
  938. # Existing instances have their existing row excluded.
  939. constraint.validate(UniqueConstraintProduct, self.p1)
  940. # Unique field is excluded.
  941. constraint.validate(
  942. UniqueConstraintProduct,
  943. UniqueConstraintProduct(name=self.p1.name.upper()),
  944. exclude={"name"},
  945. )
  946. def test_validate_ordered_expression(self):
  947. constraint = models.UniqueConstraint(
  948. Lower("name").desc(), name="name_lower_uniq_desc"
  949. )
  950. msg = "Constraint “name_lower_uniq_desc” is violated."
  951. with self.assertRaisesMessage(ValidationError, msg):
  952. constraint.validate(
  953. UniqueConstraintProduct,
  954. UniqueConstraintProduct(name=self.p1.name.upper()),
  955. )
  956. constraint.validate(
  957. UniqueConstraintProduct,
  958. UniqueConstraintProduct(name="another-name"),
  959. )
  960. # Existing instances have their existing row excluded.
  961. constraint.validate(UniqueConstraintProduct, self.p1)
  962. # Unique field is excluded.
  963. constraint.validate(
  964. UniqueConstraintProduct,
  965. UniqueConstraintProduct(name=self.p1.name.upper()),
  966. exclude={"name"},
  967. )
  968. def test_validate_expression_condition(self):
  969. constraint = models.UniqueConstraint(
  970. Lower("name"),
  971. name="name_lower_without_color_uniq",
  972. condition=models.Q(color__isnull=True),
  973. )
  974. non_unique_product = UniqueConstraintProduct(name=self.p2.name.upper())
  975. msg = "Constraint “name_lower_without_color_uniq” is violated."
  976. with self.assertRaisesMessage(ValidationError, msg):
  977. constraint.validate(UniqueConstraintProduct, non_unique_product)
  978. # Values not matching condition are ignored.
  979. constraint.validate(
  980. UniqueConstraintProduct,
  981. UniqueConstraintProduct(name=self.p1.name, color=self.p1.color),
  982. )
  983. # Existing instances have their existing row excluded.
  984. constraint.validate(UniqueConstraintProduct, self.p2)
  985. # Unique field is excluded.
  986. constraint.validate(
  987. UniqueConstraintProduct,
  988. non_unique_product,
  989. exclude={"name"},
  990. )
  991. # Field from a condition is excluded.
  992. constraint.validate(
  993. UniqueConstraintProduct,
  994. non_unique_product,
  995. exclude={"color"},
  996. )
  997. def test_validate_expression_str(self):
  998. constraint = models.UniqueConstraint("name", name="name_uniq")
  999. msg = "Constraint “name_uniq” is violated."
  1000. with self.assertRaisesMessage(ValidationError, msg):
  1001. constraint.validate(
  1002. UniqueConstraintProduct,
  1003. UniqueConstraintProduct(name=self.p1.name),
  1004. )
  1005. constraint.validate(
  1006. UniqueConstraintProduct,
  1007. UniqueConstraintProduct(name=self.p1.name),
  1008. exclude={"name"},
  1009. )
  1010. @skipUnlessDBFeature("supports_stored_generated_columns")
  1011. def test_validate_expression_generated_field_stored(self):
  1012. self.assertGeneratedFieldWithExpressionIsValidated(
  1013. model=GeneratedFieldStoredProduct
  1014. )
  1015. @skipUnlessDBFeature("supports_virtual_generated_columns")
  1016. def test_validate_expression_generated_field_virtual(self):
  1017. self.assertGeneratedFieldWithExpressionIsValidated(
  1018. model=GeneratedFieldVirtualProduct
  1019. )
  1020. def assertGeneratedFieldWithExpressionIsValidated(self, model):
  1021. constraint = UniqueConstraint(Sqrt("rebate"), name="unique_rebate_sqrt")
  1022. model.objects.create(price=100, discounted_price=84)
  1023. valid_product = model(price=100, discounted_price=75)
  1024. constraint.validate(model, valid_product)
  1025. invalid_product = model(price=20, discounted_price=4)
  1026. with self.assertRaisesMessage(
  1027. ValidationError, f"Constraint “{constraint.name}” is violated."
  1028. ):
  1029. constraint.validate(model, invalid_product)
  1030. # Excluding referenced or generated fields should skip validation.
  1031. constraint.validate(model, invalid_product, exclude={"rebate"})
  1032. constraint.validate(model, invalid_product, exclude={"price"})
  1033. @skipUnlessDBFeature("supports_stored_generated_columns")
  1034. def test_validate_fields_generated_field_stored(self):
  1035. self.assertGeneratedFieldWithFieldsIsValidated(
  1036. model=GeneratedFieldStoredProduct
  1037. )
  1038. @skipUnlessDBFeature("supports_virtual_generated_columns")
  1039. def test_validate_fields_generated_field_virtual(self):
  1040. self.assertGeneratedFieldWithFieldsIsValidated(
  1041. model=GeneratedFieldVirtualProduct
  1042. )
  1043. def assertGeneratedFieldWithFieldsIsValidated(self, model):
  1044. constraint = models.UniqueConstraint(
  1045. fields=["lower_name"], name="lower_name_unique"
  1046. )
  1047. model.objects.create(name="Box")
  1048. constraint.validate(model, model(name="Case"))
  1049. invalid_product = model(name="BOX")
  1050. msg = str(invalid_product.unique_error_message(model, ["lower_name"]))
  1051. with self.assertRaisesMessage(ValidationError, msg):
  1052. constraint.validate(model, invalid_product)
  1053. # Excluding referenced or generated fields should skip validation.
  1054. constraint.validate(model, invalid_product, exclude={"lower_name"})
  1055. constraint.validate(model, invalid_product, exclude={"name"})
  1056. @skipUnlessDBFeature("supports_stored_generated_columns")
  1057. def test_validate_fields_generated_field_stored_nulls_distinct(self):
  1058. self.assertGeneratedFieldNullsDistinctIsValidated(
  1059. model=GeneratedFieldStoredProduct
  1060. )
  1061. @skipUnlessDBFeature("supports_virtual_generated_columns")
  1062. def test_validate_fields_generated_field_virtual_nulls_distinct(self):
  1063. self.assertGeneratedFieldNullsDistinctIsValidated(
  1064. model=GeneratedFieldVirtualProduct
  1065. )
  1066. def assertGeneratedFieldNullsDistinctIsValidated(self, model):
  1067. constraint = models.UniqueConstraint(
  1068. fields=["lower_name"],
  1069. name="lower_name_unique_nulls_distinct",
  1070. nulls_distinct=False,
  1071. )
  1072. model.objects.create(name=None)
  1073. valid_product = model(name="Box")
  1074. constraint.validate(model, valid_product)
  1075. invalid_product = model(name=None)
  1076. msg = str(invalid_product.unique_error_message(model, ["lower_name"]))
  1077. with self.assertRaisesMessage(ValidationError, msg):
  1078. constraint.validate(model, invalid_product)
  1079. @skipUnlessDBFeature("supports_table_check_constraints")
  1080. def test_validate_nullable_textfield_with_isnull_true(self):
  1081. is_null_constraint = models.UniqueConstraint(
  1082. "price",
  1083. "discounted_price",
  1084. condition=models.Q(unit__isnull=True),
  1085. name="uniq_prices_no_unit",
  1086. )
  1087. is_not_null_constraint = models.UniqueConstraint(
  1088. "price",
  1089. "discounted_price",
  1090. condition=models.Q(unit__isnull=False),
  1091. name="uniq_prices_unit",
  1092. )
  1093. Product.objects.create(price=2, discounted_price=1)
  1094. Product.objects.create(price=4, discounted_price=3, unit="ng/mL")
  1095. msg = "Constraint “uniq_prices_no_unit” is violated."
  1096. with self.assertRaisesMessage(ValidationError, msg):
  1097. is_null_constraint.validate(
  1098. Product, Product(price=2, discounted_price=1, unit=None)
  1099. )
  1100. is_null_constraint.validate(
  1101. Product, Product(price=2, discounted_price=1, unit="ng/mL")
  1102. )
  1103. is_null_constraint.validate(Product, Product(price=4, discounted_price=3))
  1104. msg = "Constraint “uniq_prices_unit” is violated."
  1105. with self.assertRaisesMessage(ValidationError, msg):
  1106. is_not_null_constraint.validate(
  1107. Product,
  1108. Product(price=4, discounted_price=3, unit="μg/mL"),
  1109. )
  1110. is_not_null_constraint.validate(Product, Product(price=4, discounted_price=3))
  1111. is_not_null_constraint.validate(Product, Product(price=2, discounted_price=1))
  1112. @skipUnlessDBFeature("supports_table_check_constraints")
  1113. def test_validate_nulls_distinct_fields(self):
  1114. Product.objects.create(price=42)
  1115. constraint = models.UniqueConstraint(
  1116. fields=["price"],
  1117. nulls_distinct=False,
  1118. name="uniq_prices_nulls_distinct",
  1119. )
  1120. constraint.validate(Product, Product(price=None))
  1121. Product.objects.create(price=None)
  1122. msg = "Product with this Price already exists."
  1123. with self.assertRaisesMessage(ValidationError, msg):
  1124. constraint.validate(Product, Product(price=None))
  1125. @skipUnlessDBFeature("supports_table_check_constraints")
  1126. def test_validate_nulls_distinct_expressions(self):
  1127. Product.objects.create(price=42)
  1128. constraint = models.UniqueConstraint(
  1129. Abs("price"),
  1130. nulls_distinct=False,
  1131. name="uniq_prices_nulls_distinct",
  1132. )
  1133. constraint.validate(Product, Product(price=None))
  1134. Product.objects.create(price=None)
  1135. msg = f"Constraint “{constraint.name}” is violated."
  1136. with self.assertRaisesMessage(ValidationError, msg):
  1137. constraint.validate(Product, Product(price=None))
  1138. def test_name(self):
  1139. constraints = get_constraints(UniqueConstraintProduct._meta.db_table)
  1140. expected_name = "name_color_uniq"
  1141. self.assertIn(expected_name, constraints)
  1142. def test_condition_must_be_q(self):
  1143. with self.assertRaisesMessage(
  1144. ValueError, "UniqueConstraint.condition must be a Q instance."
  1145. ):
  1146. models.UniqueConstraint(name="uniq", fields=["name"], condition="invalid")
  1147. @skipUnlessDBFeature("supports_deferrable_unique_constraints")
  1148. def test_initially_deferred_database_constraint(self):
  1149. obj_1 = UniqueConstraintDeferrable.objects.create(name="p1", shelf="front")
  1150. obj_2 = UniqueConstraintDeferrable.objects.create(name="p2", shelf="back")
  1151. def swap():
  1152. obj_1.name, obj_2.name = obj_2.name, obj_1.name
  1153. obj_1.save()
  1154. obj_2.save()
  1155. swap()
  1156. # Behavior can be changed with SET CONSTRAINTS.
  1157. with self.assertRaises(IntegrityError):
  1158. with atomic(), connection.cursor() as cursor:
  1159. constraint_name = connection.ops.quote_name("name_init_deferred_uniq")
  1160. cursor.execute("SET CONSTRAINTS %s IMMEDIATE" % constraint_name)
  1161. swap()
  1162. @skipUnlessDBFeature("supports_deferrable_unique_constraints")
  1163. def test_initially_immediate_database_constraint(self):
  1164. obj_1 = UniqueConstraintDeferrable.objects.create(name="p1", shelf="front")
  1165. obj_2 = UniqueConstraintDeferrable.objects.create(name="p2", shelf="back")
  1166. obj_1.shelf, obj_2.shelf = obj_2.shelf, obj_1.shelf
  1167. with self.assertRaises(IntegrityError), atomic():
  1168. obj_1.save()
  1169. # Behavior can be changed with SET CONSTRAINTS.
  1170. with connection.cursor() as cursor:
  1171. constraint_name = connection.ops.quote_name("sheld_init_immediate_uniq")
  1172. cursor.execute("SET CONSTRAINTS %s DEFERRED" % constraint_name)
  1173. obj_1.save()
  1174. obj_2.save()
  1175. def test_deferrable_with_condition(self):
  1176. message = "UniqueConstraint with conditions cannot be deferred."
  1177. with self.assertRaisesMessage(ValueError, message):
  1178. models.UniqueConstraint(
  1179. fields=["name"],
  1180. name="name_without_color_unique",
  1181. condition=models.Q(color__isnull=True),
  1182. deferrable=models.Deferrable.DEFERRED,
  1183. )
  1184. def test_deferrable_with_include(self):
  1185. message = "UniqueConstraint with include fields cannot be deferred."
  1186. with self.assertRaisesMessage(ValueError, message):
  1187. models.UniqueConstraint(
  1188. fields=["name"],
  1189. name="name_inc_color_color_unique",
  1190. include=["color"],
  1191. deferrable=models.Deferrable.DEFERRED,
  1192. )
  1193. def test_deferrable_with_opclasses(self):
  1194. message = "UniqueConstraint with opclasses cannot be deferred."
  1195. with self.assertRaisesMessage(ValueError, message):
  1196. models.UniqueConstraint(
  1197. fields=["name"],
  1198. name="name_text_pattern_ops_unique",
  1199. opclasses=["text_pattern_ops"],
  1200. deferrable=models.Deferrable.DEFERRED,
  1201. )
  1202. def test_deferrable_with_expressions(self):
  1203. message = "UniqueConstraint with expressions cannot be deferred."
  1204. with self.assertRaisesMessage(ValueError, message):
  1205. models.UniqueConstraint(
  1206. Lower("name"),
  1207. name="deferred_expression_unique",
  1208. deferrable=models.Deferrable.DEFERRED,
  1209. )
  1210. def test_invalid_defer_argument(self):
  1211. message = "UniqueConstraint.deferrable must be a Deferrable instance."
  1212. with self.assertRaisesMessage(TypeError, message):
  1213. models.UniqueConstraint(
  1214. fields=["name"],
  1215. name="name_invalid",
  1216. deferrable="invalid",
  1217. )
  1218. @skipUnlessDBFeature(
  1219. "supports_table_check_constraints",
  1220. "supports_covering_indexes",
  1221. )
  1222. def test_include_database_constraint(self):
  1223. UniqueConstraintInclude.objects.create(name="p1", color="red")
  1224. with self.assertRaises(IntegrityError):
  1225. UniqueConstraintInclude.objects.create(name="p1", color="blue")
  1226. def test_invalid_include_argument(self):
  1227. msg = "UniqueConstraint.include must be a list or tuple."
  1228. with self.assertRaisesMessage(TypeError, msg):
  1229. models.UniqueConstraint(
  1230. name="uniq_include",
  1231. fields=["field"],
  1232. include="other",
  1233. )
  1234. def test_invalid_opclasses_argument(self):
  1235. msg = "UniqueConstraint.opclasses must be a list or tuple."
  1236. with self.assertRaisesMessage(TypeError, msg):
  1237. models.UniqueConstraint(
  1238. name="uniq_opclasses",
  1239. fields=["field"],
  1240. opclasses="jsonb_path_ops",
  1241. )
  1242. def test_invalid_nulls_distinct_argument(self):
  1243. msg = "UniqueConstraint.nulls_distinct must be a bool."
  1244. with self.assertRaisesMessage(TypeError, msg):
  1245. models.UniqueConstraint(
  1246. name="uniq_opclasses", fields=["field"], nulls_distinct="NULLS DISTINCT"
  1247. )
  1248. def test_opclasses_and_fields_same_length(self):
  1249. msg = (
  1250. "UniqueConstraint.fields and UniqueConstraint.opclasses must have "
  1251. "the same number of elements."
  1252. )
  1253. with self.assertRaisesMessage(ValueError, msg):
  1254. models.UniqueConstraint(
  1255. name="uniq_opclasses",
  1256. fields=["field"],
  1257. opclasses=["foo", "bar"],
  1258. )
  1259. def test_requires_field_or_expression(self):
  1260. msg = (
  1261. "At least one field or expression is required to define a unique "
  1262. "constraint."
  1263. )
  1264. with self.assertRaisesMessage(ValueError, msg):
  1265. models.UniqueConstraint(name="name")
  1266. def test_expressions_and_fields_mutually_exclusive(self):
  1267. msg = "UniqueConstraint.fields and expressions are mutually exclusive."
  1268. with self.assertRaisesMessage(ValueError, msg):
  1269. models.UniqueConstraint(Lower("field_1"), fields=["field_2"], name="name")
  1270. def test_expressions_with_opclasses(self):
  1271. msg = (
  1272. "UniqueConstraint.opclasses cannot be used with expressions. Use "
  1273. "django.contrib.postgres.indexes.OpClass() instead."
  1274. )
  1275. with self.assertRaisesMessage(ValueError, msg):
  1276. models.UniqueConstraint(
  1277. Lower("field"),
  1278. name="test_func_opclass",
  1279. opclasses=["jsonb_path_ops"],
  1280. )
  1281. def test_requires_name(self):
  1282. msg = "A unique constraint must be named."
  1283. with self.assertRaisesMessage(ValueError, msg):
  1284. models.UniqueConstraint(fields=["field"])
  1285. def test_database_default(self):
  1286. models.UniqueConstraint(
  1287. fields=["field_with_db_default"], name="unique_field_with_db_default"
  1288. ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())
  1289. models.UniqueConstraint(
  1290. Upper("field_with_db_default"),
  1291. name="unique_field_with_db_default_expression",
  1292. ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())
  1293. ModelWithDatabaseDefault.objects.create()
  1294. msg = (
  1295. "Model with database default with this Field with db default already "
  1296. "exists."
  1297. )
  1298. with self.assertRaisesMessage(ValidationError, msg):
  1299. models.UniqueConstraint(
  1300. fields=["field_with_db_default"], name="unique_field_with_db_default"
  1301. ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())
  1302. msg = "Constraint “unique_field_with_db_default_expression” is violated."
  1303. with self.assertRaisesMessage(ValidationError, msg):
  1304. models.UniqueConstraint(
  1305. Upper("field_with_db_default"),
  1306. name="unique_field_with_db_default_expression",
  1307. ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault())