2
0

test_models.py 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825
  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 Lower
  6. from django.db.models.signals import post_init
  7. from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
  8. from django.test.utils import isolate_apps, override_settings, register_lookup
  9. class EmptyRouter:
  10. pass
  11. def get_max_column_name_length():
  12. allowed_len = None
  13. db_alias = None
  14. for db in ('default', 'other'):
  15. connection = connections[db]
  16. max_name_length = connection.ops.max_name_length()
  17. if max_name_length is not None and not connection.features.truncates_names:
  18. if allowed_len is None or max_name_length < allowed_len:
  19. allowed_len = max_name_length
  20. db_alias = db
  21. return (allowed_len, db_alias)
  22. @isolate_apps('invalid_models_tests')
  23. class IndexTogetherTests(SimpleTestCase):
  24. def test_non_iterable(self):
  25. class Model(models.Model):
  26. class Meta:
  27. index_together = 42
  28. self.assertEqual(Model.check(), [
  29. Error(
  30. "'index_together' must be a list or tuple.",
  31. obj=Model,
  32. id='models.E008',
  33. ),
  34. ])
  35. def test_non_list(self):
  36. class Model(models.Model):
  37. class Meta:
  38. index_together = 'not-a-list'
  39. self.assertEqual(Model.check(), [
  40. Error(
  41. "'index_together' must be a list or tuple.",
  42. obj=Model,
  43. id='models.E008',
  44. ),
  45. ])
  46. def test_list_containing_non_iterable(self):
  47. class Model(models.Model):
  48. class Meta:
  49. index_together = [('a', 'b'), 42]
  50. self.assertEqual(Model.check(), [
  51. Error(
  52. "All 'index_together' elements must be lists or tuples.",
  53. obj=Model,
  54. id='models.E009',
  55. ),
  56. ])
  57. def test_pointing_to_missing_field(self):
  58. class Model(models.Model):
  59. class Meta:
  60. index_together = [['missing_field']]
  61. self.assertEqual(Model.check(), [
  62. Error(
  63. "'index_together' refers to the nonexistent field 'missing_field'.",
  64. obj=Model,
  65. id='models.E012',
  66. ),
  67. ])
  68. def test_pointing_to_non_local_field(self):
  69. class Foo(models.Model):
  70. field1 = models.IntegerField()
  71. class Bar(Foo):
  72. field2 = models.IntegerField()
  73. class Meta:
  74. index_together = [['field2', 'field1']]
  75. self.assertEqual(Bar.check(), [
  76. Error(
  77. "'index_together' refers to field 'field1' which is not "
  78. "local to model 'Bar'.",
  79. hint='This issue may be caused by multi-table inheritance.',
  80. obj=Bar,
  81. id='models.E016',
  82. ),
  83. ])
  84. def test_pointing_to_m2m_field(self):
  85. class Model(models.Model):
  86. m2m = models.ManyToManyField('self')
  87. class Meta:
  88. index_together = [['m2m']]
  89. self.assertEqual(Model.check(), [
  90. Error(
  91. "'index_together' refers to a ManyToManyField 'm2m', but "
  92. "ManyToManyFields are not permitted in 'index_together'.",
  93. obj=Model,
  94. id='models.E013',
  95. ),
  96. ])
  97. def test_pointing_to_fk(self):
  98. class Foo(models.Model):
  99. pass
  100. class Bar(models.Model):
  101. foo_1 = models.ForeignKey(Foo, on_delete=models.CASCADE, related_name='bar_1')
  102. foo_2 = models.ForeignKey(Foo, on_delete=models.CASCADE, related_name='bar_2')
  103. class Meta:
  104. index_together = [['foo_1_id', 'foo_2']]
  105. self.assertEqual(Bar.check(), [])
  106. # unique_together tests are very similar to index_together tests.
  107. @isolate_apps('invalid_models_tests')
  108. class UniqueTogetherTests(SimpleTestCase):
  109. def test_non_iterable(self):
  110. class Model(models.Model):
  111. class Meta:
  112. unique_together = 42
  113. self.assertEqual(Model.check(), [
  114. Error(
  115. "'unique_together' must be a list or tuple.",
  116. obj=Model,
  117. id='models.E010',
  118. ),
  119. ])
  120. def test_list_containing_non_iterable(self):
  121. class Model(models.Model):
  122. one = models.IntegerField()
  123. two = models.IntegerField()
  124. class Meta:
  125. unique_together = [('a', 'b'), 42]
  126. self.assertEqual(Model.check(), [
  127. Error(
  128. "All 'unique_together' elements must be lists or tuples.",
  129. obj=Model,
  130. id='models.E011',
  131. ),
  132. ])
  133. def test_non_list(self):
  134. class Model(models.Model):
  135. class Meta:
  136. unique_together = 'not-a-list'
  137. self.assertEqual(Model.check(), [
  138. Error(
  139. "'unique_together' must be a list or tuple.",
  140. obj=Model,
  141. id='models.E010',
  142. ),
  143. ])
  144. def test_valid_model(self):
  145. class Model(models.Model):
  146. one = models.IntegerField()
  147. two = models.IntegerField()
  148. class Meta:
  149. # unique_together can be a simple tuple
  150. unique_together = ('one', 'two')
  151. self.assertEqual(Model.check(), [])
  152. def test_pointing_to_missing_field(self):
  153. class Model(models.Model):
  154. class Meta:
  155. unique_together = [['missing_field']]
  156. self.assertEqual(Model.check(), [
  157. Error(
  158. "'unique_together' refers to the nonexistent field 'missing_field'.",
  159. obj=Model,
  160. id='models.E012',
  161. ),
  162. ])
  163. def test_pointing_to_m2m(self):
  164. class Model(models.Model):
  165. m2m = models.ManyToManyField('self')
  166. class Meta:
  167. unique_together = [['m2m']]
  168. self.assertEqual(Model.check(), [
  169. Error(
  170. "'unique_together' refers to a ManyToManyField 'm2m', but "
  171. "ManyToManyFields are not permitted in 'unique_together'.",
  172. obj=Model,
  173. id='models.E013',
  174. ),
  175. ])
  176. def test_pointing_to_fk(self):
  177. class Foo(models.Model):
  178. pass
  179. class Bar(models.Model):
  180. foo_1 = models.ForeignKey(Foo, on_delete=models.CASCADE, related_name='bar_1')
  181. foo_2 = models.ForeignKey(Foo, on_delete=models.CASCADE, related_name='bar_2')
  182. class Meta:
  183. unique_together = [['foo_1_id', 'foo_2']]
  184. self.assertEqual(Bar.check(), [])
  185. @isolate_apps('invalid_models_tests')
  186. class IndexesTests(TestCase):
  187. def test_pointing_to_missing_field(self):
  188. class Model(models.Model):
  189. class Meta:
  190. indexes = [models.Index(fields=['missing_field'], name='name')]
  191. self.assertEqual(Model.check(), [
  192. Error(
  193. "'indexes' refers to the nonexistent field 'missing_field'.",
  194. obj=Model,
  195. id='models.E012',
  196. ),
  197. ])
  198. def test_pointing_to_m2m_field(self):
  199. class Model(models.Model):
  200. m2m = models.ManyToManyField('self')
  201. class Meta:
  202. indexes = [models.Index(fields=['m2m'], name='name')]
  203. self.assertEqual(Model.check(), [
  204. Error(
  205. "'indexes' refers to a ManyToManyField 'm2m', but "
  206. "ManyToManyFields are not permitted in 'indexes'.",
  207. obj=Model,
  208. id='models.E013',
  209. ),
  210. ])
  211. def test_pointing_to_non_local_field(self):
  212. class Foo(models.Model):
  213. field1 = models.IntegerField()
  214. class Bar(Foo):
  215. field2 = models.IntegerField()
  216. class Meta:
  217. indexes = [models.Index(fields=['field2', 'field1'], name='name')]
  218. self.assertEqual(Bar.check(), [
  219. Error(
  220. "'indexes' refers to field 'field1' which is not local to "
  221. "model 'Bar'.",
  222. hint='This issue may be caused by multi-table inheritance.',
  223. obj=Bar,
  224. id='models.E016',
  225. ),
  226. ])
  227. def test_pointing_to_fk(self):
  228. class Foo(models.Model):
  229. pass
  230. class Bar(models.Model):
  231. foo_1 = models.ForeignKey(Foo, on_delete=models.CASCADE, related_name='bar_1')
  232. foo_2 = models.ForeignKey(Foo, on_delete=models.CASCADE, related_name='bar_2')
  233. class Meta:
  234. indexes = [models.Index(fields=['foo_1_id', 'foo_2'], name='index_name')]
  235. self.assertEqual(Bar.check(), [])
  236. def test_name_constraints(self):
  237. class Model(models.Model):
  238. class Meta:
  239. indexes = [
  240. models.Index(fields=['id'], name='_index_name'),
  241. models.Index(fields=['id'], name='5index_name'),
  242. ]
  243. self.assertEqual(Model.check(), [
  244. Error(
  245. "The index name '%sindex_name' cannot start with an "
  246. "underscore or a number." % prefix,
  247. obj=Model,
  248. id='models.E033',
  249. ) for prefix in ('_', '5')
  250. ])
  251. def test_max_name_length(self):
  252. index_name = 'x' * 31
  253. class Model(models.Model):
  254. class Meta:
  255. indexes = [models.Index(fields=['id'], name=index_name)]
  256. self.assertEqual(Model.check(), [
  257. Error(
  258. "The index name '%s' cannot be longer than 30 characters."
  259. % index_name,
  260. obj=Model,
  261. id='models.E034',
  262. ),
  263. ])
  264. def test_index_with_condition(self):
  265. class Model(models.Model):
  266. age = models.IntegerField()
  267. class Meta:
  268. indexes = [
  269. models.Index(
  270. fields=['age'],
  271. name='index_age_gte_10',
  272. condition=models.Q(age__gte=10),
  273. ),
  274. ]
  275. errors = Model.check(databases=self.databases)
  276. expected = [] if connection.features.supports_partial_indexes else [
  277. Warning(
  278. '%s does not support indexes with conditions.'
  279. % connection.display_name,
  280. hint=(
  281. "Conditions will be ignored. Silence this warning if you "
  282. "don't care about it."
  283. ),
  284. obj=Model,
  285. id='models.W037',
  286. )
  287. ]
  288. self.assertEqual(errors, expected)
  289. def test_index_with_condition_required_db_features(self):
  290. class Model(models.Model):
  291. age = models.IntegerField()
  292. class Meta:
  293. required_db_features = {'supports_partial_indexes'}
  294. indexes = [
  295. models.Index(
  296. fields=['age'],
  297. name='index_age_gte_10',
  298. condition=models.Q(age__gte=10),
  299. ),
  300. ]
  301. self.assertEqual(Model.check(databases=self.databases), [])
  302. def test_index_with_include(self):
  303. class Model(models.Model):
  304. age = models.IntegerField()
  305. class Meta:
  306. indexes = [
  307. models.Index(
  308. fields=['age'],
  309. name='index_age_include_id',
  310. include=['id'],
  311. ),
  312. ]
  313. errors = Model.check(databases=self.databases)
  314. expected = [] if connection.features.supports_covering_indexes else [
  315. Warning(
  316. '%s does not support indexes with non-key columns.'
  317. % connection.display_name,
  318. hint=(
  319. "Non-key columns will be ignored. Silence this warning if "
  320. "you don't care about it."
  321. ),
  322. obj=Model,
  323. id='models.W040',
  324. )
  325. ]
  326. self.assertEqual(errors, expected)
  327. def test_index_with_include_required_db_features(self):
  328. class Model(models.Model):
  329. age = models.IntegerField()
  330. class Meta:
  331. required_db_features = {'supports_covering_indexes'}
  332. indexes = [
  333. models.Index(
  334. fields=['age'],
  335. name='index_age_include_id',
  336. include=['id'],
  337. ),
  338. ]
  339. self.assertEqual(Model.check(databases=self.databases), [])
  340. @skipUnlessDBFeature('supports_covering_indexes')
  341. def test_index_include_pointing_to_missing_field(self):
  342. class Model(models.Model):
  343. class Meta:
  344. indexes = [
  345. models.Index(fields=['id'], include=['missing_field'], name='name'),
  346. ]
  347. self.assertEqual(Model.check(databases=self.databases), [
  348. Error(
  349. "'indexes' refers to the nonexistent field 'missing_field'.",
  350. obj=Model,
  351. id='models.E012',
  352. ),
  353. ])
  354. @skipUnlessDBFeature('supports_covering_indexes')
  355. def test_index_include_pointing_to_m2m_field(self):
  356. class Model(models.Model):
  357. m2m = models.ManyToManyField('self')
  358. class Meta:
  359. indexes = [models.Index(fields=['id'], include=['m2m'], name='name')]
  360. self.assertEqual(Model.check(databases=self.databases), [
  361. Error(
  362. "'indexes' refers to a ManyToManyField 'm2m', but "
  363. "ManyToManyFields are not permitted in 'indexes'.",
  364. obj=Model,
  365. id='models.E013',
  366. ),
  367. ])
  368. @skipUnlessDBFeature('supports_covering_indexes')
  369. def test_index_include_pointing_to_non_local_field(self):
  370. class Parent(models.Model):
  371. field1 = models.IntegerField()
  372. class Child(Parent):
  373. field2 = models.IntegerField()
  374. class Meta:
  375. indexes = [
  376. models.Index(fields=['field2'], include=['field1'], name='name'),
  377. ]
  378. self.assertEqual(Child.check(databases=self.databases), [
  379. Error(
  380. "'indexes' refers to field 'field1' which is not local to "
  381. "model 'Child'.",
  382. hint='This issue may be caused by multi-table inheritance.',
  383. obj=Child,
  384. id='models.E016',
  385. ),
  386. ])
  387. @skipUnlessDBFeature('supports_covering_indexes')
  388. def test_index_include_pointing_to_fk(self):
  389. class Target(models.Model):
  390. pass
  391. class Model(models.Model):
  392. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name='target_1')
  393. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name='target_2')
  394. class Meta:
  395. constraints = [
  396. models.Index(
  397. fields=['id'],
  398. include=['fk_1_id', 'fk_2'],
  399. name='name',
  400. ),
  401. ]
  402. self.assertEqual(Model.check(databases=self.databases), [])
  403. @isolate_apps('invalid_models_tests')
  404. class FieldNamesTests(TestCase):
  405. databases = {'default', 'other'}
  406. def test_ending_with_underscore(self):
  407. class Model(models.Model):
  408. field_ = models.CharField(max_length=10)
  409. m2m_ = models.ManyToManyField('self')
  410. self.assertEqual(Model.check(), [
  411. Error(
  412. 'Field names must not end with an underscore.',
  413. obj=Model._meta.get_field('field_'),
  414. id='fields.E001',
  415. ),
  416. Error(
  417. 'Field names must not end with an underscore.',
  418. obj=Model._meta.get_field('m2m_'),
  419. id='fields.E001',
  420. ),
  421. ])
  422. max_column_name_length, column_limit_db_alias = get_max_column_name_length()
  423. @unittest.skipIf(max_column_name_length is None, "The database doesn't have a column name length limit.")
  424. def test_M2M_long_column_name(self):
  425. """
  426. #13711 -- Model check for long M2M column names when database has
  427. column name length limits.
  428. """
  429. # A model with very long name which will be used to set relations to.
  430. class VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(models.Model):
  431. title = models.CharField(max_length=11)
  432. # Main model for which checks will be performed.
  433. class ModelWithLongField(models.Model):
  434. m2m_field = models.ManyToManyField(
  435. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  436. related_name='rn1',
  437. )
  438. m2m_field2 = models.ManyToManyField(
  439. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  440. related_name='rn2', through='m2msimple',
  441. )
  442. m2m_field3 = models.ManyToManyField(
  443. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  444. related_name='rn3',
  445. through='m2mcomplex',
  446. )
  447. fk = models.ForeignKey(
  448. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  449. models.CASCADE,
  450. related_name='rn4',
  451. )
  452. # Models used for setting `through` in M2M field.
  453. class m2msimple(models.Model):
  454. id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
  455. class m2mcomplex(models.Model):
  456. id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
  457. long_field_name = 'a' * (self.max_column_name_length + 1)
  458. models.ForeignKey(
  459. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  460. models.CASCADE,
  461. ).contribute_to_class(m2msimple, long_field_name)
  462. models.ForeignKey(
  463. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  464. models.CASCADE,
  465. db_column=long_field_name
  466. ).contribute_to_class(m2mcomplex, long_field_name)
  467. errors = ModelWithLongField.check(databases=('default', 'other'))
  468. # First error because of M2M field set on the model with long name.
  469. m2m_long_name = "verylongmodelnamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz_id"
  470. if self.max_column_name_length > len(m2m_long_name):
  471. # Some databases support names longer than the test name.
  472. expected = []
  473. else:
  474. expected = [
  475. Error(
  476. 'Autogenerated column name too long for M2M field "%s". '
  477. 'Maximum length is "%s" for database "%s".'
  478. % (m2m_long_name, self.max_column_name_length, self.column_limit_db_alias),
  479. hint="Use 'through' to create a separate model for "
  480. "M2M and then set column_name using 'db_column'.",
  481. obj=ModelWithLongField,
  482. id='models.E019',
  483. )
  484. ]
  485. # Second error because the FK specified in the `through` model
  486. # `m2msimple` has auto-generated name longer than allowed.
  487. # There will be no check errors in the other M2M because it
  488. # specifies db_column for the FK in `through` model even if the actual
  489. # name is longer than the limits of the database.
  490. expected.append(
  491. Error(
  492. 'Autogenerated column name too long for M2M field "%s_id". '
  493. 'Maximum length is "%s" for database "%s".'
  494. % (long_field_name, self.max_column_name_length, self.column_limit_db_alias),
  495. hint="Use 'through' to create a separate model for "
  496. "M2M and then set column_name using 'db_column'.",
  497. obj=ModelWithLongField,
  498. id='models.E019',
  499. )
  500. )
  501. self.assertEqual(errors, expected)
  502. # Check for long column names is called only for specified database
  503. # aliases.
  504. self.assertEqual(ModelWithLongField.check(databases=None), [])
  505. @unittest.skipIf(max_column_name_length is None, "The database doesn't have a column name length limit.")
  506. def test_local_field_long_column_name(self):
  507. """
  508. #13711 -- Model check for long column names
  509. when database does not support long names.
  510. """
  511. class ModelWithLongField(models.Model):
  512. title = models.CharField(max_length=11)
  513. long_field_name = 'a' * (self.max_column_name_length + 1)
  514. long_field_name2 = 'b' * (self.max_column_name_length + 1)
  515. models.CharField(max_length=11).contribute_to_class(ModelWithLongField, long_field_name)
  516. models.CharField(max_length=11, db_column='vlmn').contribute_to_class(ModelWithLongField, long_field_name2)
  517. self.assertEqual(ModelWithLongField.check(databases=('default', 'other')), [
  518. Error(
  519. 'Autogenerated column name too long for field "%s". '
  520. 'Maximum length is "%s" for database "%s".'
  521. % (long_field_name, self.max_column_name_length, self.column_limit_db_alias),
  522. hint="Set the column name manually using 'db_column'.",
  523. obj=ModelWithLongField,
  524. id='models.E018',
  525. )
  526. ])
  527. # Check for long column names is called only for specified database
  528. # aliases.
  529. self.assertEqual(ModelWithLongField.check(databases=None), [])
  530. def test_including_separator(self):
  531. class Model(models.Model):
  532. some__field = models.IntegerField()
  533. self.assertEqual(Model.check(), [
  534. Error(
  535. 'Field names must not contain "__".',
  536. obj=Model._meta.get_field('some__field'),
  537. id='fields.E002',
  538. )
  539. ])
  540. def test_pk(self):
  541. class Model(models.Model):
  542. pk = models.IntegerField()
  543. self.assertEqual(Model.check(), [
  544. Error(
  545. "'pk' is a reserved word that cannot be used as a field name.",
  546. obj=Model._meta.get_field('pk'),
  547. id='fields.E003',
  548. )
  549. ])
  550. def test_db_column_clash(self):
  551. class Model(models.Model):
  552. foo = models.IntegerField()
  553. bar = models.IntegerField(db_column='foo')
  554. self.assertEqual(Model.check(), [
  555. Error(
  556. "Field 'bar' has column name 'foo' that is used by "
  557. "another field.",
  558. hint="Specify a 'db_column' for the field.",
  559. obj=Model,
  560. id='models.E007',
  561. )
  562. ])
  563. @isolate_apps('invalid_models_tests')
  564. class ShadowingFieldsTests(SimpleTestCase):
  565. def test_field_name_clash_with_child_accessor(self):
  566. class Parent(models.Model):
  567. pass
  568. class Child(Parent):
  569. child = models.CharField(max_length=100)
  570. self.assertEqual(Child.check(), [
  571. Error(
  572. "The field 'child' clashes with the field "
  573. "'child' from model 'invalid_models_tests.parent'.",
  574. obj=Child._meta.get_field('child'),
  575. id='models.E006',
  576. )
  577. ])
  578. def test_multiinheritance_clash(self):
  579. class Mother(models.Model):
  580. clash = models.IntegerField()
  581. class Father(models.Model):
  582. clash = models.IntegerField()
  583. class Child(Mother, Father):
  584. # Here we have two clashed: id (automatic field) and clash, because
  585. # both parents define these fields.
  586. pass
  587. self.assertEqual(Child.check(), [
  588. Error(
  589. "The field 'id' from parent model "
  590. "'invalid_models_tests.mother' clashes with the field 'id' "
  591. "from parent model 'invalid_models_tests.father'.",
  592. obj=Child,
  593. id='models.E005',
  594. ),
  595. Error(
  596. "The field 'clash' from parent model "
  597. "'invalid_models_tests.mother' clashes with the field 'clash' "
  598. "from parent model 'invalid_models_tests.father'.",
  599. obj=Child,
  600. id='models.E005',
  601. )
  602. ])
  603. def test_inheritance_clash(self):
  604. class Parent(models.Model):
  605. f_id = models.IntegerField()
  606. class Target(models.Model):
  607. # This field doesn't result in a clash.
  608. f_id = models.IntegerField()
  609. class Child(Parent):
  610. # This field clashes with parent "f_id" field.
  611. f = models.ForeignKey(Target, models.CASCADE)
  612. self.assertEqual(Child.check(), [
  613. Error(
  614. "The field 'f' clashes with the field 'f_id' "
  615. "from model 'invalid_models_tests.parent'.",
  616. obj=Child._meta.get_field('f'),
  617. id='models.E006',
  618. )
  619. ])
  620. def test_multigeneration_inheritance(self):
  621. class GrandParent(models.Model):
  622. clash = models.IntegerField()
  623. class Parent(GrandParent):
  624. pass
  625. class Child(Parent):
  626. pass
  627. class GrandChild(Child):
  628. clash = models.IntegerField()
  629. self.assertEqual(GrandChild.check(), [
  630. Error(
  631. "The field 'clash' clashes with the field 'clash' "
  632. "from model 'invalid_models_tests.grandparent'.",
  633. obj=GrandChild._meta.get_field('clash'),
  634. id='models.E006',
  635. )
  636. ])
  637. def test_id_clash(self):
  638. class Target(models.Model):
  639. pass
  640. class Model(models.Model):
  641. fk = models.ForeignKey(Target, models.CASCADE)
  642. fk_id = models.IntegerField()
  643. self.assertEqual(Model.check(), [
  644. Error(
  645. "The field 'fk_id' clashes with the field 'fk' from model "
  646. "'invalid_models_tests.model'.",
  647. obj=Model._meta.get_field('fk_id'),
  648. id='models.E006',
  649. )
  650. ])
  651. @isolate_apps('invalid_models_tests')
  652. class OtherModelTests(SimpleTestCase):
  653. def test_unique_primary_key(self):
  654. invalid_id = models.IntegerField(primary_key=False)
  655. class Model(models.Model):
  656. id = invalid_id
  657. self.assertEqual(Model.check(), [
  658. Error(
  659. "'id' can only be used as a field name if the field also sets "
  660. "'primary_key=True'.",
  661. obj=Model,
  662. id='models.E004',
  663. ),
  664. ])
  665. def test_ordering_non_iterable(self):
  666. class Model(models.Model):
  667. class Meta:
  668. ordering = 'missing_field'
  669. self.assertEqual(Model.check(), [
  670. Error(
  671. "'ordering' must be a tuple or list "
  672. "(even if you want to order by only one field).",
  673. obj=Model,
  674. id='models.E014',
  675. ),
  676. ])
  677. def test_just_ordering_no_errors(self):
  678. class Model(models.Model):
  679. order = models.PositiveIntegerField()
  680. class Meta:
  681. ordering = ['order']
  682. self.assertEqual(Model.check(), [])
  683. def test_just_order_with_respect_to_no_errors(self):
  684. class Question(models.Model):
  685. pass
  686. class Answer(models.Model):
  687. question = models.ForeignKey(Question, models.CASCADE)
  688. class Meta:
  689. order_with_respect_to = 'question'
  690. self.assertEqual(Answer.check(), [])
  691. def test_ordering_with_order_with_respect_to(self):
  692. class Question(models.Model):
  693. pass
  694. class Answer(models.Model):
  695. question = models.ForeignKey(Question, models.CASCADE)
  696. order = models.IntegerField()
  697. class Meta:
  698. order_with_respect_to = 'question'
  699. ordering = ['order']
  700. self.assertEqual(Answer.check(), [
  701. Error(
  702. "'ordering' and 'order_with_respect_to' cannot be used together.",
  703. obj=Answer,
  704. id='models.E021',
  705. ),
  706. ])
  707. def test_non_valid(self):
  708. class RelationModel(models.Model):
  709. pass
  710. class Model(models.Model):
  711. relation = models.ManyToManyField(RelationModel)
  712. class Meta:
  713. ordering = ['relation']
  714. self.assertEqual(Model.check(), [
  715. Error(
  716. "'ordering' refers to the nonexistent field, related field, "
  717. "or lookup 'relation'.",
  718. obj=Model,
  719. id='models.E015',
  720. ),
  721. ])
  722. def test_ordering_pointing_to_missing_field(self):
  723. class Model(models.Model):
  724. class Meta:
  725. ordering = ('missing_field',)
  726. self.assertEqual(Model.check(), [
  727. Error(
  728. "'ordering' refers to the nonexistent field, related field, "
  729. "or lookup 'missing_field'.",
  730. obj=Model,
  731. id='models.E015',
  732. )
  733. ])
  734. def test_ordering_pointing_to_missing_foreignkey_field(self):
  735. class Model(models.Model):
  736. missing_fk_field = models.IntegerField()
  737. class Meta:
  738. ordering = ('missing_fk_field_id',)
  739. self.assertEqual(Model.check(), [
  740. Error(
  741. "'ordering' refers to the nonexistent field, related field, "
  742. "or lookup 'missing_fk_field_id'.",
  743. obj=Model,
  744. id='models.E015',
  745. )
  746. ])
  747. def test_ordering_pointing_to_missing_related_field(self):
  748. class Model(models.Model):
  749. test = models.IntegerField()
  750. class Meta:
  751. ordering = ('missing_related__id',)
  752. self.assertEqual(Model.check(), [
  753. Error(
  754. "'ordering' refers to the nonexistent field, related field, "
  755. "or lookup 'missing_related__id'.",
  756. obj=Model,
  757. id='models.E015',
  758. )
  759. ])
  760. def test_ordering_pointing_to_missing_related_model_field(self):
  761. class Parent(models.Model):
  762. pass
  763. class Child(models.Model):
  764. parent = models.ForeignKey(Parent, models.CASCADE)
  765. class Meta:
  766. ordering = ('parent__missing_field',)
  767. self.assertEqual(Child.check(), [
  768. Error(
  769. "'ordering' refers to the nonexistent field, related field, "
  770. "or lookup 'parent__missing_field'.",
  771. obj=Child,
  772. id='models.E015',
  773. )
  774. ])
  775. def test_ordering_pointing_to_non_related_field(self):
  776. class Child(models.Model):
  777. parent = models.IntegerField()
  778. class Meta:
  779. ordering = ('parent__missing_field',)
  780. self.assertEqual(Child.check(), [
  781. Error(
  782. "'ordering' refers to the nonexistent field, related field, "
  783. "or lookup 'parent__missing_field'.",
  784. obj=Child,
  785. id='models.E015',
  786. )
  787. ])
  788. def test_ordering_pointing_to_two_related_model_field(self):
  789. class Parent2(models.Model):
  790. pass
  791. class Parent1(models.Model):
  792. parent2 = models.ForeignKey(Parent2, models.CASCADE)
  793. class Child(models.Model):
  794. parent1 = models.ForeignKey(Parent1, models.CASCADE)
  795. class Meta:
  796. ordering = ('parent1__parent2__missing_field',)
  797. self.assertEqual(Child.check(), [
  798. Error(
  799. "'ordering' refers to the nonexistent field, related field, "
  800. "or lookup 'parent1__parent2__missing_field'.",
  801. obj=Child,
  802. id='models.E015',
  803. )
  804. ])
  805. def test_ordering_pointing_multiple_times_to_model_fields(self):
  806. class Parent(models.Model):
  807. field1 = models.CharField(max_length=100)
  808. field2 = models.CharField(max_length=100)
  809. class Child(models.Model):
  810. parent = models.ForeignKey(Parent, models.CASCADE)
  811. class Meta:
  812. ordering = ('parent__field1__field2',)
  813. self.assertEqual(Child.check(), [
  814. Error(
  815. "'ordering' refers to the nonexistent field, related field, "
  816. "or lookup 'parent__field1__field2'.",
  817. obj=Child,
  818. id='models.E015',
  819. )
  820. ])
  821. def test_ordering_allows_registered_lookups(self):
  822. class Model(models.Model):
  823. test = models.CharField(max_length=100)
  824. class Meta:
  825. ordering = ('test__lower',)
  826. with register_lookup(models.CharField, Lower):
  827. self.assertEqual(Model.check(), [])
  828. def test_ordering_pointing_to_lookup_not_transform(self):
  829. class Model(models.Model):
  830. test = models.CharField(max_length=100)
  831. class Meta:
  832. ordering = ('test__isnull',)
  833. self.assertEqual(Model.check(), [])
  834. def test_ordering_pointing_to_related_model_pk(self):
  835. class Parent(models.Model):
  836. pass
  837. class Child(models.Model):
  838. parent = models.ForeignKey(Parent, models.CASCADE)
  839. class Meta:
  840. ordering = ('parent__pk',)
  841. self.assertEqual(Child.check(), [])
  842. def test_ordering_pointing_to_foreignkey_field(self):
  843. class Parent(models.Model):
  844. pass
  845. class Child(models.Model):
  846. parent = models.ForeignKey(Parent, models.CASCADE)
  847. class Meta:
  848. ordering = ('parent_id',)
  849. self.assertFalse(Child.check())
  850. def test_name_beginning_with_underscore(self):
  851. class _Model(models.Model):
  852. pass
  853. self.assertEqual(_Model.check(), [
  854. Error(
  855. "The model name '_Model' cannot start or end with an underscore "
  856. "as it collides with the query lookup syntax.",
  857. obj=_Model,
  858. id='models.E023',
  859. )
  860. ])
  861. def test_name_ending_with_underscore(self):
  862. class Model_(models.Model):
  863. pass
  864. self.assertEqual(Model_.check(), [
  865. Error(
  866. "The model name 'Model_' cannot start or end with an underscore "
  867. "as it collides with the query lookup syntax.",
  868. obj=Model_,
  869. id='models.E023',
  870. )
  871. ])
  872. def test_name_contains_double_underscores(self):
  873. class Test__Model(models.Model):
  874. pass
  875. self.assertEqual(Test__Model.check(), [
  876. Error(
  877. "The model name 'Test__Model' cannot contain double underscores "
  878. "as it collides with the query lookup syntax.",
  879. obj=Test__Model,
  880. id='models.E024',
  881. )
  882. ])
  883. def test_property_and_related_field_accessor_clash(self):
  884. class Model(models.Model):
  885. fk = models.ForeignKey('self', models.CASCADE)
  886. @property
  887. def fk_id(self):
  888. pass
  889. self.assertEqual(Model.check(), [
  890. Error(
  891. "The property 'fk_id' clashes with a related field accessor.",
  892. obj=Model,
  893. id='models.E025',
  894. )
  895. ])
  896. def test_single_primary_key(self):
  897. class Model(models.Model):
  898. foo = models.IntegerField(primary_key=True)
  899. bar = models.IntegerField(primary_key=True)
  900. self.assertEqual(Model.check(), [
  901. Error(
  902. "The model cannot have more than one field with 'primary_key=True'.",
  903. obj=Model,
  904. id='models.E026',
  905. )
  906. ])
  907. @override_settings(TEST_SWAPPED_MODEL_BAD_VALUE='not-a-model')
  908. def test_swappable_missing_app_name(self):
  909. class Model(models.Model):
  910. class Meta:
  911. swappable = 'TEST_SWAPPED_MODEL_BAD_VALUE'
  912. self.assertEqual(Model.check(), [
  913. Error(
  914. "'TEST_SWAPPED_MODEL_BAD_VALUE' is not of the form 'app_label.app_name'.",
  915. id='models.E001',
  916. ),
  917. ])
  918. @override_settings(TEST_SWAPPED_MODEL_BAD_MODEL='not_an_app.Target')
  919. def test_swappable_missing_app(self):
  920. class Model(models.Model):
  921. class Meta:
  922. swappable = 'TEST_SWAPPED_MODEL_BAD_MODEL'
  923. self.assertEqual(Model.check(), [
  924. Error(
  925. "'TEST_SWAPPED_MODEL_BAD_MODEL' references 'not_an_app.Target', "
  926. 'which has not been installed, or is abstract.',
  927. id='models.E002',
  928. ),
  929. ])
  930. def test_two_m2m_through_same_relationship(self):
  931. class Person(models.Model):
  932. pass
  933. class Group(models.Model):
  934. primary = models.ManyToManyField(Person, through='Membership', related_name='primary')
  935. secondary = models.ManyToManyField(Person, through='Membership', related_name='secondary')
  936. class Membership(models.Model):
  937. person = models.ForeignKey(Person, models.CASCADE)
  938. group = models.ForeignKey(Group, models.CASCADE)
  939. self.assertEqual(Group.check(), [
  940. Error(
  941. "The model has two identical many-to-many relations through "
  942. "the intermediate model 'invalid_models_tests.Membership'.",
  943. obj=Group,
  944. id='models.E003',
  945. )
  946. ])
  947. def test_two_m2m_through_same_model_with_different_through_fields(self):
  948. class Country(models.Model):
  949. pass
  950. class ShippingMethod(models.Model):
  951. to_countries = models.ManyToManyField(
  952. Country, through='ShippingMethodPrice',
  953. through_fields=('method', 'to_country'),
  954. )
  955. from_countries = models.ManyToManyField(
  956. Country, through='ShippingMethodPrice',
  957. through_fields=('method', 'from_country'),
  958. related_name='+',
  959. )
  960. class ShippingMethodPrice(models.Model):
  961. method = models.ForeignKey(ShippingMethod, models.CASCADE)
  962. to_country = models.ForeignKey(Country, models.CASCADE)
  963. from_country = models.ForeignKey(Country, models.CASCADE)
  964. self.assertEqual(ShippingMethod.check(), [])
  965. def test_onetoone_with_parent_model(self):
  966. class Place(models.Model):
  967. pass
  968. class ParkingLot(Place):
  969. other_place = models.OneToOneField(Place, models.CASCADE, related_name='other_parking')
  970. self.assertEqual(ParkingLot.check(), [])
  971. def test_onetoone_with_explicit_parent_link_parent_model(self):
  972. class Place(models.Model):
  973. pass
  974. class ParkingLot(Place):
  975. place = models.OneToOneField(Place, models.CASCADE, parent_link=True, primary_key=True)
  976. other_place = models.OneToOneField(Place, models.CASCADE, related_name='other_parking')
  977. self.assertEqual(ParkingLot.check(), [])
  978. def test_m2m_table_name_clash(self):
  979. class Foo(models.Model):
  980. bar = models.ManyToManyField('Bar', db_table='myapp_bar')
  981. class Meta:
  982. db_table = 'myapp_foo'
  983. class Bar(models.Model):
  984. class Meta:
  985. db_table = 'myapp_bar'
  986. self.assertEqual(Foo.check(), [
  987. Error(
  988. "The field's intermediary table 'myapp_bar' clashes with the "
  989. "table name of 'invalid_models_tests.Bar'.",
  990. obj=Foo._meta.get_field('bar'),
  991. id='fields.E340',
  992. )
  993. ])
  994. @override_settings(DATABASE_ROUTERS=['invalid_models_tests.test_models.EmptyRouter'])
  995. def test_m2m_table_name_clash_database_routers_installed(self):
  996. class Foo(models.Model):
  997. bar = models.ManyToManyField('Bar', db_table='myapp_bar')
  998. class Meta:
  999. db_table = 'myapp_foo'
  1000. class Bar(models.Model):
  1001. class Meta:
  1002. db_table = 'myapp_bar'
  1003. self.assertEqual(Foo.check(), [
  1004. Warning(
  1005. "The field's intermediary table 'myapp_bar' clashes with the "
  1006. "table name of 'invalid_models_tests.Bar'.",
  1007. obj=Foo._meta.get_field('bar'),
  1008. hint=(
  1009. "You have configured settings.DATABASE_ROUTERS. Verify "
  1010. "that the table of 'invalid_models_tests.Bar' is "
  1011. "correctly routed to a separate database."
  1012. ),
  1013. id='fields.W344',
  1014. ),
  1015. ])
  1016. def test_m2m_field_table_name_clash(self):
  1017. class Foo(models.Model):
  1018. pass
  1019. class Bar(models.Model):
  1020. foos = models.ManyToManyField(Foo, db_table='clash')
  1021. class Baz(models.Model):
  1022. foos = models.ManyToManyField(Foo, db_table='clash')
  1023. self.assertEqual(Bar.check() + Baz.check(), [
  1024. Error(
  1025. "The field's intermediary table 'clash' clashes with the "
  1026. "table name of 'invalid_models_tests.Baz.foos'.",
  1027. obj=Bar._meta.get_field('foos'),
  1028. id='fields.E340',
  1029. ),
  1030. Error(
  1031. "The field's intermediary table 'clash' clashes with the "
  1032. "table name of 'invalid_models_tests.Bar.foos'.",
  1033. obj=Baz._meta.get_field('foos'),
  1034. id='fields.E340',
  1035. )
  1036. ])
  1037. @override_settings(DATABASE_ROUTERS=['invalid_models_tests.test_models.EmptyRouter'])
  1038. def test_m2m_field_table_name_clash_database_routers_installed(self):
  1039. class Foo(models.Model):
  1040. pass
  1041. class Bar(models.Model):
  1042. foos = models.ManyToManyField(Foo, db_table='clash')
  1043. class Baz(models.Model):
  1044. foos = models.ManyToManyField(Foo, db_table='clash')
  1045. self.assertEqual(Bar.check() + Baz.check(), [
  1046. Warning(
  1047. "The field's intermediary table 'clash' clashes with the "
  1048. "table name of 'invalid_models_tests.%s.foos'."
  1049. % clashing_model,
  1050. obj=model_cls._meta.get_field('foos'),
  1051. hint=(
  1052. "You have configured settings.DATABASE_ROUTERS. Verify "
  1053. "that the table of 'invalid_models_tests.%s.foos' is "
  1054. "correctly routed to a separate database." % clashing_model
  1055. ),
  1056. id='fields.W344',
  1057. ) for model_cls, clashing_model in [(Bar, 'Baz'), (Baz, 'Bar')]
  1058. ])
  1059. def test_m2m_autogenerated_table_name_clash(self):
  1060. class Foo(models.Model):
  1061. class Meta:
  1062. db_table = 'bar_foos'
  1063. class Bar(models.Model):
  1064. # The autogenerated `db_table` will be bar_foos.
  1065. foos = models.ManyToManyField(Foo)
  1066. class Meta:
  1067. db_table = 'bar'
  1068. self.assertEqual(Bar.check(), [
  1069. Error(
  1070. "The field's intermediary table 'bar_foos' clashes with the "
  1071. "table name of 'invalid_models_tests.Foo'.",
  1072. obj=Bar._meta.get_field('foos'),
  1073. id='fields.E340',
  1074. )
  1075. ])
  1076. @override_settings(DATABASE_ROUTERS=['invalid_models_tests.test_models.EmptyRouter'])
  1077. def test_m2m_autogenerated_table_name_clash_database_routers_installed(self):
  1078. class Foo(models.Model):
  1079. class Meta:
  1080. db_table = 'bar_foos'
  1081. class Bar(models.Model):
  1082. # The autogenerated db_table is bar_foos.
  1083. foos = models.ManyToManyField(Foo)
  1084. class Meta:
  1085. db_table = 'bar'
  1086. self.assertEqual(Bar.check(), [
  1087. Warning(
  1088. "The field's intermediary table 'bar_foos' clashes with the "
  1089. "table name of 'invalid_models_tests.Foo'.",
  1090. obj=Bar._meta.get_field('foos'),
  1091. hint=(
  1092. "You have configured settings.DATABASE_ROUTERS. Verify "
  1093. "that the table of 'invalid_models_tests.Foo' is "
  1094. "correctly routed to a separate database."
  1095. ),
  1096. id='fields.W344',
  1097. ),
  1098. ])
  1099. def test_m2m_unmanaged_shadow_models_not_checked(self):
  1100. class A1(models.Model):
  1101. pass
  1102. class C1(models.Model):
  1103. mm_a = models.ManyToManyField(A1, db_table='d1')
  1104. # Unmanaged models that shadow the above models. Reused table names
  1105. # shouldn't be flagged by any checks.
  1106. class A2(models.Model):
  1107. class Meta:
  1108. managed = False
  1109. class C2(models.Model):
  1110. mm_a = models.ManyToManyField(A2, through='Intermediate')
  1111. class Meta:
  1112. managed = False
  1113. class Intermediate(models.Model):
  1114. a2 = models.ForeignKey(A2, models.CASCADE, db_column='a1_id')
  1115. c2 = models.ForeignKey(C2, models.CASCADE, db_column='c1_id')
  1116. class Meta:
  1117. db_table = 'd1'
  1118. managed = False
  1119. self.assertEqual(C1.check(), [])
  1120. self.assertEqual(C2.check(), [])
  1121. def test_m2m_to_concrete_and_proxy_allowed(self):
  1122. class A(models.Model):
  1123. pass
  1124. class Through(models.Model):
  1125. a = models.ForeignKey('A', models.CASCADE)
  1126. c = models.ForeignKey('C', models.CASCADE)
  1127. class ThroughProxy(Through):
  1128. class Meta:
  1129. proxy = True
  1130. class C(models.Model):
  1131. mm_a = models.ManyToManyField(A, through=Through)
  1132. mm_aproxy = models.ManyToManyField(A, through=ThroughProxy, related_name='proxied_m2m')
  1133. self.assertEqual(C.check(), [])
  1134. @isolate_apps('django.contrib.auth', kwarg_name='apps')
  1135. def test_lazy_reference_checks(self, apps):
  1136. class DummyModel(models.Model):
  1137. author = models.ForeignKey('Author', models.CASCADE)
  1138. class Meta:
  1139. app_label = 'invalid_models_tests'
  1140. class DummyClass:
  1141. def __call__(self, **kwargs):
  1142. pass
  1143. def dummy_method(self):
  1144. pass
  1145. def dummy_function(*args, **kwargs):
  1146. pass
  1147. apps.lazy_model_operation(dummy_function, ('auth', 'imaginarymodel'))
  1148. apps.lazy_model_operation(dummy_function, ('fanciful_app', 'imaginarymodel'))
  1149. post_init.connect(dummy_function, sender='missing-app.Model', apps=apps)
  1150. post_init.connect(DummyClass(), sender='missing-app.Model', apps=apps)
  1151. post_init.connect(DummyClass().dummy_method, sender='missing-app.Model', apps=apps)
  1152. self.assertEqual(_check_lazy_references(apps), [
  1153. Error(
  1154. "%r contains a lazy reference to auth.imaginarymodel, "
  1155. "but app 'auth' doesn't provide model 'imaginarymodel'." % dummy_function,
  1156. obj=dummy_function,
  1157. id='models.E022',
  1158. ),
  1159. Error(
  1160. "%r contains a lazy reference to fanciful_app.imaginarymodel, "
  1161. "but app 'fanciful_app' isn't installed." % dummy_function,
  1162. obj=dummy_function,
  1163. id='models.E022',
  1164. ),
  1165. Error(
  1166. "An instance of class 'DummyClass' was connected to "
  1167. "the 'post_init' signal with a lazy reference to the sender "
  1168. "'missing-app.model', but app 'missing-app' isn't installed.",
  1169. hint=None,
  1170. obj='invalid_models_tests.test_models',
  1171. id='signals.E001',
  1172. ),
  1173. Error(
  1174. "Bound method 'DummyClass.dummy_method' was connected to the "
  1175. "'post_init' signal with a lazy reference to the sender "
  1176. "'missing-app.model', but app 'missing-app' isn't installed.",
  1177. hint=None,
  1178. obj='invalid_models_tests.test_models',
  1179. id='signals.E001',
  1180. ),
  1181. Error(
  1182. "The field invalid_models_tests.DummyModel.author was declared "
  1183. "with a lazy reference to 'invalid_models_tests.author', but app "
  1184. "'invalid_models_tests' isn't installed.",
  1185. hint=None,
  1186. obj=DummyModel.author.field,
  1187. id='fields.E307',
  1188. ),
  1189. Error(
  1190. "The function 'dummy_function' was connected to the 'post_init' "
  1191. "signal with a lazy reference to the sender "
  1192. "'missing-app.model', but app 'missing-app' isn't installed.",
  1193. hint=None,
  1194. obj='invalid_models_tests.test_models',
  1195. id='signals.E001',
  1196. ),
  1197. ])
  1198. @isolate_apps('invalid_models_tests')
  1199. class JSONFieldTests(TestCase):
  1200. @skipUnlessDBFeature('supports_json_field')
  1201. def test_ordering_pointing_to_json_field_value(self):
  1202. class Model(models.Model):
  1203. field = models.JSONField()
  1204. class Meta:
  1205. ordering = ['field__value']
  1206. self.assertEqual(Model.check(databases=self.databases), [])
  1207. def test_check_jsonfield(self):
  1208. class Model(models.Model):
  1209. field = models.JSONField()
  1210. error = Error(
  1211. '%s does not support JSONFields.' % connection.display_name,
  1212. obj=Model,
  1213. id='fields.E180',
  1214. )
  1215. expected = [] if connection.features.supports_json_field else [error]
  1216. self.assertEqual(Model.check(databases=self.databases), expected)
  1217. def test_check_jsonfield_required_db_features(self):
  1218. class Model(models.Model):
  1219. field = models.JSONField()
  1220. class Meta:
  1221. required_db_features = {'supports_json_field'}
  1222. self.assertEqual(Model.check(databases=self.databases), [])
  1223. @isolate_apps('invalid_models_tests')
  1224. class ConstraintsTests(TestCase):
  1225. def test_check_constraints(self):
  1226. class Model(models.Model):
  1227. age = models.IntegerField()
  1228. class Meta:
  1229. constraints = [models.CheckConstraint(check=models.Q(age__gte=18), name='is_adult')]
  1230. errors = Model.check(databases=self.databases)
  1231. warn = Warning(
  1232. '%s does not support check constraints.' % connection.display_name,
  1233. hint=(
  1234. "A constraint won't be created. Silence this warning if you "
  1235. "don't care about it."
  1236. ),
  1237. obj=Model,
  1238. id='models.W027',
  1239. )
  1240. expected = [] if connection.features.supports_table_check_constraints else [warn]
  1241. self.assertCountEqual(errors, expected)
  1242. def test_check_constraints_required_db_features(self):
  1243. class Model(models.Model):
  1244. age = models.IntegerField()
  1245. class Meta:
  1246. required_db_features = {'supports_table_check_constraints'}
  1247. constraints = [models.CheckConstraint(check=models.Q(age__gte=18), name='is_adult')]
  1248. self.assertEqual(Model.check(databases=self.databases), [])
  1249. def test_unique_constraint_with_condition(self):
  1250. class Model(models.Model):
  1251. age = models.IntegerField()
  1252. class Meta:
  1253. constraints = [
  1254. models.UniqueConstraint(
  1255. fields=['age'],
  1256. name='unique_age_gte_100',
  1257. condition=models.Q(age__gte=100),
  1258. ),
  1259. ]
  1260. errors = Model.check(databases=self.databases)
  1261. expected = [] if connection.features.supports_partial_indexes else [
  1262. Warning(
  1263. '%s does not support unique constraints with conditions.'
  1264. % connection.display_name,
  1265. hint=(
  1266. "A constraint won't be created. Silence this warning if "
  1267. "you don't care about it."
  1268. ),
  1269. obj=Model,
  1270. id='models.W036',
  1271. ),
  1272. ]
  1273. self.assertEqual(errors, expected)
  1274. def test_unique_constraint_with_condition_required_db_features(self):
  1275. class Model(models.Model):
  1276. age = models.IntegerField()
  1277. class Meta:
  1278. required_db_features = {'supports_partial_indexes'}
  1279. constraints = [
  1280. models.UniqueConstraint(
  1281. fields=['age'],
  1282. name='unique_age_gte_100',
  1283. condition=models.Q(age__gte=100),
  1284. ),
  1285. ]
  1286. self.assertEqual(Model.check(databases=self.databases), [])
  1287. def test_deferrable_unique_constraint(self):
  1288. class Model(models.Model):
  1289. age = models.IntegerField()
  1290. class Meta:
  1291. constraints = [
  1292. models.UniqueConstraint(
  1293. fields=['age'],
  1294. name='unique_age_deferrable',
  1295. deferrable=models.Deferrable.DEFERRED,
  1296. ),
  1297. ]
  1298. errors = Model.check(databases=self.databases)
  1299. expected = [] if connection.features.supports_deferrable_unique_constraints else [
  1300. Warning(
  1301. '%s does not support deferrable unique constraints.'
  1302. % connection.display_name,
  1303. hint=(
  1304. "A constraint won't be created. Silence this warning if "
  1305. "you don't care about it."
  1306. ),
  1307. obj=Model,
  1308. id='models.W038',
  1309. ),
  1310. ]
  1311. self.assertEqual(errors, expected)
  1312. def test_deferrable_unique_constraint_required_db_features(self):
  1313. class Model(models.Model):
  1314. age = models.IntegerField()
  1315. class Meta:
  1316. required_db_features = {'supports_deferrable_unique_constraints'}
  1317. constraints = [
  1318. models.UniqueConstraint(
  1319. fields=['age'],
  1320. name='unique_age_deferrable',
  1321. deferrable=models.Deferrable.IMMEDIATE,
  1322. ),
  1323. ]
  1324. self.assertEqual(Model.check(databases=self.databases), [])
  1325. def test_unique_constraint_pointing_to_missing_field(self):
  1326. class Model(models.Model):
  1327. class Meta:
  1328. constraints = [models.UniqueConstraint(fields=['missing_field'], name='name')]
  1329. self.assertEqual(Model.check(databases=self.databases), [
  1330. Error(
  1331. "'constraints' refers to the nonexistent field "
  1332. "'missing_field'.",
  1333. obj=Model,
  1334. id='models.E012',
  1335. ),
  1336. ])
  1337. def test_unique_constraint_pointing_to_m2m_field(self):
  1338. class Model(models.Model):
  1339. m2m = models.ManyToManyField('self')
  1340. class Meta:
  1341. constraints = [models.UniqueConstraint(fields=['m2m'], name='name')]
  1342. self.assertEqual(Model.check(databases=self.databases), [
  1343. Error(
  1344. "'constraints' refers to a ManyToManyField 'm2m', but "
  1345. "ManyToManyFields are not permitted in 'constraints'.",
  1346. obj=Model,
  1347. id='models.E013',
  1348. ),
  1349. ])
  1350. def test_unique_constraint_pointing_to_non_local_field(self):
  1351. class Parent(models.Model):
  1352. field1 = models.IntegerField()
  1353. class Child(Parent):
  1354. field2 = models.IntegerField()
  1355. class Meta:
  1356. constraints = [
  1357. models.UniqueConstraint(fields=['field2', 'field1'], name='name'),
  1358. ]
  1359. self.assertEqual(Child.check(databases=self.databases), [
  1360. Error(
  1361. "'constraints' refers to field 'field1' which is not local to "
  1362. "model 'Child'.",
  1363. hint='This issue may be caused by multi-table inheritance.',
  1364. obj=Child,
  1365. id='models.E016',
  1366. ),
  1367. ])
  1368. def test_unique_constraint_pointing_to_fk(self):
  1369. class Target(models.Model):
  1370. pass
  1371. class Model(models.Model):
  1372. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name='target_1')
  1373. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name='target_2')
  1374. class Meta:
  1375. constraints = [
  1376. models.UniqueConstraint(fields=['fk_1_id', 'fk_2'], name='name'),
  1377. ]
  1378. self.assertEqual(Model.check(databases=self.databases), [])
  1379. def test_unique_constraint_with_include(self):
  1380. class Model(models.Model):
  1381. age = models.IntegerField()
  1382. class Meta:
  1383. constraints = [
  1384. models.UniqueConstraint(
  1385. fields=['age'],
  1386. name='unique_age_include_id',
  1387. include=['id'],
  1388. ),
  1389. ]
  1390. errors = Model.check(databases=self.databases)
  1391. expected = [] if connection.features.supports_covering_indexes else [
  1392. Warning(
  1393. '%s does not support unique constraints with non-key columns.'
  1394. % connection.display_name,
  1395. hint=(
  1396. "A constraint won't be created. Silence this warning if "
  1397. "you don't care about it."
  1398. ),
  1399. obj=Model,
  1400. id='models.W039',
  1401. ),
  1402. ]
  1403. self.assertEqual(errors, expected)
  1404. def test_unique_constraint_with_include_required_db_features(self):
  1405. class Model(models.Model):
  1406. age = models.IntegerField()
  1407. class Meta:
  1408. required_db_features = {'supports_covering_indexes'}
  1409. constraints = [
  1410. models.UniqueConstraint(
  1411. fields=['age'],
  1412. name='unique_age_include_id',
  1413. include=['id'],
  1414. ),
  1415. ]
  1416. self.assertEqual(Model.check(databases=self.databases), [])
  1417. @skipUnlessDBFeature('supports_covering_indexes')
  1418. def test_unique_constraint_include_pointing_to_missing_field(self):
  1419. class Model(models.Model):
  1420. class Meta:
  1421. constraints = [
  1422. models.UniqueConstraint(
  1423. fields=['id'],
  1424. include=['missing_field'],
  1425. name='name',
  1426. ),
  1427. ]
  1428. self.assertEqual(Model.check(databases=self.databases), [
  1429. Error(
  1430. "'constraints' refers to the nonexistent field "
  1431. "'missing_field'.",
  1432. obj=Model,
  1433. id='models.E012',
  1434. ),
  1435. ])
  1436. @skipUnlessDBFeature('supports_covering_indexes')
  1437. def test_unique_constraint_include_pointing_to_m2m_field(self):
  1438. class Model(models.Model):
  1439. m2m = models.ManyToManyField('self')
  1440. class Meta:
  1441. constraints = [
  1442. models.UniqueConstraint(
  1443. fields=['id'],
  1444. include=['m2m'],
  1445. name='name',
  1446. ),
  1447. ]
  1448. self.assertEqual(Model.check(databases=self.databases), [
  1449. Error(
  1450. "'constraints' refers to a ManyToManyField 'm2m', but "
  1451. "ManyToManyFields are not permitted in 'constraints'.",
  1452. obj=Model,
  1453. id='models.E013',
  1454. ),
  1455. ])
  1456. @skipUnlessDBFeature('supports_covering_indexes')
  1457. def test_unique_constraint_include_pointing_to_non_local_field(self):
  1458. class Parent(models.Model):
  1459. field1 = models.IntegerField()
  1460. class Child(Parent):
  1461. field2 = models.IntegerField()
  1462. class Meta:
  1463. constraints = [
  1464. models.UniqueConstraint(
  1465. fields=['field2'],
  1466. include=['field1'],
  1467. name='name',
  1468. ),
  1469. ]
  1470. self.assertEqual(Child.check(databases=self.databases), [
  1471. Error(
  1472. "'constraints' refers to field 'field1' which is not local to "
  1473. "model 'Child'.",
  1474. hint='This issue may be caused by multi-table inheritance.',
  1475. obj=Child,
  1476. id='models.E016',
  1477. ),
  1478. ])
  1479. @skipUnlessDBFeature('supports_covering_indexes')
  1480. def test_unique_constraint_include_pointing_to_fk(self):
  1481. class Target(models.Model):
  1482. pass
  1483. class Model(models.Model):
  1484. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name='target_1')
  1485. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name='target_2')
  1486. class Meta:
  1487. constraints = [
  1488. models.UniqueConstraint(
  1489. fields=['id'],
  1490. include=['fk_1_id', 'fk_2'],
  1491. name='name',
  1492. ),
  1493. ]
  1494. self.assertEqual(Model.check(databases=self.databases), [])