test_models.py 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815
  1. import unittest
  2. from django.core.checks import Error, Warning
  3. from django.core.checks.model_checks import _check_lazy_references
  4. from django.db import connection, connections, models
  5. from django.db.models.functions import Abs, Lower, Round
  6. from django.db.models.signals import post_init
  7. from django.test import SimpleTestCase, TestCase, ignore_warnings, skipUnlessDBFeature
  8. from django.test.utils import isolate_apps, override_settings, register_lookup
  9. from django.utils.deprecation import RemovedInDjango51Warning
  10. class EmptyRouter:
  11. pass
  12. def get_max_column_name_length():
  13. allowed_len = None
  14. db_alias = None
  15. for db in ("default", "other"):
  16. connection = connections[db]
  17. max_name_length = connection.ops.max_name_length()
  18. if max_name_length is not None and not connection.features.truncates_names:
  19. if allowed_len is None or max_name_length < allowed_len:
  20. allowed_len = max_name_length
  21. db_alias = db
  22. return (allowed_len, db_alias)
  23. @isolate_apps("invalid_models_tests")
  24. @ignore_warnings(category=RemovedInDjango51Warning)
  25. class IndexTogetherTests(SimpleTestCase):
  26. def test_non_iterable(self):
  27. class Model(models.Model):
  28. class Meta:
  29. index_together = 42
  30. self.assertEqual(
  31. Model.check(),
  32. [
  33. Error(
  34. "'index_together' must be a list or tuple.",
  35. obj=Model,
  36. id="models.E008",
  37. ),
  38. ],
  39. )
  40. def test_non_list(self):
  41. class Model(models.Model):
  42. class Meta:
  43. index_together = "not-a-list"
  44. self.assertEqual(
  45. Model.check(),
  46. [
  47. Error(
  48. "'index_together' must be a list or tuple.",
  49. obj=Model,
  50. id="models.E008",
  51. ),
  52. ],
  53. )
  54. def test_list_containing_non_iterable(self):
  55. class Model(models.Model):
  56. class Meta:
  57. index_together = [("a", "b"), 42]
  58. self.assertEqual(
  59. Model.check(),
  60. [
  61. Error(
  62. "All 'index_together' elements must be lists or tuples.",
  63. obj=Model,
  64. id="models.E009",
  65. ),
  66. ],
  67. )
  68. def test_pointing_to_missing_field(self):
  69. class Model(models.Model):
  70. class Meta:
  71. index_together = [["missing_field"]]
  72. self.assertEqual(
  73. Model.check(),
  74. [
  75. Error(
  76. "'index_together' refers to the nonexistent field 'missing_field'.",
  77. obj=Model,
  78. id="models.E012",
  79. ),
  80. ],
  81. )
  82. def test_pointing_to_non_local_field(self):
  83. class Foo(models.Model):
  84. field1 = models.IntegerField()
  85. class Bar(Foo):
  86. field2 = models.IntegerField()
  87. class Meta:
  88. index_together = [["field2", "field1"]]
  89. self.assertEqual(
  90. Bar.check(),
  91. [
  92. Error(
  93. "'index_together' refers to field 'field1' which is not "
  94. "local to model 'Bar'.",
  95. hint="This issue may be caused by multi-table inheritance.",
  96. obj=Bar,
  97. id="models.E016",
  98. ),
  99. ],
  100. )
  101. def test_pointing_to_m2m_field(self):
  102. class Model(models.Model):
  103. m2m = models.ManyToManyField("self")
  104. class Meta:
  105. index_together = [["m2m"]]
  106. self.assertEqual(
  107. Model.check(),
  108. [
  109. Error(
  110. "'index_together' refers to a ManyToManyField 'm2m', but "
  111. "ManyToManyFields are not permitted in 'index_together'.",
  112. obj=Model,
  113. id="models.E013",
  114. ),
  115. ],
  116. )
  117. def test_pointing_to_fk(self):
  118. class Foo(models.Model):
  119. pass
  120. class Bar(models.Model):
  121. foo_1 = models.ForeignKey(
  122. Foo, on_delete=models.CASCADE, related_name="bar_1"
  123. )
  124. foo_2 = models.ForeignKey(
  125. Foo, on_delete=models.CASCADE, related_name="bar_2"
  126. )
  127. class Meta:
  128. index_together = [["foo_1_id", "foo_2"]]
  129. self.assertEqual(Bar.check(), [])
  130. # unique_together tests are very similar to index_together tests.
  131. @isolate_apps("invalid_models_tests")
  132. class UniqueTogetherTests(SimpleTestCase):
  133. def test_non_iterable(self):
  134. class Model(models.Model):
  135. class Meta:
  136. unique_together = 42
  137. self.assertEqual(
  138. Model.check(),
  139. [
  140. Error(
  141. "'unique_together' must be a list or tuple.",
  142. obj=Model,
  143. id="models.E010",
  144. ),
  145. ],
  146. )
  147. def test_list_containing_non_iterable(self):
  148. class Model(models.Model):
  149. one = models.IntegerField()
  150. two = models.IntegerField()
  151. class Meta:
  152. unique_together = [("a", "b"), 42]
  153. self.assertEqual(
  154. Model.check(),
  155. [
  156. Error(
  157. "All 'unique_together' elements must be lists or tuples.",
  158. obj=Model,
  159. id="models.E011",
  160. ),
  161. ],
  162. )
  163. def test_non_list(self):
  164. class Model(models.Model):
  165. class Meta:
  166. unique_together = "not-a-list"
  167. self.assertEqual(
  168. Model.check(),
  169. [
  170. Error(
  171. "'unique_together' must be a list or tuple.",
  172. obj=Model,
  173. id="models.E010",
  174. ),
  175. ],
  176. )
  177. def test_valid_model(self):
  178. class Model(models.Model):
  179. one = models.IntegerField()
  180. two = models.IntegerField()
  181. class Meta:
  182. # unique_together can be a simple tuple
  183. unique_together = ("one", "two")
  184. self.assertEqual(Model.check(), [])
  185. def test_pointing_to_missing_field(self):
  186. class Model(models.Model):
  187. class Meta:
  188. unique_together = [["missing_field"]]
  189. self.assertEqual(
  190. Model.check(),
  191. [
  192. Error(
  193. "'unique_together' refers to the nonexistent field "
  194. "'missing_field'.",
  195. obj=Model,
  196. id="models.E012",
  197. ),
  198. ],
  199. )
  200. def test_pointing_to_m2m(self):
  201. class Model(models.Model):
  202. m2m = models.ManyToManyField("self")
  203. class Meta:
  204. unique_together = [["m2m"]]
  205. self.assertEqual(
  206. Model.check(),
  207. [
  208. Error(
  209. "'unique_together' refers to a ManyToManyField 'm2m', but "
  210. "ManyToManyFields are not permitted in 'unique_together'.",
  211. obj=Model,
  212. id="models.E013",
  213. ),
  214. ],
  215. )
  216. def test_pointing_to_fk(self):
  217. class Foo(models.Model):
  218. pass
  219. class Bar(models.Model):
  220. foo_1 = models.ForeignKey(
  221. Foo, on_delete=models.CASCADE, related_name="bar_1"
  222. )
  223. foo_2 = models.ForeignKey(
  224. Foo, on_delete=models.CASCADE, related_name="bar_2"
  225. )
  226. class Meta:
  227. unique_together = [["foo_1_id", "foo_2"]]
  228. self.assertEqual(Bar.check(), [])
  229. @isolate_apps("invalid_models_tests")
  230. class IndexesTests(TestCase):
  231. def test_pointing_to_missing_field(self):
  232. class Model(models.Model):
  233. class Meta:
  234. indexes = [models.Index(fields=["missing_field"], name="name")]
  235. self.assertEqual(
  236. Model.check(),
  237. [
  238. Error(
  239. "'indexes' refers to the nonexistent field 'missing_field'.",
  240. obj=Model,
  241. id="models.E012",
  242. ),
  243. ],
  244. )
  245. def test_pointing_to_m2m_field(self):
  246. class Model(models.Model):
  247. m2m = models.ManyToManyField("self")
  248. class Meta:
  249. indexes = [models.Index(fields=["m2m"], name="name")]
  250. self.assertEqual(
  251. Model.check(),
  252. [
  253. Error(
  254. "'indexes' refers to a ManyToManyField 'm2m', but "
  255. "ManyToManyFields are not permitted in 'indexes'.",
  256. obj=Model,
  257. id="models.E013",
  258. ),
  259. ],
  260. )
  261. def test_pointing_to_non_local_field(self):
  262. class Foo(models.Model):
  263. field1 = models.IntegerField()
  264. class Bar(Foo):
  265. field2 = models.IntegerField()
  266. class Meta:
  267. indexes = [models.Index(fields=["field2", "field1"], name="name")]
  268. self.assertEqual(
  269. Bar.check(),
  270. [
  271. Error(
  272. "'indexes' refers to field 'field1' which is not local to "
  273. "model 'Bar'.",
  274. hint="This issue may be caused by multi-table inheritance.",
  275. obj=Bar,
  276. id="models.E016",
  277. ),
  278. ],
  279. )
  280. def test_pointing_to_fk(self):
  281. class Foo(models.Model):
  282. pass
  283. class Bar(models.Model):
  284. foo_1 = models.ForeignKey(
  285. Foo, on_delete=models.CASCADE, related_name="bar_1"
  286. )
  287. foo_2 = models.ForeignKey(
  288. Foo, on_delete=models.CASCADE, related_name="bar_2"
  289. )
  290. class Meta:
  291. indexes = [
  292. models.Index(fields=["foo_1_id", "foo_2"], name="index_name")
  293. ]
  294. self.assertEqual(Bar.check(), [])
  295. def test_name_constraints(self):
  296. class Model(models.Model):
  297. class Meta:
  298. indexes = [
  299. models.Index(fields=["id"], name="_index_name"),
  300. models.Index(fields=["id"], name="5index_name"),
  301. ]
  302. self.assertEqual(
  303. Model.check(),
  304. [
  305. Error(
  306. "The index name '%sindex_name' cannot start with an "
  307. "underscore or a number." % prefix,
  308. obj=Model,
  309. id="models.E033",
  310. )
  311. for prefix in ("_", "5")
  312. ],
  313. )
  314. def test_max_name_length(self):
  315. index_name = "x" * 31
  316. class Model(models.Model):
  317. class Meta:
  318. indexes = [models.Index(fields=["id"], name=index_name)]
  319. self.assertEqual(
  320. Model.check(),
  321. [
  322. Error(
  323. "The index name '%s' cannot be longer than 30 characters."
  324. % index_name,
  325. obj=Model,
  326. id="models.E034",
  327. ),
  328. ],
  329. )
  330. def test_index_with_condition(self):
  331. class Model(models.Model):
  332. age = models.IntegerField()
  333. class Meta:
  334. indexes = [
  335. models.Index(
  336. fields=["age"],
  337. name="index_age_gte_10",
  338. condition=models.Q(age__gte=10),
  339. ),
  340. ]
  341. errors = Model.check(databases=self.databases)
  342. expected = (
  343. []
  344. if connection.features.supports_partial_indexes
  345. else [
  346. Warning(
  347. "%s does not support indexes with conditions."
  348. % connection.display_name,
  349. hint=(
  350. "Conditions will be ignored. Silence this warning if you "
  351. "don't care about it."
  352. ),
  353. obj=Model,
  354. id="models.W037",
  355. )
  356. ]
  357. )
  358. self.assertEqual(errors, expected)
  359. def test_index_with_condition_required_db_features(self):
  360. class Model(models.Model):
  361. age = models.IntegerField()
  362. class Meta:
  363. required_db_features = {"supports_partial_indexes"}
  364. indexes = [
  365. models.Index(
  366. fields=["age"],
  367. name="index_age_gte_10",
  368. condition=models.Q(age__gte=10),
  369. ),
  370. ]
  371. self.assertEqual(Model.check(databases=self.databases), [])
  372. def test_index_with_include(self):
  373. class Model(models.Model):
  374. age = models.IntegerField()
  375. class Meta:
  376. indexes = [
  377. models.Index(
  378. fields=["age"],
  379. name="index_age_include_id",
  380. include=["id"],
  381. ),
  382. ]
  383. errors = Model.check(databases=self.databases)
  384. expected = (
  385. []
  386. if connection.features.supports_covering_indexes
  387. else [
  388. Warning(
  389. "%s does not support indexes with non-key columns."
  390. % connection.display_name,
  391. hint=(
  392. "Non-key columns will be ignored. Silence this warning if "
  393. "you don't care about it."
  394. ),
  395. obj=Model,
  396. id="models.W040",
  397. )
  398. ]
  399. )
  400. self.assertEqual(errors, expected)
  401. def test_index_with_include_required_db_features(self):
  402. class Model(models.Model):
  403. age = models.IntegerField()
  404. class Meta:
  405. required_db_features = {"supports_covering_indexes"}
  406. indexes = [
  407. models.Index(
  408. fields=["age"],
  409. name="index_age_include_id",
  410. include=["id"],
  411. ),
  412. ]
  413. self.assertEqual(Model.check(databases=self.databases), [])
  414. @skipUnlessDBFeature("supports_covering_indexes")
  415. def test_index_include_pointing_to_missing_field(self):
  416. class Model(models.Model):
  417. class Meta:
  418. indexes = [
  419. models.Index(fields=["id"], include=["missing_field"], name="name"),
  420. ]
  421. self.assertEqual(
  422. Model.check(databases=self.databases),
  423. [
  424. Error(
  425. "'indexes' refers to the nonexistent field 'missing_field'.",
  426. obj=Model,
  427. id="models.E012",
  428. ),
  429. ],
  430. )
  431. @skipUnlessDBFeature("supports_covering_indexes")
  432. def test_index_include_pointing_to_m2m_field(self):
  433. class Model(models.Model):
  434. m2m = models.ManyToManyField("self")
  435. class Meta:
  436. indexes = [models.Index(fields=["id"], include=["m2m"], name="name")]
  437. self.assertEqual(
  438. Model.check(databases=self.databases),
  439. [
  440. Error(
  441. "'indexes' refers to a ManyToManyField 'm2m', but "
  442. "ManyToManyFields are not permitted in 'indexes'.",
  443. obj=Model,
  444. id="models.E013",
  445. ),
  446. ],
  447. )
  448. @skipUnlessDBFeature("supports_covering_indexes")
  449. def test_index_include_pointing_to_non_local_field(self):
  450. class Parent(models.Model):
  451. field1 = models.IntegerField()
  452. class Child(Parent):
  453. field2 = models.IntegerField()
  454. class Meta:
  455. indexes = [
  456. models.Index(fields=["field2"], include=["field1"], name="name"),
  457. ]
  458. self.assertEqual(
  459. Child.check(databases=self.databases),
  460. [
  461. Error(
  462. "'indexes' refers to field 'field1' which is not local to "
  463. "model 'Child'.",
  464. hint="This issue may be caused by multi-table inheritance.",
  465. obj=Child,
  466. id="models.E016",
  467. ),
  468. ],
  469. )
  470. @skipUnlessDBFeature("supports_covering_indexes")
  471. def test_index_include_pointing_to_fk(self):
  472. class Target(models.Model):
  473. pass
  474. class Model(models.Model):
  475. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name="target_1")
  476. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name="target_2")
  477. class Meta:
  478. constraints = [
  479. models.Index(
  480. fields=["id"],
  481. include=["fk_1_id", "fk_2"],
  482. name="name",
  483. ),
  484. ]
  485. self.assertEqual(Model.check(databases=self.databases), [])
  486. def test_func_index(self):
  487. class Model(models.Model):
  488. name = models.CharField(max_length=10)
  489. class Meta:
  490. indexes = [models.Index(Lower("name"), name="index_lower_name")]
  491. warn = Warning(
  492. "%s does not support indexes on expressions." % connection.display_name,
  493. hint=(
  494. "An index won't be created. Silence this warning if you don't "
  495. "care about it."
  496. ),
  497. obj=Model,
  498. id="models.W043",
  499. )
  500. expected = [] if connection.features.supports_expression_indexes else [warn]
  501. self.assertEqual(Model.check(databases=self.databases), expected)
  502. def test_func_index_required_db_features(self):
  503. class Model(models.Model):
  504. name = models.CharField(max_length=10)
  505. class Meta:
  506. indexes = [models.Index(Lower("name"), name="index_lower_name")]
  507. required_db_features = {"supports_expression_indexes"}
  508. self.assertEqual(Model.check(databases=self.databases), [])
  509. def test_func_index_complex_expression_custom_lookup(self):
  510. class Model(models.Model):
  511. height = models.IntegerField()
  512. weight = models.IntegerField()
  513. class Meta:
  514. indexes = [
  515. models.Index(
  516. models.F("height")
  517. / (models.F("weight__abs") + models.Value(5)),
  518. name="name",
  519. ),
  520. ]
  521. with register_lookup(models.IntegerField, Abs):
  522. self.assertEqual(Model.check(), [])
  523. def test_func_index_pointing_to_missing_field(self):
  524. class Model(models.Model):
  525. class Meta:
  526. indexes = [models.Index(Lower("missing_field").desc(), name="name")]
  527. self.assertEqual(
  528. Model.check(),
  529. [
  530. Error(
  531. "'indexes' refers to the nonexistent field 'missing_field'.",
  532. obj=Model,
  533. id="models.E012",
  534. ),
  535. ],
  536. )
  537. def test_func_index_pointing_to_missing_field_nested(self):
  538. class Model(models.Model):
  539. class Meta:
  540. indexes = [
  541. models.Index(Abs(Round("missing_field")), name="name"),
  542. ]
  543. self.assertEqual(
  544. Model.check(),
  545. [
  546. Error(
  547. "'indexes' refers to the nonexistent field 'missing_field'.",
  548. obj=Model,
  549. id="models.E012",
  550. ),
  551. ],
  552. )
  553. def test_func_index_pointing_to_m2m_field(self):
  554. class Model(models.Model):
  555. m2m = models.ManyToManyField("self")
  556. class Meta:
  557. indexes = [models.Index(Lower("m2m"), name="name")]
  558. self.assertEqual(
  559. Model.check(),
  560. [
  561. Error(
  562. "'indexes' refers to a ManyToManyField 'm2m', but "
  563. "ManyToManyFields are not permitted in 'indexes'.",
  564. obj=Model,
  565. id="models.E013",
  566. ),
  567. ],
  568. )
  569. def test_func_index_pointing_to_non_local_field(self):
  570. class Foo(models.Model):
  571. field1 = models.CharField(max_length=15)
  572. class Bar(Foo):
  573. class Meta:
  574. indexes = [models.Index(Lower("field1"), name="name")]
  575. self.assertEqual(
  576. Bar.check(),
  577. [
  578. Error(
  579. "'indexes' refers to field 'field1' which is not local to "
  580. "model 'Bar'.",
  581. hint="This issue may be caused by multi-table inheritance.",
  582. obj=Bar,
  583. id="models.E016",
  584. ),
  585. ],
  586. )
  587. def test_func_index_pointing_to_fk(self):
  588. class Foo(models.Model):
  589. pass
  590. class Bar(models.Model):
  591. foo_1 = models.ForeignKey(Foo, models.CASCADE, related_name="bar_1")
  592. foo_2 = models.ForeignKey(Foo, models.CASCADE, related_name="bar_2")
  593. class Meta:
  594. indexes = [
  595. models.Index(Lower("foo_1_id"), Lower("foo_2"), name="index_name"),
  596. ]
  597. self.assertEqual(Bar.check(), [])
  598. @isolate_apps("invalid_models_tests")
  599. class FieldNamesTests(TestCase):
  600. databases = {"default", "other"}
  601. def test_ending_with_underscore(self):
  602. class Model(models.Model):
  603. field_ = models.CharField(max_length=10)
  604. m2m_ = models.ManyToManyField("self")
  605. self.assertEqual(
  606. Model.check(),
  607. [
  608. Error(
  609. "Field names must not end with an underscore.",
  610. obj=Model._meta.get_field("field_"),
  611. id="fields.E001",
  612. ),
  613. Error(
  614. "Field names must not end with an underscore.",
  615. obj=Model._meta.get_field("m2m_"),
  616. id="fields.E001",
  617. ),
  618. ],
  619. )
  620. max_column_name_length, column_limit_db_alias = get_max_column_name_length()
  621. @unittest.skipIf(
  622. max_column_name_length is None,
  623. "The database doesn't have a column name length limit.",
  624. )
  625. def test_M2M_long_column_name(self):
  626. """
  627. #13711 -- Model check for long M2M column names when database has
  628. column name length limits.
  629. """
  630. # A model with very long name which will be used to set relations to.
  631. class VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
  632. models.Model
  633. ):
  634. title = models.CharField(max_length=11)
  635. # Main model for which checks will be performed.
  636. class ModelWithLongField(models.Model):
  637. m2m_field = models.ManyToManyField(
  638. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  639. related_name="rn1",
  640. )
  641. m2m_field2 = models.ManyToManyField(
  642. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  643. related_name="rn2",
  644. through="m2msimple",
  645. )
  646. m2m_field3 = models.ManyToManyField(
  647. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  648. related_name="rn3",
  649. through="m2mcomplex",
  650. )
  651. fk = models.ForeignKey(
  652. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  653. models.CASCADE,
  654. related_name="rn4",
  655. )
  656. # Models used for setting `through` in M2M field.
  657. class m2msimple(models.Model):
  658. id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
  659. class m2mcomplex(models.Model):
  660. id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
  661. long_field_name = "a" * (self.max_column_name_length + 1)
  662. models.ForeignKey(
  663. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  664. models.CASCADE,
  665. ).contribute_to_class(m2msimple, long_field_name)
  666. models.ForeignKey(
  667. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  668. models.CASCADE,
  669. db_column=long_field_name,
  670. ).contribute_to_class(m2mcomplex, long_field_name)
  671. errors = ModelWithLongField.check(databases=("default", "other"))
  672. # First error because of M2M field set on the model with long name.
  673. m2m_long_name = (
  674. "verylongmodelnamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz_id"
  675. )
  676. if self.max_column_name_length > len(m2m_long_name):
  677. # Some databases support names longer than the test name.
  678. expected = []
  679. else:
  680. expected = [
  681. Error(
  682. 'Autogenerated column name too long for M2M field "%s". '
  683. 'Maximum length is "%s" for database "%s".'
  684. % (
  685. m2m_long_name,
  686. self.max_column_name_length,
  687. self.column_limit_db_alias,
  688. ),
  689. hint="Use 'through' to create a separate model for "
  690. "M2M and then set column_name using 'db_column'.",
  691. obj=ModelWithLongField,
  692. id="models.E019",
  693. )
  694. ]
  695. # Second error because the FK specified in the `through` model
  696. # `m2msimple` has auto-generated name longer than allowed.
  697. # There will be no check errors in the other M2M because it
  698. # specifies db_column for the FK in `through` model even if the actual
  699. # name is longer than the limits of the database.
  700. expected.append(
  701. Error(
  702. 'Autogenerated column name too long for M2M field "%s_id". '
  703. 'Maximum length is "%s" for database "%s".'
  704. % (
  705. long_field_name,
  706. self.max_column_name_length,
  707. self.column_limit_db_alias,
  708. ),
  709. hint="Use 'through' to create a separate model for "
  710. "M2M and then set column_name using 'db_column'.",
  711. obj=ModelWithLongField,
  712. id="models.E019",
  713. )
  714. )
  715. self.assertEqual(errors, expected)
  716. # Check for long column names is called only for specified database
  717. # aliases.
  718. self.assertEqual(ModelWithLongField.check(databases=None), [])
  719. @unittest.skipIf(
  720. max_column_name_length is None,
  721. "The database doesn't have a column name length limit.",
  722. )
  723. def test_local_field_long_column_name(self):
  724. """
  725. #13711 -- Model check for long column names
  726. when database does not support long names.
  727. """
  728. class ModelWithLongField(models.Model):
  729. title = models.CharField(max_length=11)
  730. long_field_name = "a" * (self.max_column_name_length + 1)
  731. long_field_name2 = "b" * (self.max_column_name_length + 1)
  732. models.CharField(max_length=11).contribute_to_class(
  733. ModelWithLongField, long_field_name
  734. )
  735. models.CharField(max_length=11, db_column="vlmn").contribute_to_class(
  736. ModelWithLongField, long_field_name2
  737. )
  738. self.assertEqual(
  739. ModelWithLongField.check(databases=("default", "other")),
  740. [
  741. Error(
  742. 'Autogenerated column name too long for field "%s". '
  743. 'Maximum length is "%s" for database "%s".'
  744. % (
  745. long_field_name,
  746. self.max_column_name_length,
  747. self.column_limit_db_alias,
  748. ),
  749. hint="Set the column name manually using 'db_column'.",
  750. obj=ModelWithLongField,
  751. id="models.E018",
  752. )
  753. ],
  754. )
  755. # Check for long column names is called only for specified database
  756. # aliases.
  757. self.assertEqual(ModelWithLongField.check(databases=None), [])
  758. def test_including_separator(self):
  759. class Model(models.Model):
  760. some__field = models.IntegerField()
  761. self.assertEqual(
  762. Model.check(),
  763. [
  764. Error(
  765. 'Field names must not contain "__".',
  766. obj=Model._meta.get_field("some__field"),
  767. id="fields.E002",
  768. )
  769. ],
  770. )
  771. def test_pk(self):
  772. class Model(models.Model):
  773. pk = models.IntegerField()
  774. self.assertEqual(
  775. Model.check(),
  776. [
  777. Error(
  778. "'pk' is a reserved word that cannot be used as a field name.",
  779. obj=Model._meta.get_field("pk"),
  780. id="fields.E003",
  781. )
  782. ],
  783. )
  784. def test_db_column_clash(self):
  785. class Model(models.Model):
  786. foo = models.IntegerField()
  787. bar = models.IntegerField(db_column="foo")
  788. self.assertEqual(
  789. Model.check(),
  790. [
  791. Error(
  792. "Field 'bar' has column name 'foo' that is used by "
  793. "another field.",
  794. hint="Specify a 'db_column' for the field.",
  795. obj=Model,
  796. id="models.E007",
  797. )
  798. ],
  799. )
  800. @isolate_apps("invalid_models_tests")
  801. class ShadowingFieldsTests(SimpleTestCase):
  802. def test_field_name_clash_with_child_accessor(self):
  803. class Parent(models.Model):
  804. pass
  805. class Child(Parent):
  806. child = models.CharField(max_length=100)
  807. self.assertEqual(
  808. Child.check(),
  809. [
  810. Error(
  811. "The field 'child' clashes with the field "
  812. "'child' from model 'invalid_models_tests.parent'.",
  813. obj=Child._meta.get_field("child"),
  814. id="models.E006",
  815. )
  816. ],
  817. )
  818. def test_field_name_clash_with_m2m_through(self):
  819. class Parent(models.Model):
  820. clash_id = models.IntegerField()
  821. class Child(Parent):
  822. clash = models.ForeignKey("Child", models.CASCADE)
  823. class Model(models.Model):
  824. parents = models.ManyToManyField(
  825. to=Parent,
  826. through="Through",
  827. through_fields=["parent", "model"],
  828. )
  829. class Through(models.Model):
  830. parent = models.ForeignKey(Parent, models.CASCADE)
  831. model = models.ForeignKey(Model, models.CASCADE)
  832. self.assertEqual(
  833. Child.check(),
  834. [
  835. Error(
  836. "The field 'clash' clashes with the field 'clash_id' from "
  837. "model 'invalid_models_tests.parent'.",
  838. obj=Child._meta.get_field("clash"),
  839. id="models.E006",
  840. )
  841. ],
  842. )
  843. def test_multiinheritance_clash(self):
  844. class Mother(models.Model):
  845. clash = models.IntegerField()
  846. class Father(models.Model):
  847. clash = models.IntegerField()
  848. class Child(Mother, Father):
  849. # Here we have two clashed: id (automatic field) and clash, because
  850. # both parents define these fields.
  851. pass
  852. self.assertEqual(
  853. Child.check(),
  854. [
  855. Error(
  856. "The field 'id' from parent model "
  857. "'invalid_models_tests.mother' clashes with the field 'id' "
  858. "from parent model 'invalid_models_tests.father'.",
  859. obj=Child,
  860. id="models.E005",
  861. ),
  862. Error(
  863. "The field 'clash' from parent model "
  864. "'invalid_models_tests.mother' clashes with the field 'clash' "
  865. "from parent model 'invalid_models_tests.father'.",
  866. obj=Child,
  867. id="models.E005",
  868. ),
  869. ],
  870. )
  871. def test_inheritance_clash(self):
  872. class Parent(models.Model):
  873. f_id = models.IntegerField()
  874. class Target(models.Model):
  875. # This field doesn't result in a clash.
  876. f_id = models.IntegerField()
  877. class Child(Parent):
  878. # This field clashes with parent "f_id" field.
  879. f = models.ForeignKey(Target, models.CASCADE)
  880. self.assertEqual(
  881. Child.check(),
  882. [
  883. Error(
  884. "The field 'f' clashes with the field 'f_id' "
  885. "from model 'invalid_models_tests.parent'.",
  886. obj=Child._meta.get_field("f"),
  887. id="models.E006",
  888. )
  889. ],
  890. )
  891. def test_multigeneration_inheritance(self):
  892. class GrandParent(models.Model):
  893. clash = models.IntegerField()
  894. class Parent(GrandParent):
  895. pass
  896. class Child(Parent):
  897. pass
  898. class GrandChild(Child):
  899. clash = models.IntegerField()
  900. self.assertEqual(
  901. GrandChild.check(),
  902. [
  903. Error(
  904. "The field 'clash' clashes with the field 'clash' "
  905. "from model 'invalid_models_tests.grandparent'.",
  906. obj=GrandChild._meta.get_field("clash"),
  907. id="models.E006",
  908. )
  909. ],
  910. )
  911. def test_id_clash(self):
  912. class Target(models.Model):
  913. pass
  914. class Model(models.Model):
  915. fk = models.ForeignKey(Target, models.CASCADE)
  916. fk_id = models.IntegerField()
  917. self.assertEqual(
  918. Model.check(),
  919. [
  920. Error(
  921. "The field 'fk_id' clashes with the field 'fk' from model "
  922. "'invalid_models_tests.model'.",
  923. obj=Model._meta.get_field("fk_id"),
  924. id="models.E006",
  925. )
  926. ],
  927. )
  928. @isolate_apps("invalid_models_tests")
  929. class OtherModelTests(SimpleTestCase):
  930. def test_unique_primary_key(self):
  931. invalid_id = models.IntegerField(primary_key=False)
  932. class Model(models.Model):
  933. id = invalid_id
  934. self.assertEqual(
  935. Model.check(),
  936. [
  937. Error(
  938. "'id' can only be used as a field name if the field also sets "
  939. "'primary_key=True'.",
  940. obj=Model,
  941. id="models.E004",
  942. ),
  943. ],
  944. )
  945. def test_ordering_non_iterable(self):
  946. class Model(models.Model):
  947. class Meta:
  948. ordering = "missing_field"
  949. self.assertEqual(
  950. Model.check(),
  951. [
  952. Error(
  953. "'ordering' must be a tuple or list "
  954. "(even if you want to order by only one field).",
  955. obj=Model,
  956. id="models.E014",
  957. ),
  958. ],
  959. )
  960. def test_just_ordering_no_errors(self):
  961. class Model(models.Model):
  962. order = models.PositiveIntegerField()
  963. class Meta:
  964. ordering = ["order"]
  965. self.assertEqual(Model.check(), [])
  966. def test_just_order_with_respect_to_no_errors(self):
  967. class Question(models.Model):
  968. pass
  969. class Answer(models.Model):
  970. question = models.ForeignKey(Question, models.CASCADE)
  971. class Meta:
  972. order_with_respect_to = "question"
  973. self.assertEqual(Answer.check(), [])
  974. def test_ordering_with_order_with_respect_to(self):
  975. class Question(models.Model):
  976. pass
  977. class Answer(models.Model):
  978. question = models.ForeignKey(Question, models.CASCADE)
  979. order = models.IntegerField()
  980. class Meta:
  981. order_with_respect_to = "question"
  982. ordering = ["order"]
  983. self.assertEqual(
  984. Answer.check(),
  985. [
  986. Error(
  987. "'ordering' and 'order_with_respect_to' cannot be used together.",
  988. obj=Answer,
  989. id="models.E021",
  990. ),
  991. ],
  992. )
  993. def test_non_valid(self):
  994. class RelationModel(models.Model):
  995. pass
  996. class Model(models.Model):
  997. relation = models.ManyToManyField(RelationModel)
  998. class Meta:
  999. ordering = ["relation"]
  1000. self.assertEqual(
  1001. Model.check(),
  1002. [
  1003. Error(
  1004. "'ordering' refers to the nonexistent field, related field, "
  1005. "or lookup 'relation'.",
  1006. obj=Model,
  1007. id="models.E015",
  1008. ),
  1009. ],
  1010. )
  1011. def test_ordering_pointing_to_missing_field(self):
  1012. class Model(models.Model):
  1013. class Meta:
  1014. ordering = ("missing_field",)
  1015. self.assertEqual(
  1016. Model.check(),
  1017. [
  1018. Error(
  1019. "'ordering' refers to the nonexistent field, related field, "
  1020. "or lookup 'missing_field'.",
  1021. obj=Model,
  1022. id="models.E015",
  1023. )
  1024. ],
  1025. )
  1026. def test_ordering_pointing_to_missing_foreignkey_field(self):
  1027. class Model(models.Model):
  1028. missing_fk_field = models.IntegerField()
  1029. class Meta:
  1030. ordering = ("missing_fk_field_id",)
  1031. self.assertEqual(
  1032. Model.check(),
  1033. [
  1034. Error(
  1035. "'ordering' refers to the nonexistent field, related field, "
  1036. "or lookup 'missing_fk_field_id'.",
  1037. obj=Model,
  1038. id="models.E015",
  1039. )
  1040. ],
  1041. )
  1042. def test_ordering_pointing_to_missing_related_field(self):
  1043. class Model(models.Model):
  1044. test = models.IntegerField()
  1045. class Meta:
  1046. ordering = ("missing_related__id",)
  1047. self.assertEqual(
  1048. Model.check(),
  1049. [
  1050. Error(
  1051. "'ordering' refers to the nonexistent field, related field, "
  1052. "or lookup 'missing_related__id'.",
  1053. obj=Model,
  1054. id="models.E015",
  1055. )
  1056. ],
  1057. )
  1058. def test_ordering_pointing_to_missing_related_model_field(self):
  1059. class Parent(models.Model):
  1060. pass
  1061. class Child(models.Model):
  1062. parent = models.ForeignKey(Parent, models.CASCADE)
  1063. class Meta:
  1064. ordering = ("parent__missing_field",)
  1065. self.assertEqual(
  1066. Child.check(),
  1067. [
  1068. Error(
  1069. "'ordering' refers to the nonexistent field, related field, "
  1070. "or lookup 'parent__missing_field'.",
  1071. obj=Child,
  1072. id="models.E015",
  1073. )
  1074. ],
  1075. )
  1076. def test_ordering_pointing_to_non_related_field(self):
  1077. class Child(models.Model):
  1078. parent = models.IntegerField()
  1079. class Meta:
  1080. ordering = ("parent__missing_field",)
  1081. self.assertEqual(
  1082. Child.check(),
  1083. [
  1084. Error(
  1085. "'ordering' refers to the nonexistent field, related field, "
  1086. "or lookup 'parent__missing_field'.",
  1087. obj=Child,
  1088. id="models.E015",
  1089. )
  1090. ],
  1091. )
  1092. def test_ordering_pointing_to_two_related_model_field(self):
  1093. class Parent2(models.Model):
  1094. pass
  1095. class Parent1(models.Model):
  1096. parent2 = models.ForeignKey(Parent2, models.CASCADE)
  1097. class Child(models.Model):
  1098. parent1 = models.ForeignKey(Parent1, models.CASCADE)
  1099. class Meta:
  1100. ordering = ("parent1__parent2__missing_field",)
  1101. self.assertEqual(
  1102. Child.check(),
  1103. [
  1104. Error(
  1105. "'ordering' refers to the nonexistent field, related field, "
  1106. "or lookup 'parent1__parent2__missing_field'.",
  1107. obj=Child,
  1108. id="models.E015",
  1109. )
  1110. ],
  1111. )
  1112. def test_ordering_pointing_multiple_times_to_model_fields(self):
  1113. class Parent(models.Model):
  1114. field1 = models.CharField(max_length=100)
  1115. field2 = models.CharField(max_length=100)
  1116. class Child(models.Model):
  1117. parent = models.ForeignKey(Parent, models.CASCADE)
  1118. class Meta:
  1119. ordering = ("parent__field1__field2",)
  1120. self.assertEqual(
  1121. Child.check(),
  1122. [
  1123. Error(
  1124. "'ordering' refers to the nonexistent field, related field, "
  1125. "or lookup 'parent__field1__field2'.",
  1126. obj=Child,
  1127. id="models.E015",
  1128. )
  1129. ],
  1130. )
  1131. def test_ordering_allows_registered_lookups(self):
  1132. class Model(models.Model):
  1133. test = models.CharField(max_length=100)
  1134. class Meta:
  1135. ordering = ("test__lower",)
  1136. with register_lookup(models.CharField, Lower):
  1137. self.assertEqual(Model.check(), [])
  1138. def test_ordering_pointing_to_lookup_not_transform(self):
  1139. class Model(models.Model):
  1140. test = models.CharField(max_length=100)
  1141. class Meta:
  1142. ordering = ("test__isnull",)
  1143. self.assertEqual(Model.check(), [])
  1144. def test_ordering_pointing_to_related_model_pk(self):
  1145. class Parent(models.Model):
  1146. pass
  1147. class Child(models.Model):
  1148. parent = models.ForeignKey(Parent, models.CASCADE)
  1149. class Meta:
  1150. ordering = ("parent__pk",)
  1151. self.assertEqual(Child.check(), [])
  1152. def test_ordering_pointing_to_foreignkey_field(self):
  1153. class Parent(models.Model):
  1154. pass
  1155. class Child(models.Model):
  1156. parent = models.ForeignKey(Parent, models.CASCADE)
  1157. class Meta:
  1158. ordering = ("parent_id",)
  1159. self.assertFalse(Child.check())
  1160. def test_name_beginning_with_underscore(self):
  1161. class _Model(models.Model):
  1162. pass
  1163. self.assertEqual(
  1164. _Model.check(),
  1165. [
  1166. Error(
  1167. "The model name '_Model' cannot start or end with an underscore "
  1168. "as it collides with the query lookup syntax.",
  1169. obj=_Model,
  1170. id="models.E023",
  1171. )
  1172. ],
  1173. )
  1174. def test_name_ending_with_underscore(self):
  1175. class Model_(models.Model):
  1176. pass
  1177. self.assertEqual(
  1178. Model_.check(),
  1179. [
  1180. Error(
  1181. "The model name 'Model_' cannot start or end with an underscore "
  1182. "as it collides with the query lookup syntax.",
  1183. obj=Model_,
  1184. id="models.E023",
  1185. )
  1186. ],
  1187. )
  1188. def test_name_contains_double_underscores(self):
  1189. class Test__Model(models.Model):
  1190. pass
  1191. self.assertEqual(
  1192. Test__Model.check(),
  1193. [
  1194. Error(
  1195. "The model name 'Test__Model' cannot contain double underscores "
  1196. "as it collides with the query lookup syntax.",
  1197. obj=Test__Model,
  1198. id="models.E024",
  1199. )
  1200. ],
  1201. )
  1202. def test_property_and_related_field_accessor_clash(self):
  1203. class Model(models.Model):
  1204. fk = models.ForeignKey("self", models.CASCADE)
  1205. # Override related field accessor.
  1206. Model.fk_id = property(lambda self: "ERROR")
  1207. self.assertEqual(
  1208. Model.check(),
  1209. [
  1210. Error(
  1211. "The property 'fk_id' clashes with a related field accessor.",
  1212. obj=Model,
  1213. id="models.E025",
  1214. )
  1215. ],
  1216. )
  1217. def test_single_primary_key(self):
  1218. class Model(models.Model):
  1219. foo = models.IntegerField(primary_key=True)
  1220. bar = models.IntegerField(primary_key=True)
  1221. self.assertEqual(
  1222. Model.check(),
  1223. [
  1224. Error(
  1225. "The model cannot have more than one field with "
  1226. "'primary_key=True'.",
  1227. obj=Model,
  1228. id="models.E026",
  1229. )
  1230. ],
  1231. )
  1232. @override_settings(TEST_SWAPPED_MODEL_BAD_VALUE="not-a-model")
  1233. def test_swappable_missing_app_name(self):
  1234. class Model(models.Model):
  1235. class Meta:
  1236. swappable = "TEST_SWAPPED_MODEL_BAD_VALUE"
  1237. self.assertEqual(
  1238. Model.check(),
  1239. [
  1240. Error(
  1241. "'TEST_SWAPPED_MODEL_BAD_VALUE' is not of the form "
  1242. "'app_label.app_name'.",
  1243. id="models.E001",
  1244. ),
  1245. ],
  1246. )
  1247. @override_settings(TEST_SWAPPED_MODEL_BAD_MODEL="not_an_app.Target")
  1248. def test_swappable_missing_app(self):
  1249. class Model(models.Model):
  1250. class Meta:
  1251. swappable = "TEST_SWAPPED_MODEL_BAD_MODEL"
  1252. self.assertEqual(
  1253. Model.check(),
  1254. [
  1255. Error(
  1256. "'TEST_SWAPPED_MODEL_BAD_MODEL' references 'not_an_app.Target', "
  1257. "which has not been installed, or is abstract.",
  1258. id="models.E002",
  1259. ),
  1260. ],
  1261. )
  1262. def test_two_m2m_through_same_relationship(self):
  1263. class Person(models.Model):
  1264. pass
  1265. class Group(models.Model):
  1266. primary = models.ManyToManyField(
  1267. Person, through="Membership", related_name="primary"
  1268. )
  1269. secondary = models.ManyToManyField(
  1270. Person, through="Membership", related_name="secondary"
  1271. )
  1272. class Membership(models.Model):
  1273. person = models.ForeignKey(Person, models.CASCADE)
  1274. group = models.ForeignKey(Group, models.CASCADE)
  1275. self.assertEqual(
  1276. Group.check(),
  1277. [
  1278. Error(
  1279. "The model has two identical many-to-many relations through "
  1280. "the intermediate model 'invalid_models_tests.Membership'.",
  1281. obj=Group,
  1282. id="models.E003",
  1283. )
  1284. ],
  1285. )
  1286. def test_two_m2m_through_same_model_with_different_through_fields(self):
  1287. class Country(models.Model):
  1288. pass
  1289. class ShippingMethod(models.Model):
  1290. to_countries = models.ManyToManyField(
  1291. Country,
  1292. through="ShippingMethodPrice",
  1293. through_fields=("method", "to_country"),
  1294. )
  1295. from_countries = models.ManyToManyField(
  1296. Country,
  1297. through="ShippingMethodPrice",
  1298. through_fields=("method", "from_country"),
  1299. related_name="+",
  1300. )
  1301. class ShippingMethodPrice(models.Model):
  1302. method = models.ForeignKey(ShippingMethod, models.CASCADE)
  1303. to_country = models.ForeignKey(Country, models.CASCADE)
  1304. from_country = models.ForeignKey(Country, models.CASCADE)
  1305. self.assertEqual(ShippingMethod.check(), [])
  1306. def test_onetoone_with_parent_model(self):
  1307. class Place(models.Model):
  1308. pass
  1309. class ParkingLot(Place):
  1310. other_place = models.OneToOneField(
  1311. Place, models.CASCADE, related_name="other_parking"
  1312. )
  1313. self.assertEqual(ParkingLot.check(), [])
  1314. def test_onetoone_with_explicit_parent_link_parent_model(self):
  1315. class Place(models.Model):
  1316. pass
  1317. class ParkingLot(Place):
  1318. place = models.OneToOneField(
  1319. Place, models.CASCADE, parent_link=True, primary_key=True
  1320. )
  1321. other_place = models.OneToOneField(
  1322. Place, models.CASCADE, related_name="other_parking"
  1323. )
  1324. self.assertEqual(ParkingLot.check(), [])
  1325. def test_m2m_table_name_clash(self):
  1326. class Foo(models.Model):
  1327. bar = models.ManyToManyField("Bar", db_table="myapp_bar")
  1328. class Meta:
  1329. db_table = "myapp_foo"
  1330. class Bar(models.Model):
  1331. class Meta:
  1332. db_table = "myapp_bar"
  1333. self.assertEqual(
  1334. Foo.check(),
  1335. [
  1336. Error(
  1337. "The field's intermediary table 'myapp_bar' clashes with the "
  1338. "table name of 'invalid_models_tests.Bar'.",
  1339. obj=Foo._meta.get_field("bar"),
  1340. id="fields.E340",
  1341. )
  1342. ],
  1343. )
  1344. @override_settings(
  1345. DATABASE_ROUTERS=["invalid_models_tests.test_models.EmptyRouter"]
  1346. )
  1347. def test_m2m_table_name_clash_database_routers_installed(self):
  1348. class Foo(models.Model):
  1349. bar = models.ManyToManyField("Bar", db_table="myapp_bar")
  1350. class Meta:
  1351. db_table = "myapp_foo"
  1352. class Bar(models.Model):
  1353. class Meta:
  1354. db_table = "myapp_bar"
  1355. self.assertEqual(
  1356. Foo.check(),
  1357. [
  1358. Warning(
  1359. "The field's intermediary table 'myapp_bar' clashes with the "
  1360. "table name of 'invalid_models_tests.Bar'.",
  1361. obj=Foo._meta.get_field("bar"),
  1362. hint=(
  1363. "You have configured settings.DATABASE_ROUTERS. Verify "
  1364. "that the table of 'invalid_models_tests.Bar' is "
  1365. "correctly routed to a separate database."
  1366. ),
  1367. id="fields.W344",
  1368. ),
  1369. ],
  1370. )
  1371. def test_m2m_field_table_name_clash(self):
  1372. class Foo(models.Model):
  1373. pass
  1374. class Bar(models.Model):
  1375. foos = models.ManyToManyField(Foo, db_table="clash")
  1376. class Baz(models.Model):
  1377. foos = models.ManyToManyField(Foo, db_table="clash")
  1378. self.assertEqual(
  1379. Bar.check() + Baz.check(),
  1380. [
  1381. Error(
  1382. "The field's intermediary table 'clash' clashes with the "
  1383. "table name of 'invalid_models_tests.Baz.foos'.",
  1384. obj=Bar._meta.get_field("foos"),
  1385. id="fields.E340",
  1386. ),
  1387. Error(
  1388. "The field's intermediary table 'clash' clashes with the "
  1389. "table name of 'invalid_models_tests.Bar.foos'.",
  1390. obj=Baz._meta.get_field("foos"),
  1391. id="fields.E340",
  1392. ),
  1393. ],
  1394. )
  1395. @override_settings(
  1396. DATABASE_ROUTERS=["invalid_models_tests.test_models.EmptyRouter"]
  1397. )
  1398. def test_m2m_field_table_name_clash_database_routers_installed(self):
  1399. class Foo(models.Model):
  1400. pass
  1401. class Bar(models.Model):
  1402. foos = models.ManyToManyField(Foo, db_table="clash")
  1403. class Baz(models.Model):
  1404. foos = models.ManyToManyField(Foo, db_table="clash")
  1405. self.assertEqual(
  1406. Bar.check() + Baz.check(),
  1407. [
  1408. Warning(
  1409. "The field's intermediary table 'clash' clashes with the "
  1410. "table name of 'invalid_models_tests.%s.foos'." % clashing_model,
  1411. obj=model_cls._meta.get_field("foos"),
  1412. hint=(
  1413. "You have configured settings.DATABASE_ROUTERS. Verify "
  1414. "that the table of 'invalid_models_tests.%s.foos' is "
  1415. "correctly routed to a separate database." % clashing_model
  1416. ),
  1417. id="fields.W344",
  1418. )
  1419. for model_cls, clashing_model in [(Bar, "Baz"), (Baz, "Bar")]
  1420. ],
  1421. )
  1422. def test_m2m_autogenerated_table_name_clash(self):
  1423. class Foo(models.Model):
  1424. class Meta:
  1425. db_table = "bar_foos"
  1426. class Bar(models.Model):
  1427. # The autogenerated `db_table` will be bar_foos.
  1428. foos = models.ManyToManyField(Foo)
  1429. class Meta:
  1430. db_table = "bar"
  1431. self.assertEqual(
  1432. Bar.check(),
  1433. [
  1434. Error(
  1435. "The field's intermediary table 'bar_foos' clashes with the "
  1436. "table name of 'invalid_models_tests.Foo'.",
  1437. obj=Bar._meta.get_field("foos"),
  1438. id="fields.E340",
  1439. )
  1440. ],
  1441. )
  1442. @override_settings(
  1443. DATABASE_ROUTERS=["invalid_models_tests.test_models.EmptyRouter"]
  1444. )
  1445. def test_m2m_autogenerated_table_name_clash_database_routers_installed(self):
  1446. class Foo(models.Model):
  1447. class Meta:
  1448. db_table = "bar_foos"
  1449. class Bar(models.Model):
  1450. # The autogenerated db_table is bar_foos.
  1451. foos = models.ManyToManyField(Foo)
  1452. class Meta:
  1453. db_table = "bar"
  1454. self.assertEqual(
  1455. Bar.check(),
  1456. [
  1457. Warning(
  1458. "The field's intermediary table 'bar_foos' clashes with the "
  1459. "table name of 'invalid_models_tests.Foo'.",
  1460. obj=Bar._meta.get_field("foos"),
  1461. hint=(
  1462. "You have configured settings.DATABASE_ROUTERS. Verify "
  1463. "that the table of 'invalid_models_tests.Foo' is "
  1464. "correctly routed to a separate database."
  1465. ),
  1466. id="fields.W344",
  1467. ),
  1468. ],
  1469. )
  1470. def test_m2m_unmanaged_shadow_models_not_checked(self):
  1471. class A1(models.Model):
  1472. pass
  1473. class C1(models.Model):
  1474. mm_a = models.ManyToManyField(A1, db_table="d1")
  1475. # Unmanaged models that shadow the above models. Reused table names
  1476. # shouldn't be flagged by any checks.
  1477. class A2(models.Model):
  1478. class Meta:
  1479. managed = False
  1480. class C2(models.Model):
  1481. mm_a = models.ManyToManyField(A2, through="Intermediate")
  1482. class Meta:
  1483. managed = False
  1484. class Intermediate(models.Model):
  1485. a2 = models.ForeignKey(A2, models.CASCADE, db_column="a1_id")
  1486. c2 = models.ForeignKey(C2, models.CASCADE, db_column="c1_id")
  1487. class Meta:
  1488. db_table = "d1"
  1489. managed = False
  1490. self.assertEqual(C1.check(), [])
  1491. self.assertEqual(C2.check(), [])
  1492. def test_m2m_to_concrete_and_proxy_allowed(self):
  1493. class A(models.Model):
  1494. pass
  1495. class Through(models.Model):
  1496. a = models.ForeignKey("A", models.CASCADE)
  1497. c = models.ForeignKey("C", models.CASCADE)
  1498. class ThroughProxy(Through):
  1499. class Meta:
  1500. proxy = True
  1501. class C(models.Model):
  1502. mm_a = models.ManyToManyField(A, through=Through)
  1503. mm_aproxy = models.ManyToManyField(
  1504. A, through=ThroughProxy, related_name="proxied_m2m"
  1505. )
  1506. self.assertEqual(C.check(), [])
  1507. @isolate_apps("django.contrib.auth", kwarg_name="apps")
  1508. def test_lazy_reference_checks(self, apps):
  1509. class DummyModel(models.Model):
  1510. author = models.ForeignKey("Author", models.CASCADE)
  1511. class Meta:
  1512. app_label = "invalid_models_tests"
  1513. class DummyClass:
  1514. def __call__(self, **kwargs):
  1515. pass
  1516. def dummy_method(self):
  1517. pass
  1518. def dummy_function(*args, **kwargs):
  1519. pass
  1520. apps.lazy_model_operation(dummy_function, ("auth", "imaginarymodel"))
  1521. apps.lazy_model_operation(dummy_function, ("fanciful_app", "imaginarymodel"))
  1522. post_init.connect(dummy_function, sender="missing-app.Model", apps=apps)
  1523. post_init.connect(DummyClass(), sender="missing-app.Model", apps=apps)
  1524. post_init.connect(
  1525. DummyClass().dummy_method, sender="missing-app.Model", apps=apps
  1526. )
  1527. self.assertEqual(
  1528. _check_lazy_references(apps),
  1529. [
  1530. Error(
  1531. "%r contains a lazy reference to auth.imaginarymodel, "
  1532. "but app 'auth' doesn't provide model 'imaginarymodel'."
  1533. % dummy_function,
  1534. obj=dummy_function,
  1535. id="models.E022",
  1536. ),
  1537. Error(
  1538. "%r contains a lazy reference to fanciful_app.imaginarymodel, "
  1539. "but app 'fanciful_app' isn't installed." % dummy_function,
  1540. obj=dummy_function,
  1541. id="models.E022",
  1542. ),
  1543. Error(
  1544. "An instance of class 'DummyClass' was connected to "
  1545. "the 'post_init' signal with a lazy reference to the sender "
  1546. "'missing-app.model', but app 'missing-app' isn't installed.",
  1547. hint=None,
  1548. obj="invalid_models_tests.test_models",
  1549. id="signals.E001",
  1550. ),
  1551. Error(
  1552. "Bound method 'DummyClass.dummy_method' was connected to the "
  1553. "'post_init' signal with a lazy reference to the sender "
  1554. "'missing-app.model', but app 'missing-app' isn't installed.",
  1555. hint=None,
  1556. obj="invalid_models_tests.test_models",
  1557. id="signals.E001",
  1558. ),
  1559. Error(
  1560. "The field invalid_models_tests.DummyModel.author was declared "
  1561. "with a lazy reference to 'invalid_models_tests.author', but app "
  1562. "'invalid_models_tests' isn't installed.",
  1563. hint=None,
  1564. obj=DummyModel.author.field,
  1565. id="fields.E307",
  1566. ),
  1567. Error(
  1568. "The function 'dummy_function' was connected to the 'post_init' "
  1569. "signal with a lazy reference to the sender "
  1570. "'missing-app.model', but app 'missing-app' isn't installed.",
  1571. hint=None,
  1572. obj="invalid_models_tests.test_models",
  1573. id="signals.E001",
  1574. ),
  1575. ],
  1576. )
  1577. class MultipleAutoFieldsTests(TestCase):
  1578. def test_multiple_autofields(self):
  1579. msg = (
  1580. "Model invalid_models_tests.MultipleAutoFields can't have more "
  1581. "than one auto-generated field."
  1582. )
  1583. with self.assertRaisesMessage(ValueError, msg):
  1584. class MultipleAutoFields(models.Model):
  1585. auto1 = models.AutoField(primary_key=True)
  1586. auto2 = models.AutoField(primary_key=True)
  1587. @isolate_apps("invalid_models_tests")
  1588. class JSONFieldTests(TestCase):
  1589. @skipUnlessDBFeature("supports_json_field")
  1590. def test_ordering_pointing_to_json_field_value(self):
  1591. class Model(models.Model):
  1592. field = models.JSONField()
  1593. class Meta:
  1594. ordering = ["field__value"]
  1595. self.assertEqual(Model.check(databases=self.databases), [])
  1596. def test_check_jsonfield(self):
  1597. class Model(models.Model):
  1598. field = models.JSONField()
  1599. error = Error(
  1600. "%s does not support JSONFields." % connection.display_name,
  1601. obj=Model,
  1602. id="fields.E180",
  1603. )
  1604. expected = [] if connection.features.supports_json_field else [error]
  1605. self.assertEqual(Model.check(databases=self.databases), expected)
  1606. def test_check_jsonfield_required_db_features(self):
  1607. class Model(models.Model):
  1608. field = models.JSONField()
  1609. class Meta:
  1610. required_db_features = {"supports_json_field"}
  1611. self.assertEqual(Model.check(databases=self.databases), [])
  1612. @isolate_apps("invalid_models_tests")
  1613. class ConstraintsTests(TestCase):
  1614. def test_check_constraints(self):
  1615. class Model(models.Model):
  1616. age = models.IntegerField()
  1617. class Meta:
  1618. constraints = [
  1619. models.CheckConstraint(check=models.Q(age__gte=18), name="is_adult")
  1620. ]
  1621. errors = Model.check(databases=self.databases)
  1622. warn = Warning(
  1623. "%s does not support check constraints." % connection.display_name,
  1624. hint=(
  1625. "A constraint won't be created. Silence this warning if you "
  1626. "don't care about it."
  1627. ),
  1628. obj=Model,
  1629. id="models.W027",
  1630. )
  1631. expected = (
  1632. [] if connection.features.supports_table_check_constraints else [warn]
  1633. )
  1634. self.assertCountEqual(errors, expected)
  1635. def test_check_constraints_required_db_features(self):
  1636. class Model(models.Model):
  1637. age = models.IntegerField()
  1638. class Meta:
  1639. required_db_features = {"supports_table_check_constraints"}
  1640. constraints = [
  1641. models.CheckConstraint(check=models.Q(age__gte=18), name="is_adult")
  1642. ]
  1643. self.assertEqual(Model.check(databases=self.databases), [])
  1644. def test_check_constraint_pointing_to_missing_field(self):
  1645. class Model(models.Model):
  1646. class Meta:
  1647. required_db_features = {"supports_table_check_constraints"}
  1648. constraints = [
  1649. models.CheckConstraint(
  1650. name="name",
  1651. check=models.Q(missing_field=2),
  1652. ),
  1653. ]
  1654. self.assertEqual(
  1655. Model.check(databases=self.databases),
  1656. [
  1657. Error(
  1658. "'constraints' refers to the nonexistent field 'missing_field'.",
  1659. obj=Model,
  1660. id="models.E012",
  1661. ),
  1662. ]
  1663. if connection.features.supports_table_check_constraints
  1664. else [],
  1665. )
  1666. @skipUnlessDBFeature("supports_table_check_constraints")
  1667. def test_check_constraint_pointing_to_reverse_fk(self):
  1668. class Model(models.Model):
  1669. parent = models.ForeignKey("self", models.CASCADE, related_name="parents")
  1670. class Meta:
  1671. constraints = [
  1672. models.CheckConstraint(name="name", check=models.Q(parents=3)),
  1673. ]
  1674. self.assertEqual(
  1675. Model.check(databases=self.databases),
  1676. [
  1677. Error(
  1678. "'constraints' refers to the nonexistent field 'parents'.",
  1679. obj=Model,
  1680. id="models.E012",
  1681. ),
  1682. ],
  1683. )
  1684. @skipUnlessDBFeature("supports_table_check_constraints")
  1685. def test_check_constraint_pointing_to_reverse_o2o(self):
  1686. class Model(models.Model):
  1687. parent = models.OneToOneField("self", models.CASCADE)
  1688. class Meta:
  1689. constraints = [
  1690. models.CheckConstraint(
  1691. name="name",
  1692. check=models.Q(model__isnull=True),
  1693. ),
  1694. ]
  1695. self.assertEqual(
  1696. Model.check(databases=self.databases),
  1697. [
  1698. Error(
  1699. "'constraints' refers to the nonexistent field 'model'.",
  1700. obj=Model,
  1701. id="models.E012",
  1702. ),
  1703. ],
  1704. )
  1705. @skipUnlessDBFeature("supports_table_check_constraints")
  1706. def test_check_constraint_pointing_to_m2m_field(self):
  1707. class Model(models.Model):
  1708. m2m = models.ManyToManyField("self")
  1709. class Meta:
  1710. constraints = [
  1711. models.CheckConstraint(name="name", check=models.Q(m2m=2)),
  1712. ]
  1713. self.assertEqual(
  1714. Model.check(databases=self.databases),
  1715. [
  1716. Error(
  1717. "'constraints' refers to a ManyToManyField 'm2m', but "
  1718. "ManyToManyFields are not permitted in 'constraints'.",
  1719. obj=Model,
  1720. id="models.E013",
  1721. ),
  1722. ],
  1723. )
  1724. @skipUnlessDBFeature("supports_table_check_constraints")
  1725. def test_check_constraint_pointing_to_fk(self):
  1726. class Target(models.Model):
  1727. pass
  1728. class Model(models.Model):
  1729. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name="target_1")
  1730. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name="target_2")
  1731. class Meta:
  1732. constraints = [
  1733. models.CheckConstraint(
  1734. name="name",
  1735. check=models.Q(fk_1_id=2) | models.Q(fk_2=2),
  1736. ),
  1737. ]
  1738. self.assertEqual(Model.check(databases=self.databases), [])
  1739. @skipUnlessDBFeature("supports_table_check_constraints")
  1740. def test_check_constraint_pointing_to_pk(self):
  1741. class Model(models.Model):
  1742. age = models.SmallIntegerField()
  1743. class Meta:
  1744. constraints = [
  1745. models.CheckConstraint(
  1746. name="name",
  1747. check=models.Q(pk__gt=5) & models.Q(age__gt=models.F("pk")),
  1748. ),
  1749. ]
  1750. self.assertEqual(Model.check(databases=self.databases), [])
  1751. @skipUnlessDBFeature("supports_table_check_constraints")
  1752. def test_check_constraint_pointing_to_non_local_field(self):
  1753. class Parent(models.Model):
  1754. field1 = models.IntegerField()
  1755. class Child(Parent):
  1756. pass
  1757. class Meta:
  1758. constraints = [
  1759. models.CheckConstraint(name="name", check=models.Q(field1=1)),
  1760. ]
  1761. self.assertEqual(
  1762. Child.check(databases=self.databases),
  1763. [
  1764. Error(
  1765. "'constraints' refers to field 'field1' which is not local to "
  1766. "model 'Child'.",
  1767. hint="This issue may be caused by multi-table inheritance.",
  1768. obj=Child,
  1769. id="models.E016",
  1770. ),
  1771. ],
  1772. )
  1773. @skipUnlessDBFeature("supports_table_check_constraints")
  1774. def test_check_constraint_pointing_to_joined_fields(self):
  1775. class Model(models.Model):
  1776. name = models.CharField(max_length=10)
  1777. field1 = models.PositiveSmallIntegerField()
  1778. field2 = models.PositiveSmallIntegerField()
  1779. field3 = models.PositiveSmallIntegerField()
  1780. parent = models.ForeignKey("self", models.CASCADE)
  1781. previous = models.OneToOneField("self", models.CASCADE, related_name="next")
  1782. class Meta:
  1783. constraints = [
  1784. models.CheckConstraint(
  1785. name="name1",
  1786. check=models.Q(
  1787. field1__lt=models.F("parent__field1")
  1788. + models.F("parent__field2")
  1789. ),
  1790. ),
  1791. models.CheckConstraint(
  1792. name="name2", check=models.Q(name=Lower("parent__name"))
  1793. ),
  1794. models.CheckConstraint(
  1795. name="name3", check=models.Q(parent__field3=models.F("field1"))
  1796. ),
  1797. models.CheckConstraint(
  1798. name="name4",
  1799. check=models.Q(name=Lower("previous__name")),
  1800. ),
  1801. ]
  1802. joined_fields = [
  1803. "parent__field1",
  1804. "parent__field2",
  1805. "parent__field3",
  1806. "parent__name",
  1807. "previous__name",
  1808. ]
  1809. errors = Model.check(databases=self.databases)
  1810. expected_errors = [
  1811. Error(
  1812. "'constraints' refers to the joined field '%s'." % field_name,
  1813. obj=Model,
  1814. id="models.E041",
  1815. )
  1816. for field_name in joined_fields
  1817. ]
  1818. self.assertCountEqual(errors, expected_errors)
  1819. @skipUnlessDBFeature("supports_table_check_constraints")
  1820. def test_check_constraint_pointing_to_joined_fields_complex_check(self):
  1821. class Model(models.Model):
  1822. name = models.PositiveSmallIntegerField()
  1823. field1 = models.PositiveSmallIntegerField()
  1824. field2 = models.PositiveSmallIntegerField()
  1825. parent = models.ForeignKey("self", models.CASCADE)
  1826. class Meta:
  1827. constraints = [
  1828. models.CheckConstraint(
  1829. name="name",
  1830. check=models.Q(
  1831. (
  1832. models.Q(name="test")
  1833. & models.Q(field1__lt=models.F("parent__field1"))
  1834. )
  1835. | (
  1836. models.Q(name__startswith=Lower("parent__name"))
  1837. & models.Q(
  1838. field1__gte=(
  1839. models.F("parent__field1")
  1840. + models.F("parent__field2")
  1841. )
  1842. )
  1843. )
  1844. )
  1845. | (models.Q(name="test1")),
  1846. ),
  1847. ]
  1848. joined_fields = ["parent__field1", "parent__field2", "parent__name"]
  1849. errors = Model.check(databases=self.databases)
  1850. expected_errors = [
  1851. Error(
  1852. "'constraints' refers to the joined field '%s'." % field_name,
  1853. obj=Model,
  1854. id="models.E041",
  1855. )
  1856. for field_name in joined_fields
  1857. ]
  1858. self.assertCountEqual(errors, expected_errors)
  1859. def test_check_constraint_raw_sql_check(self):
  1860. class Model(models.Model):
  1861. class Meta:
  1862. required_db_features = {"supports_table_check_constraints"}
  1863. constraints = [
  1864. models.CheckConstraint(check=models.Q(id__gt=0), name="q_check"),
  1865. models.CheckConstraint(
  1866. check=models.ExpressionWrapper(
  1867. models.Q(price__gt=20),
  1868. output_field=models.BooleanField(),
  1869. ),
  1870. name="expression_wrapper_check",
  1871. ),
  1872. models.CheckConstraint(
  1873. check=models.expressions.RawSQL(
  1874. "id = 0",
  1875. params=(),
  1876. output_field=models.BooleanField(),
  1877. ),
  1878. name="raw_sql_check",
  1879. ),
  1880. models.CheckConstraint(
  1881. check=models.Q(
  1882. models.ExpressionWrapper(
  1883. models.Q(
  1884. models.expressions.RawSQL(
  1885. "id = 0",
  1886. params=(),
  1887. output_field=models.BooleanField(),
  1888. )
  1889. ),
  1890. output_field=models.BooleanField(),
  1891. )
  1892. ),
  1893. name="nested_raw_sql_check",
  1894. ),
  1895. ]
  1896. expected_warnings = (
  1897. [
  1898. Warning(
  1899. "Check constraint 'raw_sql_check' contains RawSQL() expression and "
  1900. "won't be validated during the model full_clean().",
  1901. hint="Silence this warning if you don't care about it.",
  1902. obj=Model,
  1903. id="models.W045",
  1904. ),
  1905. Warning(
  1906. "Check constraint 'nested_raw_sql_check' contains RawSQL() "
  1907. "expression and won't be validated during the model full_clean().",
  1908. hint="Silence this warning if you don't care about it.",
  1909. obj=Model,
  1910. id="models.W045",
  1911. ),
  1912. ]
  1913. if connection.features.supports_table_check_constraints
  1914. else []
  1915. )
  1916. self.assertEqual(Model.check(databases=self.databases), expected_warnings)
  1917. def test_unique_constraint_with_condition(self):
  1918. class Model(models.Model):
  1919. age = models.IntegerField()
  1920. class Meta:
  1921. constraints = [
  1922. models.UniqueConstraint(
  1923. fields=["age"],
  1924. name="unique_age_gte_100",
  1925. condition=models.Q(age__gte=100),
  1926. ),
  1927. ]
  1928. errors = Model.check(databases=self.databases)
  1929. expected = (
  1930. []
  1931. if connection.features.supports_partial_indexes
  1932. else [
  1933. Warning(
  1934. "%s does not support unique constraints with conditions."
  1935. % connection.display_name,
  1936. hint=(
  1937. "A constraint won't be created. Silence this warning if "
  1938. "you don't care about it."
  1939. ),
  1940. obj=Model,
  1941. id="models.W036",
  1942. ),
  1943. ]
  1944. )
  1945. self.assertEqual(errors, expected)
  1946. def test_unique_constraint_with_condition_required_db_features(self):
  1947. class Model(models.Model):
  1948. age = models.IntegerField()
  1949. class Meta:
  1950. required_db_features = {"supports_partial_indexes"}
  1951. constraints = [
  1952. models.UniqueConstraint(
  1953. fields=["age"],
  1954. name="unique_age_gte_100",
  1955. condition=models.Q(age__gte=100),
  1956. ),
  1957. ]
  1958. self.assertEqual(Model.check(databases=self.databases), [])
  1959. def test_unique_constraint_condition_pointing_to_missing_field(self):
  1960. class Model(models.Model):
  1961. age = models.SmallIntegerField()
  1962. class Meta:
  1963. required_db_features = {"supports_partial_indexes"}
  1964. constraints = [
  1965. models.UniqueConstraint(
  1966. name="name",
  1967. fields=["age"],
  1968. condition=models.Q(missing_field=2),
  1969. ),
  1970. ]
  1971. self.assertEqual(
  1972. Model.check(databases=self.databases),
  1973. [
  1974. Error(
  1975. "'constraints' refers to the nonexistent field 'missing_field'.",
  1976. obj=Model,
  1977. id="models.E012",
  1978. ),
  1979. ]
  1980. if connection.features.supports_partial_indexes
  1981. else [],
  1982. )
  1983. def test_unique_constraint_condition_pointing_to_joined_fields(self):
  1984. class Model(models.Model):
  1985. age = models.SmallIntegerField()
  1986. parent = models.ForeignKey("self", models.CASCADE)
  1987. class Meta:
  1988. required_db_features = {"supports_partial_indexes"}
  1989. constraints = [
  1990. models.UniqueConstraint(
  1991. name="name",
  1992. fields=["age"],
  1993. condition=models.Q(parent__age__lt=2),
  1994. ),
  1995. ]
  1996. self.assertEqual(
  1997. Model.check(databases=self.databases),
  1998. [
  1999. Error(
  2000. "'constraints' refers to the joined field 'parent__age__lt'.",
  2001. obj=Model,
  2002. id="models.E041",
  2003. )
  2004. ]
  2005. if connection.features.supports_partial_indexes
  2006. else [],
  2007. )
  2008. def test_unique_constraint_pointing_to_reverse_o2o(self):
  2009. class Model(models.Model):
  2010. parent = models.OneToOneField("self", models.CASCADE)
  2011. class Meta:
  2012. required_db_features = {"supports_partial_indexes"}
  2013. constraints = [
  2014. models.UniqueConstraint(
  2015. fields=["parent"],
  2016. name="name",
  2017. condition=models.Q(model__isnull=True),
  2018. ),
  2019. ]
  2020. self.assertEqual(
  2021. Model.check(databases=self.databases),
  2022. [
  2023. Error(
  2024. "'constraints' refers to the nonexistent field 'model'.",
  2025. obj=Model,
  2026. id="models.E012",
  2027. ),
  2028. ]
  2029. if connection.features.supports_partial_indexes
  2030. else [],
  2031. )
  2032. def test_deferrable_unique_constraint(self):
  2033. class Model(models.Model):
  2034. age = models.IntegerField()
  2035. class Meta:
  2036. constraints = [
  2037. models.UniqueConstraint(
  2038. fields=["age"],
  2039. name="unique_age_deferrable",
  2040. deferrable=models.Deferrable.DEFERRED,
  2041. ),
  2042. ]
  2043. errors = Model.check(databases=self.databases)
  2044. expected = (
  2045. []
  2046. if connection.features.supports_deferrable_unique_constraints
  2047. else [
  2048. Warning(
  2049. "%s does not support deferrable unique constraints."
  2050. % connection.display_name,
  2051. hint=(
  2052. "A constraint won't be created. Silence this warning if "
  2053. "you don't care about it."
  2054. ),
  2055. obj=Model,
  2056. id="models.W038",
  2057. ),
  2058. ]
  2059. )
  2060. self.assertEqual(errors, expected)
  2061. def test_deferrable_unique_constraint_required_db_features(self):
  2062. class Model(models.Model):
  2063. age = models.IntegerField()
  2064. class Meta:
  2065. required_db_features = {"supports_deferrable_unique_constraints"}
  2066. constraints = [
  2067. models.UniqueConstraint(
  2068. fields=["age"],
  2069. name="unique_age_deferrable",
  2070. deferrable=models.Deferrable.IMMEDIATE,
  2071. ),
  2072. ]
  2073. self.assertEqual(Model.check(databases=self.databases), [])
  2074. def test_unique_constraint_pointing_to_missing_field(self):
  2075. class Model(models.Model):
  2076. class Meta:
  2077. constraints = [
  2078. models.UniqueConstraint(fields=["missing_field"], name="name")
  2079. ]
  2080. self.assertEqual(
  2081. Model.check(databases=self.databases),
  2082. [
  2083. Error(
  2084. "'constraints' refers to the nonexistent field 'missing_field'.",
  2085. obj=Model,
  2086. id="models.E012",
  2087. ),
  2088. ],
  2089. )
  2090. def test_unique_constraint_pointing_to_m2m_field(self):
  2091. class Model(models.Model):
  2092. m2m = models.ManyToManyField("self")
  2093. class Meta:
  2094. constraints = [models.UniqueConstraint(fields=["m2m"], name="name")]
  2095. self.assertEqual(
  2096. Model.check(databases=self.databases),
  2097. [
  2098. Error(
  2099. "'constraints' refers to a ManyToManyField 'm2m', but "
  2100. "ManyToManyFields are not permitted in 'constraints'.",
  2101. obj=Model,
  2102. id="models.E013",
  2103. ),
  2104. ],
  2105. )
  2106. def test_unique_constraint_pointing_to_non_local_field(self):
  2107. class Parent(models.Model):
  2108. field1 = models.IntegerField()
  2109. class Child(Parent):
  2110. field2 = models.IntegerField()
  2111. class Meta:
  2112. constraints = [
  2113. models.UniqueConstraint(fields=["field2", "field1"], name="name"),
  2114. ]
  2115. self.assertEqual(
  2116. Child.check(databases=self.databases),
  2117. [
  2118. Error(
  2119. "'constraints' refers to field 'field1' which is not local to "
  2120. "model 'Child'.",
  2121. hint="This issue may be caused by multi-table inheritance.",
  2122. obj=Child,
  2123. id="models.E016",
  2124. ),
  2125. ],
  2126. )
  2127. def test_unique_constraint_pointing_to_fk(self):
  2128. class Target(models.Model):
  2129. pass
  2130. class Model(models.Model):
  2131. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name="target_1")
  2132. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name="target_2")
  2133. class Meta:
  2134. constraints = [
  2135. models.UniqueConstraint(fields=["fk_1_id", "fk_2"], name="name"),
  2136. ]
  2137. self.assertEqual(Model.check(databases=self.databases), [])
  2138. def test_unique_constraint_with_include(self):
  2139. class Model(models.Model):
  2140. age = models.IntegerField()
  2141. class Meta:
  2142. constraints = [
  2143. models.UniqueConstraint(
  2144. fields=["age"],
  2145. name="unique_age_include_id",
  2146. include=["id"],
  2147. ),
  2148. ]
  2149. errors = Model.check(databases=self.databases)
  2150. expected = (
  2151. []
  2152. if connection.features.supports_covering_indexes
  2153. else [
  2154. Warning(
  2155. "%s does not support unique constraints with non-key columns."
  2156. % connection.display_name,
  2157. hint=(
  2158. "A constraint won't be created. Silence this warning if "
  2159. "you don't care about it."
  2160. ),
  2161. obj=Model,
  2162. id="models.W039",
  2163. ),
  2164. ]
  2165. )
  2166. self.assertEqual(errors, expected)
  2167. def test_unique_constraint_with_include_required_db_features(self):
  2168. class Model(models.Model):
  2169. age = models.IntegerField()
  2170. class Meta:
  2171. required_db_features = {"supports_covering_indexes"}
  2172. constraints = [
  2173. models.UniqueConstraint(
  2174. fields=["age"],
  2175. name="unique_age_include_id",
  2176. include=["id"],
  2177. ),
  2178. ]
  2179. self.assertEqual(Model.check(databases=self.databases), [])
  2180. @skipUnlessDBFeature("supports_covering_indexes")
  2181. def test_unique_constraint_include_pointing_to_missing_field(self):
  2182. class Model(models.Model):
  2183. class Meta:
  2184. constraints = [
  2185. models.UniqueConstraint(
  2186. fields=["id"],
  2187. include=["missing_field"],
  2188. name="name",
  2189. ),
  2190. ]
  2191. self.assertEqual(
  2192. Model.check(databases=self.databases),
  2193. [
  2194. Error(
  2195. "'constraints' refers to the nonexistent field 'missing_field'.",
  2196. obj=Model,
  2197. id="models.E012",
  2198. ),
  2199. ],
  2200. )
  2201. @skipUnlessDBFeature("supports_covering_indexes")
  2202. def test_unique_constraint_include_pointing_to_m2m_field(self):
  2203. class Model(models.Model):
  2204. m2m = models.ManyToManyField("self")
  2205. class Meta:
  2206. constraints = [
  2207. models.UniqueConstraint(
  2208. fields=["id"],
  2209. include=["m2m"],
  2210. name="name",
  2211. ),
  2212. ]
  2213. self.assertEqual(
  2214. Model.check(databases=self.databases),
  2215. [
  2216. Error(
  2217. "'constraints' refers to a ManyToManyField 'm2m', but "
  2218. "ManyToManyFields are not permitted in 'constraints'.",
  2219. obj=Model,
  2220. id="models.E013",
  2221. ),
  2222. ],
  2223. )
  2224. @skipUnlessDBFeature("supports_covering_indexes")
  2225. def test_unique_constraint_include_pointing_to_non_local_field(self):
  2226. class Parent(models.Model):
  2227. field1 = models.IntegerField()
  2228. class Child(Parent):
  2229. field2 = models.IntegerField()
  2230. class Meta:
  2231. constraints = [
  2232. models.UniqueConstraint(
  2233. fields=["field2"],
  2234. include=["field1"],
  2235. name="name",
  2236. ),
  2237. ]
  2238. self.assertEqual(
  2239. Child.check(databases=self.databases),
  2240. [
  2241. Error(
  2242. "'constraints' refers to field 'field1' which is not local to "
  2243. "model 'Child'.",
  2244. hint="This issue may be caused by multi-table inheritance.",
  2245. obj=Child,
  2246. id="models.E016",
  2247. ),
  2248. ],
  2249. )
  2250. @skipUnlessDBFeature("supports_covering_indexes")
  2251. def test_unique_constraint_include_pointing_to_fk(self):
  2252. class Target(models.Model):
  2253. pass
  2254. class Model(models.Model):
  2255. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name="target_1")
  2256. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name="target_2")
  2257. class Meta:
  2258. constraints = [
  2259. models.UniqueConstraint(
  2260. fields=["id"],
  2261. include=["fk_1_id", "fk_2"],
  2262. name="name",
  2263. ),
  2264. ]
  2265. self.assertEqual(Model.check(databases=self.databases), [])
  2266. def test_func_unique_constraint(self):
  2267. class Model(models.Model):
  2268. name = models.CharField(max_length=10)
  2269. class Meta:
  2270. constraints = [
  2271. models.UniqueConstraint(Lower("name"), name="lower_name_uq"),
  2272. ]
  2273. warn = Warning(
  2274. "%s does not support unique constraints on expressions."
  2275. % connection.display_name,
  2276. hint=(
  2277. "A constraint won't be created. Silence this warning if you "
  2278. "don't care about it."
  2279. ),
  2280. obj=Model,
  2281. id="models.W044",
  2282. )
  2283. expected = [] if connection.features.supports_expression_indexes else [warn]
  2284. self.assertEqual(Model.check(databases=self.databases), expected)
  2285. def test_func_unique_constraint_required_db_features(self):
  2286. class Model(models.Model):
  2287. name = models.CharField(max_length=10)
  2288. class Meta:
  2289. constraints = [
  2290. models.UniqueConstraint(Lower("name"), name="lower_name_unq"),
  2291. ]
  2292. required_db_features = {"supports_expression_indexes"}
  2293. self.assertEqual(Model.check(databases=self.databases), [])
  2294. @skipUnlessDBFeature("supports_expression_indexes")
  2295. def test_func_unique_constraint_expression_custom_lookup(self):
  2296. class Model(models.Model):
  2297. height = models.IntegerField()
  2298. weight = models.IntegerField()
  2299. class Meta:
  2300. constraints = [
  2301. models.UniqueConstraint(
  2302. models.F("height")
  2303. / (models.F("weight__abs") + models.Value(5)),
  2304. name="name",
  2305. ),
  2306. ]
  2307. with register_lookup(models.IntegerField, Abs):
  2308. self.assertEqual(Model.check(databases=self.databases), [])
  2309. @skipUnlessDBFeature("supports_expression_indexes")
  2310. def test_func_unique_constraint_pointing_to_missing_field(self):
  2311. class Model(models.Model):
  2312. class Meta:
  2313. constraints = [
  2314. models.UniqueConstraint(Lower("missing_field").desc(), name="name"),
  2315. ]
  2316. self.assertEqual(
  2317. Model.check(databases=self.databases),
  2318. [
  2319. Error(
  2320. "'constraints' refers to the nonexistent field 'missing_field'.",
  2321. obj=Model,
  2322. id="models.E012",
  2323. ),
  2324. ],
  2325. )
  2326. @skipUnlessDBFeature("supports_expression_indexes")
  2327. def test_func_unique_constraint_pointing_to_missing_field_nested(self):
  2328. class Model(models.Model):
  2329. class Meta:
  2330. constraints = [
  2331. models.UniqueConstraint(Abs(Round("missing_field")), name="name"),
  2332. ]
  2333. self.assertEqual(
  2334. Model.check(databases=self.databases),
  2335. [
  2336. Error(
  2337. "'constraints' refers to the nonexistent field 'missing_field'.",
  2338. obj=Model,
  2339. id="models.E012",
  2340. ),
  2341. ],
  2342. )
  2343. @skipUnlessDBFeature("supports_expression_indexes")
  2344. def test_func_unique_constraint_pointing_to_m2m_field(self):
  2345. class Model(models.Model):
  2346. m2m = models.ManyToManyField("self")
  2347. class Meta:
  2348. constraints = [models.UniqueConstraint(Lower("m2m"), name="name")]
  2349. self.assertEqual(
  2350. Model.check(databases=self.databases),
  2351. [
  2352. Error(
  2353. "'constraints' refers to a ManyToManyField 'm2m', but "
  2354. "ManyToManyFields are not permitted in 'constraints'.",
  2355. obj=Model,
  2356. id="models.E013",
  2357. ),
  2358. ],
  2359. )
  2360. @skipUnlessDBFeature("supports_expression_indexes")
  2361. def test_func_unique_constraint_pointing_to_non_local_field(self):
  2362. class Foo(models.Model):
  2363. field1 = models.CharField(max_length=15)
  2364. class Bar(Foo):
  2365. class Meta:
  2366. constraints = [models.UniqueConstraint(Lower("field1"), name="name")]
  2367. self.assertEqual(
  2368. Bar.check(databases=self.databases),
  2369. [
  2370. Error(
  2371. "'constraints' refers to field 'field1' which is not local to "
  2372. "model 'Bar'.",
  2373. hint="This issue may be caused by multi-table inheritance.",
  2374. obj=Bar,
  2375. id="models.E016",
  2376. ),
  2377. ],
  2378. )
  2379. @skipUnlessDBFeature("supports_expression_indexes")
  2380. def test_func_unique_constraint_pointing_to_fk(self):
  2381. class Foo(models.Model):
  2382. id = models.CharField(primary_key=True, max_length=255)
  2383. class Bar(models.Model):
  2384. foo_1 = models.ForeignKey(Foo, models.CASCADE, related_name="bar_1")
  2385. foo_2 = models.ForeignKey(Foo, models.CASCADE, related_name="bar_2")
  2386. class Meta:
  2387. constraints = [
  2388. models.UniqueConstraint(
  2389. Lower("foo_1_id"),
  2390. Lower("foo_2"),
  2391. name="name",
  2392. ),
  2393. ]
  2394. self.assertEqual(Bar.check(databases=self.databases), [])