test_models.py 80 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373
  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, 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. def test_func_index(self):
  404. class Model(models.Model):
  405. name = models.CharField(max_length=10)
  406. class Meta:
  407. indexes = [models.Index(Lower('name'), name='index_lower_name')]
  408. warn = Warning(
  409. '%s does not support indexes on expressions.'
  410. % connection.display_name,
  411. hint=(
  412. "An index won't be created. Silence this warning if you don't "
  413. "care about it."
  414. ),
  415. obj=Model,
  416. id='models.W043',
  417. )
  418. expected = [] if connection.features.supports_expression_indexes else [warn]
  419. self.assertEqual(Model.check(databases=self.databases), expected)
  420. def test_func_index_required_db_features(self):
  421. class Model(models.Model):
  422. name = models.CharField(max_length=10)
  423. class Meta:
  424. indexes = [models.Index(Lower('name'), name='index_lower_name')]
  425. required_db_features = {'supports_expression_indexes'}
  426. self.assertEqual(Model.check(databases=self.databases), [])
  427. def test_func_index_complex_expression_custom_lookup(self):
  428. class Model(models.Model):
  429. height = models.IntegerField()
  430. weight = models.IntegerField()
  431. class Meta:
  432. indexes = [
  433. models.Index(
  434. models.F('height') / (models.F('weight__abs') + models.Value(5)),
  435. name='name',
  436. ),
  437. ]
  438. with register_lookup(models.IntegerField, Abs):
  439. self.assertEqual(Model.check(), [])
  440. def test_func_index_pointing_to_missing_field(self):
  441. class Model(models.Model):
  442. class Meta:
  443. indexes = [models.Index(Lower('missing_field').desc(), name='name')]
  444. self.assertEqual(Model.check(), [
  445. Error(
  446. "'indexes' refers to the nonexistent field 'missing_field'.",
  447. obj=Model,
  448. id='models.E012',
  449. ),
  450. ])
  451. def test_func_index_pointing_to_missing_field_nested(self):
  452. class Model(models.Model):
  453. class Meta:
  454. indexes = [
  455. models.Index(Abs(Round('missing_field')), name='name'),
  456. ]
  457. self.assertEqual(Model.check(), [
  458. Error(
  459. "'indexes' refers to the nonexistent field 'missing_field'.",
  460. obj=Model,
  461. id='models.E012',
  462. ),
  463. ])
  464. def test_func_index_pointing_to_m2m_field(self):
  465. class Model(models.Model):
  466. m2m = models.ManyToManyField('self')
  467. class Meta:
  468. indexes = [models.Index(Lower('m2m'), name='name')]
  469. self.assertEqual(Model.check(), [
  470. Error(
  471. "'indexes' refers to a ManyToManyField 'm2m', but "
  472. "ManyToManyFields are not permitted in 'indexes'.",
  473. obj=Model,
  474. id='models.E013',
  475. ),
  476. ])
  477. def test_func_index_pointing_to_non_local_field(self):
  478. class Foo(models.Model):
  479. field1 = models.CharField(max_length=15)
  480. class Bar(Foo):
  481. class Meta:
  482. indexes = [models.Index(Lower('field1'), name='name')]
  483. self.assertEqual(Bar.check(), [
  484. Error(
  485. "'indexes' refers to field 'field1' which is not local to "
  486. "model 'Bar'.",
  487. hint='This issue may be caused by multi-table inheritance.',
  488. obj=Bar,
  489. id='models.E016',
  490. ),
  491. ])
  492. def test_func_index_pointing_to_fk(self):
  493. class Foo(models.Model):
  494. pass
  495. class Bar(models.Model):
  496. foo_1 = models.ForeignKey(Foo, models.CASCADE, related_name='bar_1')
  497. foo_2 = models.ForeignKey(Foo, models.CASCADE, related_name='bar_2')
  498. class Meta:
  499. indexes = [
  500. models.Index(Lower('foo_1_id'), Lower('foo_2'), name='index_name'),
  501. ]
  502. self.assertEqual(Bar.check(), [])
  503. @isolate_apps('invalid_models_tests')
  504. class FieldNamesTests(TestCase):
  505. databases = {'default', 'other'}
  506. def test_ending_with_underscore(self):
  507. class Model(models.Model):
  508. field_ = models.CharField(max_length=10)
  509. m2m_ = models.ManyToManyField('self')
  510. self.assertEqual(Model.check(), [
  511. Error(
  512. 'Field names must not end with an underscore.',
  513. obj=Model._meta.get_field('field_'),
  514. id='fields.E001',
  515. ),
  516. Error(
  517. 'Field names must not end with an underscore.',
  518. obj=Model._meta.get_field('m2m_'),
  519. id='fields.E001',
  520. ),
  521. ])
  522. max_column_name_length, column_limit_db_alias = get_max_column_name_length()
  523. @unittest.skipIf(max_column_name_length is None, "The database doesn't have a column name length limit.")
  524. def test_M2M_long_column_name(self):
  525. """
  526. #13711 -- Model check for long M2M column names when database has
  527. column name length limits.
  528. """
  529. # A model with very long name which will be used to set relations to.
  530. class VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(models.Model):
  531. title = models.CharField(max_length=11)
  532. # Main model for which checks will be performed.
  533. class ModelWithLongField(models.Model):
  534. m2m_field = models.ManyToManyField(
  535. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  536. related_name='rn1',
  537. )
  538. m2m_field2 = models.ManyToManyField(
  539. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  540. related_name='rn2', through='m2msimple',
  541. )
  542. m2m_field3 = models.ManyToManyField(
  543. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  544. related_name='rn3',
  545. through='m2mcomplex',
  546. )
  547. fk = models.ForeignKey(
  548. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  549. models.CASCADE,
  550. related_name='rn4',
  551. )
  552. # Models used for setting `through` in M2M field.
  553. class m2msimple(models.Model):
  554. id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
  555. class m2mcomplex(models.Model):
  556. id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
  557. long_field_name = 'a' * (self.max_column_name_length + 1)
  558. models.ForeignKey(
  559. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  560. models.CASCADE,
  561. ).contribute_to_class(m2msimple, long_field_name)
  562. models.ForeignKey(
  563. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  564. models.CASCADE,
  565. db_column=long_field_name
  566. ).contribute_to_class(m2mcomplex, long_field_name)
  567. errors = ModelWithLongField.check(databases=('default', 'other'))
  568. # First error because of M2M field set on the model with long name.
  569. m2m_long_name = "verylongmodelnamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz_id"
  570. if self.max_column_name_length > len(m2m_long_name):
  571. # Some databases support names longer than the test name.
  572. expected = []
  573. else:
  574. expected = [
  575. Error(
  576. 'Autogenerated column name too long for M2M field "%s". '
  577. 'Maximum length is "%s" for database "%s".'
  578. % (m2m_long_name, self.max_column_name_length, self.column_limit_db_alias),
  579. hint="Use 'through' to create a separate model for "
  580. "M2M and then set column_name using 'db_column'.",
  581. obj=ModelWithLongField,
  582. id='models.E019',
  583. )
  584. ]
  585. # Second error because the FK specified in the `through` model
  586. # `m2msimple` has auto-generated name longer than allowed.
  587. # There will be no check errors in the other M2M because it
  588. # specifies db_column for the FK in `through` model even if the actual
  589. # name is longer than the limits of the database.
  590. expected.append(
  591. Error(
  592. 'Autogenerated column name too long for M2M field "%s_id". '
  593. 'Maximum length is "%s" for database "%s".'
  594. % (long_field_name, self.max_column_name_length, self.column_limit_db_alias),
  595. hint="Use 'through' to create a separate model for "
  596. "M2M and then set column_name using 'db_column'.",
  597. obj=ModelWithLongField,
  598. id='models.E019',
  599. )
  600. )
  601. self.assertEqual(errors, expected)
  602. # Check for long column names is called only for specified database
  603. # aliases.
  604. self.assertEqual(ModelWithLongField.check(databases=None), [])
  605. @unittest.skipIf(max_column_name_length is None, "The database doesn't have a column name length limit.")
  606. def test_local_field_long_column_name(self):
  607. """
  608. #13711 -- Model check for long column names
  609. when database does not support long names.
  610. """
  611. class ModelWithLongField(models.Model):
  612. title = models.CharField(max_length=11)
  613. long_field_name = 'a' * (self.max_column_name_length + 1)
  614. long_field_name2 = 'b' * (self.max_column_name_length + 1)
  615. models.CharField(max_length=11).contribute_to_class(ModelWithLongField, long_field_name)
  616. models.CharField(max_length=11, db_column='vlmn').contribute_to_class(ModelWithLongField, long_field_name2)
  617. self.assertEqual(ModelWithLongField.check(databases=('default', 'other')), [
  618. Error(
  619. 'Autogenerated column name too long for field "%s". '
  620. 'Maximum length is "%s" for database "%s".'
  621. % (long_field_name, self.max_column_name_length, self.column_limit_db_alias),
  622. hint="Set the column name manually using 'db_column'.",
  623. obj=ModelWithLongField,
  624. id='models.E018',
  625. )
  626. ])
  627. # Check for long column names is called only for specified database
  628. # aliases.
  629. self.assertEqual(ModelWithLongField.check(databases=None), [])
  630. def test_including_separator(self):
  631. class Model(models.Model):
  632. some__field = models.IntegerField()
  633. self.assertEqual(Model.check(), [
  634. Error(
  635. 'Field names must not contain "__".',
  636. obj=Model._meta.get_field('some__field'),
  637. id='fields.E002',
  638. )
  639. ])
  640. def test_pk(self):
  641. class Model(models.Model):
  642. pk = models.IntegerField()
  643. self.assertEqual(Model.check(), [
  644. Error(
  645. "'pk' is a reserved word that cannot be used as a field name.",
  646. obj=Model._meta.get_field('pk'),
  647. id='fields.E003',
  648. )
  649. ])
  650. def test_db_column_clash(self):
  651. class Model(models.Model):
  652. foo = models.IntegerField()
  653. bar = models.IntegerField(db_column='foo')
  654. self.assertEqual(Model.check(), [
  655. Error(
  656. "Field 'bar' has column name 'foo' that is used by "
  657. "another field.",
  658. hint="Specify a 'db_column' for the field.",
  659. obj=Model,
  660. id='models.E007',
  661. )
  662. ])
  663. @isolate_apps('invalid_models_tests')
  664. class ShadowingFieldsTests(SimpleTestCase):
  665. def test_field_name_clash_with_child_accessor(self):
  666. class Parent(models.Model):
  667. pass
  668. class Child(Parent):
  669. child = models.CharField(max_length=100)
  670. self.assertEqual(Child.check(), [
  671. Error(
  672. "The field 'child' clashes with the field "
  673. "'child' from model 'invalid_models_tests.parent'.",
  674. obj=Child._meta.get_field('child'),
  675. id='models.E006',
  676. )
  677. ])
  678. def test_multiinheritance_clash(self):
  679. class Mother(models.Model):
  680. clash = models.IntegerField()
  681. class Father(models.Model):
  682. clash = models.IntegerField()
  683. class Child(Mother, Father):
  684. # Here we have two clashed: id (automatic field) and clash, because
  685. # both parents define these fields.
  686. pass
  687. self.assertEqual(Child.check(), [
  688. Error(
  689. "The field 'id' from parent model "
  690. "'invalid_models_tests.mother' clashes with the field 'id' "
  691. "from parent model 'invalid_models_tests.father'.",
  692. obj=Child,
  693. id='models.E005',
  694. ),
  695. Error(
  696. "The field 'clash' from parent model "
  697. "'invalid_models_tests.mother' clashes with the field 'clash' "
  698. "from parent model 'invalid_models_tests.father'.",
  699. obj=Child,
  700. id='models.E005',
  701. )
  702. ])
  703. def test_inheritance_clash(self):
  704. class Parent(models.Model):
  705. f_id = models.IntegerField()
  706. class Target(models.Model):
  707. # This field doesn't result in a clash.
  708. f_id = models.IntegerField()
  709. class Child(Parent):
  710. # This field clashes with parent "f_id" field.
  711. f = models.ForeignKey(Target, models.CASCADE)
  712. self.assertEqual(Child.check(), [
  713. Error(
  714. "The field 'f' clashes with the field 'f_id' "
  715. "from model 'invalid_models_tests.parent'.",
  716. obj=Child._meta.get_field('f'),
  717. id='models.E006',
  718. )
  719. ])
  720. def test_multigeneration_inheritance(self):
  721. class GrandParent(models.Model):
  722. clash = models.IntegerField()
  723. class Parent(GrandParent):
  724. pass
  725. class Child(Parent):
  726. pass
  727. class GrandChild(Child):
  728. clash = models.IntegerField()
  729. self.assertEqual(GrandChild.check(), [
  730. Error(
  731. "The field 'clash' clashes with the field 'clash' "
  732. "from model 'invalid_models_tests.grandparent'.",
  733. obj=GrandChild._meta.get_field('clash'),
  734. id='models.E006',
  735. )
  736. ])
  737. def test_id_clash(self):
  738. class Target(models.Model):
  739. pass
  740. class Model(models.Model):
  741. fk = models.ForeignKey(Target, models.CASCADE)
  742. fk_id = models.IntegerField()
  743. self.assertEqual(Model.check(), [
  744. Error(
  745. "The field 'fk_id' clashes with the field 'fk' from model "
  746. "'invalid_models_tests.model'.",
  747. obj=Model._meta.get_field('fk_id'),
  748. id='models.E006',
  749. )
  750. ])
  751. @isolate_apps('invalid_models_tests')
  752. class OtherModelTests(SimpleTestCase):
  753. def test_unique_primary_key(self):
  754. invalid_id = models.IntegerField(primary_key=False)
  755. class Model(models.Model):
  756. id = invalid_id
  757. self.assertEqual(Model.check(), [
  758. Error(
  759. "'id' can only be used as a field name if the field also sets "
  760. "'primary_key=True'.",
  761. obj=Model,
  762. id='models.E004',
  763. ),
  764. ])
  765. def test_ordering_non_iterable(self):
  766. class Model(models.Model):
  767. class Meta:
  768. ordering = 'missing_field'
  769. self.assertEqual(Model.check(), [
  770. Error(
  771. "'ordering' must be a tuple or list "
  772. "(even if you want to order by only one field).",
  773. obj=Model,
  774. id='models.E014',
  775. ),
  776. ])
  777. def test_just_ordering_no_errors(self):
  778. class Model(models.Model):
  779. order = models.PositiveIntegerField()
  780. class Meta:
  781. ordering = ['order']
  782. self.assertEqual(Model.check(), [])
  783. def test_just_order_with_respect_to_no_errors(self):
  784. class Question(models.Model):
  785. pass
  786. class Answer(models.Model):
  787. question = models.ForeignKey(Question, models.CASCADE)
  788. class Meta:
  789. order_with_respect_to = 'question'
  790. self.assertEqual(Answer.check(), [])
  791. def test_ordering_with_order_with_respect_to(self):
  792. class Question(models.Model):
  793. pass
  794. class Answer(models.Model):
  795. question = models.ForeignKey(Question, models.CASCADE)
  796. order = models.IntegerField()
  797. class Meta:
  798. order_with_respect_to = 'question'
  799. ordering = ['order']
  800. self.assertEqual(Answer.check(), [
  801. Error(
  802. "'ordering' and 'order_with_respect_to' cannot be used together.",
  803. obj=Answer,
  804. id='models.E021',
  805. ),
  806. ])
  807. def test_non_valid(self):
  808. class RelationModel(models.Model):
  809. pass
  810. class Model(models.Model):
  811. relation = models.ManyToManyField(RelationModel)
  812. class Meta:
  813. ordering = ['relation']
  814. self.assertEqual(Model.check(), [
  815. Error(
  816. "'ordering' refers to the nonexistent field, related field, "
  817. "or lookup 'relation'.",
  818. obj=Model,
  819. id='models.E015',
  820. ),
  821. ])
  822. def test_ordering_pointing_to_missing_field(self):
  823. class Model(models.Model):
  824. class Meta:
  825. ordering = ('missing_field',)
  826. self.assertEqual(Model.check(), [
  827. Error(
  828. "'ordering' refers to the nonexistent field, related field, "
  829. "or lookup 'missing_field'.",
  830. obj=Model,
  831. id='models.E015',
  832. )
  833. ])
  834. def test_ordering_pointing_to_missing_foreignkey_field(self):
  835. class Model(models.Model):
  836. missing_fk_field = models.IntegerField()
  837. class Meta:
  838. ordering = ('missing_fk_field_id',)
  839. self.assertEqual(Model.check(), [
  840. Error(
  841. "'ordering' refers to the nonexistent field, related field, "
  842. "or lookup 'missing_fk_field_id'.",
  843. obj=Model,
  844. id='models.E015',
  845. )
  846. ])
  847. def test_ordering_pointing_to_missing_related_field(self):
  848. class Model(models.Model):
  849. test = models.IntegerField()
  850. class Meta:
  851. ordering = ('missing_related__id',)
  852. self.assertEqual(Model.check(), [
  853. Error(
  854. "'ordering' refers to the nonexistent field, related field, "
  855. "or lookup 'missing_related__id'.",
  856. obj=Model,
  857. id='models.E015',
  858. )
  859. ])
  860. def test_ordering_pointing_to_missing_related_model_field(self):
  861. class Parent(models.Model):
  862. pass
  863. class Child(models.Model):
  864. parent = models.ForeignKey(Parent, models.CASCADE)
  865. class Meta:
  866. ordering = ('parent__missing_field',)
  867. self.assertEqual(Child.check(), [
  868. Error(
  869. "'ordering' refers to the nonexistent field, related field, "
  870. "or lookup 'parent__missing_field'.",
  871. obj=Child,
  872. id='models.E015',
  873. )
  874. ])
  875. def test_ordering_pointing_to_non_related_field(self):
  876. class Child(models.Model):
  877. parent = models.IntegerField()
  878. class Meta:
  879. ordering = ('parent__missing_field',)
  880. self.assertEqual(Child.check(), [
  881. Error(
  882. "'ordering' refers to the nonexistent field, related field, "
  883. "or lookup 'parent__missing_field'.",
  884. obj=Child,
  885. id='models.E015',
  886. )
  887. ])
  888. def test_ordering_pointing_to_two_related_model_field(self):
  889. class Parent2(models.Model):
  890. pass
  891. class Parent1(models.Model):
  892. parent2 = models.ForeignKey(Parent2, models.CASCADE)
  893. class Child(models.Model):
  894. parent1 = models.ForeignKey(Parent1, models.CASCADE)
  895. class Meta:
  896. ordering = ('parent1__parent2__missing_field',)
  897. self.assertEqual(Child.check(), [
  898. Error(
  899. "'ordering' refers to the nonexistent field, related field, "
  900. "or lookup 'parent1__parent2__missing_field'.",
  901. obj=Child,
  902. id='models.E015',
  903. )
  904. ])
  905. def test_ordering_pointing_multiple_times_to_model_fields(self):
  906. class Parent(models.Model):
  907. field1 = models.CharField(max_length=100)
  908. field2 = models.CharField(max_length=100)
  909. class Child(models.Model):
  910. parent = models.ForeignKey(Parent, models.CASCADE)
  911. class Meta:
  912. ordering = ('parent__field1__field2',)
  913. self.assertEqual(Child.check(), [
  914. Error(
  915. "'ordering' refers to the nonexistent field, related field, "
  916. "or lookup 'parent__field1__field2'.",
  917. obj=Child,
  918. id='models.E015',
  919. )
  920. ])
  921. def test_ordering_allows_registered_lookups(self):
  922. class Model(models.Model):
  923. test = models.CharField(max_length=100)
  924. class Meta:
  925. ordering = ('test__lower',)
  926. with register_lookup(models.CharField, Lower):
  927. self.assertEqual(Model.check(), [])
  928. def test_ordering_pointing_to_lookup_not_transform(self):
  929. class Model(models.Model):
  930. test = models.CharField(max_length=100)
  931. class Meta:
  932. ordering = ('test__isnull',)
  933. self.assertEqual(Model.check(), [])
  934. def test_ordering_pointing_to_related_model_pk(self):
  935. class Parent(models.Model):
  936. pass
  937. class Child(models.Model):
  938. parent = models.ForeignKey(Parent, models.CASCADE)
  939. class Meta:
  940. ordering = ('parent__pk',)
  941. self.assertEqual(Child.check(), [])
  942. def test_ordering_pointing_to_foreignkey_field(self):
  943. class Parent(models.Model):
  944. pass
  945. class Child(models.Model):
  946. parent = models.ForeignKey(Parent, models.CASCADE)
  947. class Meta:
  948. ordering = ('parent_id',)
  949. self.assertFalse(Child.check())
  950. def test_name_beginning_with_underscore(self):
  951. class _Model(models.Model):
  952. pass
  953. self.assertEqual(_Model.check(), [
  954. Error(
  955. "The model name '_Model' cannot start or end with an underscore "
  956. "as it collides with the query lookup syntax.",
  957. obj=_Model,
  958. id='models.E023',
  959. )
  960. ])
  961. def test_name_ending_with_underscore(self):
  962. class Model_(models.Model):
  963. pass
  964. self.assertEqual(Model_.check(), [
  965. Error(
  966. "The model name 'Model_' cannot start or end with an underscore "
  967. "as it collides with the query lookup syntax.",
  968. obj=Model_,
  969. id='models.E023',
  970. )
  971. ])
  972. def test_name_contains_double_underscores(self):
  973. class Test__Model(models.Model):
  974. pass
  975. self.assertEqual(Test__Model.check(), [
  976. Error(
  977. "The model name 'Test__Model' cannot contain double underscores "
  978. "as it collides with the query lookup syntax.",
  979. obj=Test__Model,
  980. id='models.E024',
  981. )
  982. ])
  983. def test_property_and_related_field_accessor_clash(self):
  984. class Model(models.Model):
  985. fk = models.ForeignKey('self', models.CASCADE)
  986. # Override related field accessor.
  987. Model.fk_id = property(lambda self: 'ERROR')
  988. self.assertEqual(Model.check(), [
  989. Error(
  990. "The property 'fk_id' clashes with a related field accessor.",
  991. obj=Model,
  992. id='models.E025',
  993. )
  994. ])
  995. def test_single_primary_key(self):
  996. class Model(models.Model):
  997. foo = models.IntegerField(primary_key=True)
  998. bar = models.IntegerField(primary_key=True)
  999. self.assertEqual(Model.check(), [
  1000. Error(
  1001. "The model cannot have more than one field with 'primary_key=True'.",
  1002. obj=Model,
  1003. id='models.E026',
  1004. )
  1005. ])
  1006. @override_settings(TEST_SWAPPED_MODEL_BAD_VALUE='not-a-model')
  1007. def test_swappable_missing_app_name(self):
  1008. class Model(models.Model):
  1009. class Meta:
  1010. swappable = 'TEST_SWAPPED_MODEL_BAD_VALUE'
  1011. self.assertEqual(Model.check(), [
  1012. Error(
  1013. "'TEST_SWAPPED_MODEL_BAD_VALUE' is not of the form 'app_label.app_name'.",
  1014. id='models.E001',
  1015. ),
  1016. ])
  1017. @override_settings(TEST_SWAPPED_MODEL_BAD_MODEL='not_an_app.Target')
  1018. def test_swappable_missing_app(self):
  1019. class Model(models.Model):
  1020. class Meta:
  1021. swappable = 'TEST_SWAPPED_MODEL_BAD_MODEL'
  1022. self.assertEqual(Model.check(), [
  1023. Error(
  1024. "'TEST_SWAPPED_MODEL_BAD_MODEL' references 'not_an_app.Target', "
  1025. 'which has not been installed, or is abstract.',
  1026. id='models.E002',
  1027. ),
  1028. ])
  1029. def test_two_m2m_through_same_relationship(self):
  1030. class Person(models.Model):
  1031. pass
  1032. class Group(models.Model):
  1033. primary = models.ManyToManyField(Person, through='Membership', related_name='primary')
  1034. secondary = models.ManyToManyField(Person, through='Membership', related_name='secondary')
  1035. class Membership(models.Model):
  1036. person = models.ForeignKey(Person, models.CASCADE)
  1037. group = models.ForeignKey(Group, models.CASCADE)
  1038. self.assertEqual(Group.check(), [
  1039. Error(
  1040. "The model has two identical many-to-many relations through "
  1041. "the intermediate model 'invalid_models_tests.Membership'.",
  1042. obj=Group,
  1043. id='models.E003',
  1044. )
  1045. ])
  1046. def test_two_m2m_through_same_model_with_different_through_fields(self):
  1047. class Country(models.Model):
  1048. pass
  1049. class ShippingMethod(models.Model):
  1050. to_countries = models.ManyToManyField(
  1051. Country, through='ShippingMethodPrice',
  1052. through_fields=('method', 'to_country'),
  1053. )
  1054. from_countries = models.ManyToManyField(
  1055. Country, through='ShippingMethodPrice',
  1056. through_fields=('method', 'from_country'),
  1057. related_name='+',
  1058. )
  1059. class ShippingMethodPrice(models.Model):
  1060. method = models.ForeignKey(ShippingMethod, models.CASCADE)
  1061. to_country = models.ForeignKey(Country, models.CASCADE)
  1062. from_country = models.ForeignKey(Country, models.CASCADE)
  1063. self.assertEqual(ShippingMethod.check(), [])
  1064. def test_onetoone_with_parent_model(self):
  1065. class Place(models.Model):
  1066. pass
  1067. class ParkingLot(Place):
  1068. other_place = models.OneToOneField(Place, models.CASCADE, related_name='other_parking')
  1069. self.assertEqual(ParkingLot.check(), [])
  1070. def test_onetoone_with_explicit_parent_link_parent_model(self):
  1071. class Place(models.Model):
  1072. pass
  1073. class ParkingLot(Place):
  1074. place = models.OneToOneField(Place, models.CASCADE, parent_link=True, primary_key=True)
  1075. other_place = models.OneToOneField(Place, models.CASCADE, related_name='other_parking')
  1076. self.assertEqual(ParkingLot.check(), [])
  1077. def test_m2m_table_name_clash(self):
  1078. class Foo(models.Model):
  1079. bar = models.ManyToManyField('Bar', db_table='myapp_bar')
  1080. class Meta:
  1081. db_table = 'myapp_foo'
  1082. class Bar(models.Model):
  1083. class Meta:
  1084. db_table = 'myapp_bar'
  1085. self.assertEqual(Foo.check(), [
  1086. Error(
  1087. "The field's intermediary table 'myapp_bar' clashes with the "
  1088. "table name of 'invalid_models_tests.Bar'.",
  1089. obj=Foo._meta.get_field('bar'),
  1090. id='fields.E340',
  1091. )
  1092. ])
  1093. @override_settings(DATABASE_ROUTERS=['invalid_models_tests.test_models.EmptyRouter'])
  1094. def test_m2m_table_name_clash_database_routers_installed(self):
  1095. class Foo(models.Model):
  1096. bar = models.ManyToManyField('Bar', db_table='myapp_bar')
  1097. class Meta:
  1098. db_table = 'myapp_foo'
  1099. class Bar(models.Model):
  1100. class Meta:
  1101. db_table = 'myapp_bar'
  1102. self.assertEqual(Foo.check(), [
  1103. Warning(
  1104. "The field's intermediary table 'myapp_bar' clashes with the "
  1105. "table name of 'invalid_models_tests.Bar'.",
  1106. obj=Foo._meta.get_field('bar'),
  1107. hint=(
  1108. "You have configured settings.DATABASE_ROUTERS. Verify "
  1109. "that the table of 'invalid_models_tests.Bar' is "
  1110. "correctly routed to a separate database."
  1111. ),
  1112. id='fields.W344',
  1113. ),
  1114. ])
  1115. def test_m2m_field_table_name_clash(self):
  1116. class Foo(models.Model):
  1117. pass
  1118. class Bar(models.Model):
  1119. foos = models.ManyToManyField(Foo, db_table='clash')
  1120. class Baz(models.Model):
  1121. foos = models.ManyToManyField(Foo, db_table='clash')
  1122. self.assertEqual(Bar.check() + Baz.check(), [
  1123. Error(
  1124. "The field's intermediary table 'clash' clashes with the "
  1125. "table name of 'invalid_models_tests.Baz.foos'.",
  1126. obj=Bar._meta.get_field('foos'),
  1127. id='fields.E340',
  1128. ),
  1129. Error(
  1130. "The field's intermediary table 'clash' clashes with the "
  1131. "table name of 'invalid_models_tests.Bar.foos'.",
  1132. obj=Baz._meta.get_field('foos'),
  1133. id='fields.E340',
  1134. )
  1135. ])
  1136. @override_settings(DATABASE_ROUTERS=['invalid_models_tests.test_models.EmptyRouter'])
  1137. def test_m2m_field_table_name_clash_database_routers_installed(self):
  1138. class Foo(models.Model):
  1139. pass
  1140. class Bar(models.Model):
  1141. foos = models.ManyToManyField(Foo, db_table='clash')
  1142. class Baz(models.Model):
  1143. foos = models.ManyToManyField(Foo, db_table='clash')
  1144. self.assertEqual(Bar.check() + Baz.check(), [
  1145. Warning(
  1146. "The field's intermediary table 'clash' clashes with the "
  1147. "table name of 'invalid_models_tests.%s.foos'."
  1148. % clashing_model,
  1149. obj=model_cls._meta.get_field('foos'),
  1150. hint=(
  1151. "You have configured settings.DATABASE_ROUTERS. Verify "
  1152. "that the table of 'invalid_models_tests.%s.foos' is "
  1153. "correctly routed to a separate database." % clashing_model
  1154. ),
  1155. id='fields.W344',
  1156. ) for model_cls, clashing_model in [(Bar, 'Baz'), (Baz, 'Bar')]
  1157. ])
  1158. def test_m2m_autogenerated_table_name_clash(self):
  1159. class Foo(models.Model):
  1160. class Meta:
  1161. db_table = 'bar_foos'
  1162. class Bar(models.Model):
  1163. # The autogenerated `db_table` will be bar_foos.
  1164. foos = models.ManyToManyField(Foo)
  1165. class Meta:
  1166. db_table = 'bar'
  1167. self.assertEqual(Bar.check(), [
  1168. Error(
  1169. "The field's intermediary table 'bar_foos' clashes with the "
  1170. "table name of 'invalid_models_tests.Foo'.",
  1171. obj=Bar._meta.get_field('foos'),
  1172. id='fields.E340',
  1173. )
  1174. ])
  1175. @override_settings(DATABASE_ROUTERS=['invalid_models_tests.test_models.EmptyRouter'])
  1176. def test_m2m_autogenerated_table_name_clash_database_routers_installed(self):
  1177. class Foo(models.Model):
  1178. class Meta:
  1179. db_table = 'bar_foos'
  1180. class Bar(models.Model):
  1181. # The autogenerated db_table is bar_foos.
  1182. foos = models.ManyToManyField(Foo)
  1183. class Meta:
  1184. db_table = 'bar'
  1185. self.assertEqual(Bar.check(), [
  1186. Warning(
  1187. "The field's intermediary table 'bar_foos' clashes with the "
  1188. "table name of 'invalid_models_tests.Foo'.",
  1189. obj=Bar._meta.get_field('foos'),
  1190. hint=(
  1191. "You have configured settings.DATABASE_ROUTERS. Verify "
  1192. "that the table of 'invalid_models_tests.Foo' is "
  1193. "correctly routed to a separate database."
  1194. ),
  1195. id='fields.W344',
  1196. ),
  1197. ])
  1198. def test_m2m_unmanaged_shadow_models_not_checked(self):
  1199. class A1(models.Model):
  1200. pass
  1201. class C1(models.Model):
  1202. mm_a = models.ManyToManyField(A1, db_table='d1')
  1203. # Unmanaged models that shadow the above models. Reused table names
  1204. # shouldn't be flagged by any checks.
  1205. class A2(models.Model):
  1206. class Meta:
  1207. managed = False
  1208. class C2(models.Model):
  1209. mm_a = models.ManyToManyField(A2, through='Intermediate')
  1210. class Meta:
  1211. managed = False
  1212. class Intermediate(models.Model):
  1213. a2 = models.ForeignKey(A2, models.CASCADE, db_column='a1_id')
  1214. c2 = models.ForeignKey(C2, models.CASCADE, db_column='c1_id')
  1215. class Meta:
  1216. db_table = 'd1'
  1217. managed = False
  1218. self.assertEqual(C1.check(), [])
  1219. self.assertEqual(C2.check(), [])
  1220. def test_m2m_to_concrete_and_proxy_allowed(self):
  1221. class A(models.Model):
  1222. pass
  1223. class Through(models.Model):
  1224. a = models.ForeignKey('A', models.CASCADE)
  1225. c = models.ForeignKey('C', models.CASCADE)
  1226. class ThroughProxy(Through):
  1227. class Meta:
  1228. proxy = True
  1229. class C(models.Model):
  1230. mm_a = models.ManyToManyField(A, through=Through)
  1231. mm_aproxy = models.ManyToManyField(A, through=ThroughProxy, related_name='proxied_m2m')
  1232. self.assertEqual(C.check(), [])
  1233. @isolate_apps('django.contrib.auth', kwarg_name='apps')
  1234. def test_lazy_reference_checks(self, apps):
  1235. class DummyModel(models.Model):
  1236. author = models.ForeignKey('Author', models.CASCADE)
  1237. class Meta:
  1238. app_label = 'invalid_models_tests'
  1239. class DummyClass:
  1240. def __call__(self, **kwargs):
  1241. pass
  1242. def dummy_method(self):
  1243. pass
  1244. def dummy_function(*args, **kwargs):
  1245. pass
  1246. apps.lazy_model_operation(dummy_function, ('auth', 'imaginarymodel'))
  1247. apps.lazy_model_operation(dummy_function, ('fanciful_app', 'imaginarymodel'))
  1248. post_init.connect(dummy_function, sender='missing-app.Model', apps=apps)
  1249. post_init.connect(DummyClass(), sender='missing-app.Model', apps=apps)
  1250. post_init.connect(DummyClass().dummy_method, sender='missing-app.Model', apps=apps)
  1251. self.assertEqual(_check_lazy_references(apps), [
  1252. Error(
  1253. "%r contains a lazy reference to auth.imaginarymodel, "
  1254. "but app 'auth' doesn't provide model 'imaginarymodel'." % dummy_function,
  1255. obj=dummy_function,
  1256. id='models.E022',
  1257. ),
  1258. Error(
  1259. "%r contains a lazy reference to fanciful_app.imaginarymodel, "
  1260. "but app 'fanciful_app' isn't installed." % dummy_function,
  1261. obj=dummy_function,
  1262. id='models.E022',
  1263. ),
  1264. Error(
  1265. "An instance of class 'DummyClass' was connected to "
  1266. "the 'post_init' signal with a lazy reference to the sender "
  1267. "'missing-app.model', but app 'missing-app' isn't installed.",
  1268. hint=None,
  1269. obj='invalid_models_tests.test_models',
  1270. id='signals.E001',
  1271. ),
  1272. Error(
  1273. "Bound method 'DummyClass.dummy_method' was connected to the "
  1274. "'post_init' signal with a lazy reference to the sender "
  1275. "'missing-app.model', but app 'missing-app' isn't installed.",
  1276. hint=None,
  1277. obj='invalid_models_tests.test_models',
  1278. id='signals.E001',
  1279. ),
  1280. Error(
  1281. "The field invalid_models_tests.DummyModel.author was declared "
  1282. "with a lazy reference to 'invalid_models_tests.author', but app "
  1283. "'invalid_models_tests' isn't installed.",
  1284. hint=None,
  1285. obj=DummyModel.author.field,
  1286. id='fields.E307',
  1287. ),
  1288. Error(
  1289. "The function 'dummy_function' was connected to the 'post_init' "
  1290. "signal with a lazy reference to the sender "
  1291. "'missing-app.model', but app 'missing-app' isn't installed.",
  1292. hint=None,
  1293. obj='invalid_models_tests.test_models',
  1294. id='signals.E001',
  1295. ),
  1296. ])
  1297. @isolate_apps('invalid_models_tests')
  1298. class JSONFieldTests(TestCase):
  1299. @skipUnlessDBFeature('supports_json_field')
  1300. def test_ordering_pointing_to_json_field_value(self):
  1301. class Model(models.Model):
  1302. field = models.JSONField()
  1303. class Meta:
  1304. ordering = ['field__value']
  1305. self.assertEqual(Model.check(databases=self.databases), [])
  1306. def test_check_jsonfield(self):
  1307. class Model(models.Model):
  1308. field = models.JSONField()
  1309. error = Error(
  1310. '%s does not support JSONFields.' % connection.display_name,
  1311. obj=Model,
  1312. id='fields.E180',
  1313. )
  1314. expected = [] if connection.features.supports_json_field else [error]
  1315. self.assertEqual(Model.check(databases=self.databases), expected)
  1316. def test_check_jsonfield_required_db_features(self):
  1317. class Model(models.Model):
  1318. field = models.JSONField()
  1319. class Meta:
  1320. required_db_features = {'supports_json_field'}
  1321. self.assertEqual(Model.check(databases=self.databases), [])
  1322. @isolate_apps('invalid_models_tests')
  1323. class ConstraintsTests(TestCase):
  1324. def test_check_constraints(self):
  1325. class Model(models.Model):
  1326. age = models.IntegerField()
  1327. class Meta:
  1328. constraints = [models.CheckConstraint(check=models.Q(age__gte=18), name='is_adult')]
  1329. errors = Model.check(databases=self.databases)
  1330. warn = Warning(
  1331. '%s does not support check constraints.' % connection.display_name,
  1332. hint=(
  1333. "A constraint won't be created. Silence this warning if you "
  1334. "don't care about it."
  1335. ),
  1336. obj=Model,
  1337. id='models.W027',
  1338. )
  1339. expected = [] if connection.features.supports_table_check_constraints else [warn]
  1340. self.assertCountEqual(errors, expected)
  1341. def test_check_constraints_required_db_features(self):
  1342. class Model(models.Model):
  1343. age = models.IntegerField()
  1344. class Meta:
  1345. required_db_features = {'supports_table_check_constraints'}
  1346. constraints = [models.CheckConstraint(check=models.Q(age__gte=18), name='is_adult')]
  1347. self.assertEqual(Model.check(databases=self.databases), [])
  1348. def test_check_constraint_pointing_to_missing_field(self):
  1349. class Model(models.Model):
  1350. class Meta:
  1351. required_db_features = {'supports_table_check_constraints'}
  1352. constraints = [
  1353. models.CheckConstraint(
  1354. name='name', check=models.Q(missing_field=2),
  1355. ),
  1356. ]
  1357. self.assertEqual(Model.check(databases=self.databases), [
  1358. Error(
  1359. "'constraints' refers to the nonexistent field "
  1360. "'missing_field'.",
  1361. obj=Model,
  1362. id='models.E012',
  1363. ),
  1364. ] if connection.features.supports_table_check_constraints else [])
  1365. @skipUnlessDBFeature('supports_table_check_constraints')
  1366. def test_check_constraint_pointing_to_reverse_fk(self):
  1367. class Model(models.Model):
  1368. parent = models.ForeignKey('self', models.CASCADE, related_name='parents')
  1369. class Meta:
  1370. constraints = [
  1371. models.CheckConstraint(name='name', check=models.Q(parents=3)),
  1372. ]
  1373. self.assertEqual(Model.check(databases=self.databases), [
  1374. Error(
  1375. "'constraints' refers to the nonexistent field 'parents'.",
  1376. obj=Model,
  1377. id='models.E012',
  1378. ),
  1379. ])
  1380. @skipUnlessDBFeature('supports_table_check_constraints')
  1381. def test_check_constraint_pointing_to_reverse_o2o(self):
  1382. class Model(models.Model):
  1383. parent = models.OneToOneField('self', models.CASCADE)
  1384. class Meta:
  1385. constraints = [
  1386. models.CheckConstraint(
  1387. name='name',
  1388. check=models.Q(model__isnull=True),
  1389. ),
  1390. ]
  1391. self.assertEqual(Model.check(databases=self.databases), [
  1392. Error(
  1393. "'constraints' refers to the nonexistent field 'model'.",
  1394. obj=Model,
  1395. id='models.E012',
  1396. ),
  1397. ])
  1398. @skipUnlessDBFeature('supports_table_check_constraints')
  1399. def test_check_constraint_pointing_to_m2m_field(self):
  1400. class Model(models.Model):
  1401. m2m = models.ManyToManyField('self')
  1402. class Meta:
  1403. constraints = [
  1404. models.CheckConstraint(name='name', check=models.Q(m2m=2)),
  1405. ]
  1406. self.assertEqual(Model.check(databases=self.databases), [
  1407. Error(
  1408. "'constraints' refers to a ManyToManyField 'm2m', but "
  1409. "ManyToManyFields are not permitted in 'constraints'.",
  1410. obj=Model,
  1411. id='models.E013',
  1412. ),
  1413. ])
  1414. @skipUnlessDBFeature('supports_table_check_constraints')
  1415. def test_check_constraint_pointing_to_fk(self):
  1416. class Target(models.Model):
  1417. pass
  1418. class Model(models.Model):
  1419. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name='target_1')
  1420. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name='target_2')
  1421. class Meta:
  1422. constraints = [
  1423. models.CheckConstraint(
  1424. name='name',
  1425. check=models.Q(fk_1_id=2) | models.Q(fk_2=2),
  1426. ),
  1427. ]
  1428. self.assertEqual(Model.check(databases=self.databases), [])
  1429. @skipUnlessDBFeature('supports_table_check_constraints')
  1430. def test_check_constraint_pointing_to_pk(self):
  1431. class Model(models.Model):
  1432. age = models.SmallIntegerField()
  1433. class Meta:
  1434. constraints = [
  1435. models.CheckConstraint(
  1436. name='name',
  1437. check=models.Q(pk__gt=5) & models.Q(age__gt=models.F('pk')),
  1438. ),
  1439. ]
  1440. self.assertEqual(Model.check(databases=self.databases), [])
  1441. @skipUnlessDBFeature('supports_table_check_constraints')
  1442. def test_check_constraint_pointing_to_non_local_field(self):
  1443. class Parent(models.Model):
  1444. field1 = models.IntegerField()
  1445. class Child(Parent):
  1446. pass
  1447. class Meta:
  1448. constraints = [
  1449. models.CheckConstraint(name='name', check=models.Q(field1=1)),
  1450. ]
  1451. self.assertEqual(Child.check(databases=self.databases), [
  1452. Error(
  1453. "'constraints' refers to field 'field1' which is not local to "
  1454. "model 'Child'.",
  1455. hint='This issue may be caused by multi-table inheritance.',
  1456. obj=Child,
  1457. id='models.E016',
  1458. ),
  1459. ])
  1460. @skipUnlessDBFeature('supports_table_check_constraints')
  1461. def test_check_constraint_pointing_to_joined_fields(self):
  1462. class Model(models.Model):
  1463. name = models.CharField(max_length=10)
  1464. field1 = models.PositiveSmallIntegerField()
  1465. field2 = models.PositiveSmallIntegerField()
  1466. field3 = models.PositiveSmallIntegerField()
  1467. parent = models.ForeignKey('self', models.CASCADE)
  1468. previous = models.OneToOneField('self', models.CASCADE, related_name='next')
  1469. class Meta:
  1470. constraints = [
  1471. models.CheckConstraint(
  1472. name='name1', check=models.Q(
  1473. field1__lt=models.F('parent__field1') + models.F('parent__field2')
  1474. )
  1475. ),
  1476. models.CheckConstraint(
  1477. name='name2', check=models.Q(name=Lower('parent__name'))
  1478. ),
  1479. models.CheckConstraint(
  1480. name='name3', check=models.Q(parent__field3=models.F('field1'))
  1481. ),
  1482. models.CheckConstraint(
  1483. name='name4', check=models.Q(name=Lower('previous__name')),
  1484. ),
  1485. ]
  1486. joined_fields = [
  1487. 'parent__field1',
  1488. 'parent__field2',
  1489. 'parent__field3',
  1490. 'parent__name',
  1491. 'previous__name',
  1492. ]
  1493. errors = Model.check(databases=self.databases)
  1494. expected_errors = [
  1495. Error(
  1496. "'constraints' refers to the joined field '%s'." % field_name,
  1497. obj=Model,
  1498. id='models.E041',
  1499. ) for field_name in joined_fields
  1500. ]
  1501. self.assertCountEqual(errors, expected_errors)
  1502. @skipUnlessDBFeature('supports_table_check_constraints')
  1503. def test_check_constraint_pointing_to_joined_fields_complex_check(self):
  1504. class Model(models.Model):
  1505. name = models.PositiveSmallIntegerField()
  1506. field1 = models.PositiveSmallIntegerField()
  1507. field2 = models.PositiveSmallIntegerField()
  1508. parent = models.ForeignKey('self', models.CASCADE)
  1509. class Meta:
  1510. constraints = [
  1511. models.CheckConstraint(
  1512. name='name',
  1513. check=models.Q(
  1514. (
  1515. models.Q(name='test') &
  1516. models.Q(field1__lt=models.F('parent__field1'))
  1517. ) |
  1518. (
  1519. models.Q(name__startswith=Lower('parent__name')) &
  1520. models.Q(field1__gte=(
  1521. models.F('parent__field1') + models.F('parent__field2')
  1522. ))
  1523. )
  1524. ) | (models.Q(name='test1'))
  1525. ),
  1526. ]
  1527. joined_fields = ['parent__field1', 'parent__field2', 'parent__name']
  1528. errors = Model.check(databases=self.databases)
  1529. expected_errors = [
  1530. Error(
  1531. "'constraints' refers to the joined field '%s'." % field_name,
  1532. obj=Model,
  1533. id='models.E041',
  1534. ) for field_name in joined_fields
  1535. ]
  1536. self.assertCountEqual(errors, expected_errors)
  1537. def test_unique_constraint_with_condition(self):
  1538. class Model(models.Model):
  1539. age = models.IntegerField()
  1540. class Meta:
  1541. constraints = [
  1542. models.UniqueConstraint(
  1543. fields=['age'],
  1544. name='unique_age_gte_100',
  1545. condition=models.Q(age__gte=100),
  1546. ),
  1547. ]
  1548. errors = Model.check(databases=self.databases)
  1549. expected = [] if connection.features.supports_partial_indexes else [
  1550. Warning(
  1551. '%s does not support unique constraints with conditions.'
  1552. % connection.display_name,
  1553. hint=(
  1554. "A constraint won't be created. Silence this warning if "
  1555. "you don't care about it."
  1556. ),
  1557. obj=Model,
  1558. id='models.W036',
  1559. ),
  1560. ]
  1561. self.assertEqual(errors, expected)
  1562. def test_unique_constraint_with_condition_required_db_features(self):
  1563. class Model(models.Model):
  1564. age = models.IntegerField()
  1565. class Meta:
  1566. required_db_features = {'supports_partial_indexes'}
  1567. constraints = [
  1568. models.UniqueConstraint(
  1569. fields=['age'],
  1570. name='unique_age_gte_100',
  1571. condition=models.Q(age__gte=100),
  1572. ),
  1573. ]
  1574. self.assertEqual(Model.check(databases=self.databases), [])
  1575. def test_unique_constraint_condition_pointing_to_missing_field(self):
  1576. class Model(models.Model):
  1577. age = models.SmallIntegerField()
  1578. class Meta:
  1579. required_db_features = {'supports_partial_indexes'}
  1580. constraints = [
  1581. models.UniqueConstraint(
  1582. name='name',
  1583. fields=['age'],
  1584. condition=models.Q(missing_field=2),
  1585. ),
  1586. ]
  1587. self.assertEqual(Model.check(databases=self.databases), [
  1588. Error(
  1589. "'constraints' refers to the nonexistent field "
  1590. "'missing_field'.",
  1591. obj=Model,
  1592. id='models.E012',
  1593. ),
  1594. ] if connection.features.supports_partial_indexes else [])
  1595. def test_unique_constraint_condition_pointing_to_joined_fields(self):
  1596. class Model(models.Model):
  1597. age = models.SmallIntegerField()
  1598. parent = models.ForeignKey('self', models.CASCADE)
  1599. class Meta:
  1600. required_db_features = {'supports_partial_indexes'}
  1601. constraints = [
  1602. models.UniqueConstraint(
  1603. name='name',
  1604. fields=['age'],
  1605. condition=models.Q(parent__age__lt=2),
  1606. ),
  1607. ]
  1608. self.assertEqual(Model.check(databases=self.databases), [
  1609. Error(
  1610. "'constraints' refers to the joined field 'parent__age__lt'.",
  1611. obj=Model,
  1612. id='models.E041',
  1613. )
  1614. ] if connection.features.supports_partial_indexes else [])
  1615. def test_unique_constraint_pointing_to_reverse_o2o(self):
  1616. class Model(models.Model):
  1617. parent = models.OneToOneField('self', models.CASCADE)
  1618. class Meta:
  1619. required_db_features = {'supports_partial_indexes'}
  1620. constraints = [
  1621. models.UniqueConstraint(
  1622. fields=['parent'],
  1623. name='name',
  1624. condition=models.Q(model__isnull=True),
  1625. ),
  1626. ]
  1627. self.assertEqual(Model.check(databases=self.databases), [
  1628. Error(
  1629. "'constraints' refers to the nonexistent field 'model'.",
  1630. obj=Model,
  1631. id='models.E012',
  1632. ),
  1633. ] if connection.features.supports_partial_indexes else [])
  1634. def test_deferrable_unique_constraint(self):
  1635. class Model(models.Model):
  1636. age = models.IntegerField()
  1637. class Meta:
  1638. constraints = [
  1639. models.UniqueConstraint(
  1640. fields=['age'],
  1641. name='unique_age_deferrable',
  1642. deferrable=models.Deferrable.DEFERRED,
  1643. ),
  1644. ]
  1645. errors = Model.check(databases=self.databases)
  1646. expected = [] if connection.features.supports_deferrable_unique_constraints else [
  1647. Warning(
  1648. '%s does not support deferrable unique constraints.'
  1649. % connection.display_name,
  1650. hint=(
  1651. "A constraint won't be created. Silence this warning if "
  1652. "you don't care about it."
  1653. ),
  1654. obj=Model,
  1655. id='models.W038',
  1656. ),
  1657. ]
  1658. self.assertEqual(errors, expected)
  1659. def test_deferrable_unique_constraint_required_db_features(self):
  1660. class Model(models.Model):
  1661. age = models.IntegerField()
  1662. class Meta:
  1663. required_db_features = {'supports_deferrable_unique_constraints'}
  1664. constraints = [
  1665. models.UniqueConstraint(
  1666. fields=['age'],
  1667. name='unique_age_deferrable',
  1668. deferrable=models.Deferrable.IMMEDIATE,
  1669. ),
  1670. ]
  1671. self.assertEqual(Model.check(databases=self.databases), [])
  1672. def test_unique_constraint_pointing_to_missing_field(self):
  1673. class Model(models.Model):
  1674. class Meta:
  1675. constraints = [models.UniqueConstraint(fields=['missing_field'], name='name')]
  1676. self.assertEqual(Model.check(databases=self.databases), [
  1677. Error(
  1678. "'constraints' refers to the nonexistent field "
  1679. "'missing_field'.",
  1680. obj=Model,
  1681. id='models.E012',
  1682. ),
  1683. ])
  1684. def test_unique_constraint_pointing_to_m2m_field(self):
  1685. class Model(models.Model):
  1686. m2m = models.ManyToManyField('self')
  1687. class Meta:
  1688. constraints = [models.UniqueConstraint(fields=['m2m'], name='name')]
  1689. self.assertEqual(Model.check(databases=self.databases), [
  1690. Error(
  1691. "'constraints' refers to a ManyToManyField 'm2m', but "
  1692. "ManyToManyFields are not permitted in 'constraints'.",
  1693. obj=Model,
  1694. id='models.E013',
  1695. ),
  1696. ])
  1697. def test_unique_constraint_pointing_to_non_local_field(self):
  1698. class Parent(models.Model):
  1699. field1 = models.IntegerField()
  1700. class Child(Parent):
  1701. field2 = models.IntegerField()
  1702. class Meta:
  1703. constraints = [
  1704. models.UniqueConstraint(fields=['field2', 'field1'], name='name'),
  1705. ]
  1706. self.assertEqual(Child.check(databases=self.databases), [
  1707. Error(
  1708. "'constraints' refers to field 'field1' which is not local to "
  1709. "model 'Child'.",
  1710. hint='This issue may be caused by multi-table inheritance.',
  1711. obj=Child,
  1712. id='models.E016',
  1713. ),
  1714. ])
  1715. def test_unique_constraint_pointing_to_fk(self):
  1716. class Target(models.Model):
  1717. pass
  1718. class Model(models.Model):
  1719. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name='target_1')
  1720. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name='target_2')
  1721. class Meta:
  1722. constraints = [
  1723. models.UniqueConstraint(fields=['fk_1_id', 'fk_2'], name='name'),
  1724. ]
  1725. self.assertEqual(Model.check(databases=self.databases), [])
  1726. def test_unique_constraint_with_include(self):
  1727. class Model(models.Model):
  1728. age = models.IntegerField()
  1729. class Meta:
  1730. constraints = [
  1731. models.UniqueConstraint(
  1732. fields=['age'],
  1733. name='unique_age_include_id',
  1734. include=['id'],
  1735. ),
  1736. ]
  1737. errors = Model.check(databases=self.databases)
  1738. expected = [] if connection.features.supports_covering_indexes else [
  1739. Warning(
  1740. '%s does not support unique constraints with non-key columns.'
  1741. % connection.display_name,
  1742. hint=(
  1743. "A constraint won't be created. Silence this warning if "
  1744. "you don't care about it."
  1745. ),
  1746. obj=Model,
  1747. id='models.W039',
  1748. ),
  1749. ]
  1750. self.assertEqual(errors, expected)
  1751. def test_unique_constraint_with_include_required_db_features(self):
  1752. class Model(models.Model):
  1753. age = models.IntegerField()
  1754. class Meta:
  1755. required_db_features = {'supports_covering_indexes'}
  1756. constraints = [
  1757. models.UniqueConstraint(
  1758. fields=['age'],
  1759. name='unique_age_include_id',
  1760. include=['id'],
  1761. ),
  1762. ]
  1763. self.assertEqual(Model.check(databases=self.databases), [])
  1764. @skipUnlessDBFeature('supports_covering_indexes')
  1765. def test_unique_constraint_include_pointing_to_missing_field(self):
  1766. class Model(models.Model):
  1767. class Meta:
  1768. constraints = [
  1769. models.UniqueConstraint(
  1770. fields=['id'],
  1771. include=['missing_field'],
  1772. name='name',
  1773. ),
  1774. ]
  1775. self.assertEqual(Model.check(databases=self.databases), [
  1776. Error(
  1777. "'constraints' refers to the nonexistent field "
  1778. "'missing_field'.",
  1779. obj=Model,
  1780. id='models.E012',
  1781. ),
  1782. ])
  1783. @skipUnlessDBFeature('supports_covering_indexes')
  1784. def test_unique_constraint_include_pointing_to_m2m_field(self):
  1785. class Model(models.Model):
  1786. m2m = models.ManyToManyField('self')
  1787. class Meta:
  1788. constraints = [
  1789. models.UniqueConstraint(
  1790. fields=['id'],
  1791. include=['m2m'],
  1792. name='name',
  1793. ),
  1794. ]
  1795. self.assertEqual(Model.check(databases=self.databases), [
  1796. Error(
  1797. "'constraints' refers to a ManyToManyField 'm2m', but "
  1798. "ManyToManyFields are not permitted in 'constraints'.",
  1799. obj=Model,
  1800. id='models.E013',
  1801. ),
  1802. ])
  1803. @skipUnlessDBFeature('supports_covering_indexes')
  1804. def test_unique_constraint_include_pointing_to_non_local_field(self):
  1805. class Parent(models.Model):
  1806. field1 = models.IntegerField()
  1807. class Child(Parent):
  1808. field2 = models.IntegerField()
  1809. class Meta:
  1810. constraints = [
  1811. models.UniqueConstraint(
  1812. fields=['field2'],
  1813. include=['field1'],
  1814. name='name',
  1815. ),
  1816. ]
  1817. self.assertEqual(Child.check(databases=self.databases), [
  1818. Error(
  1819. "'constraints' refers to field 'field1' which is not local to "
  1820. "model 'Child'.",
  1821. hint='This issue may be caused by multi-table inheritance.',
  1822. obj=Child,
  1823. id='models.E016',
  1824. ),
  1825. ])
  1826. @skipUnlessDBFeature('supports_covering_indexes')
  1827. def test_unique_constraint_include_pointing_to_fk(self):
  1828. class Target(models.Model):
  1829. pass
  1830. class Model(models.Model):
  1831. fk_1 = models.ForeignKey(Target, models.CASCADE, related_name='target_1')
  1832. fk_2 = models.ForeignKey(Target, models.CASCADE, related_name='target_2')
  1833. class Meta:
  1834. constraints = [
  1835. models.UniqueConstraint(
  1836. fields=['id'],
  1837. include=['fk_1_id', 'fk_2'],
  1838. name='name',
  1839. ),
  1840. ]
  1841. self.assertEqual(Model.check(databases=self.databases), [])
  1842. def test_func_unique_constraint(self):
  1843. class Model(models.Model):
  1844. name = models.CharField(max_length=10)
  1845. class Meta:
  1846. constraints = [
  1847. models.UniqueConstraint(Lower('name'), name='lower_name_uq'),
  1848. ]
  1849. warn = Warning(
  1850. '%s does not support unique constraints on expressions.'
  1851. % connection.display_name,
  1852. hint=(
  1853. "A constraint won't be created. Silence this warning if you "
  1854. "don't care about it."
  1855. ),
  1856. obj=Model,
  1857. id='models.W044',
  1858. )
  1859. expected = [] if connection.features.supports_expression_indexes else [warn]
  1860. self.assertEqual(Model.check(databases=self.databases), expected)
  1861. def test_func_unique_constraint_required_db_features(self):
  1862. class Model(models.Model):
  1863. name = models.CharField(max_length=10)
  1864. class Meta:
  1865. constraints = [
  1866. models.UniqueConstraint(Lower('name'), name='lower_name_unq'),
  1867. ]
  1868. required_db_features = {'supports_expression_indexes'}
  1869. self.assertEqual(Model.check(databases=self.databases), [])
  1870. @skipUnlessDBFeature('supports_expression_indexes')
  1871. def test_func_unique_constraint_expression_custom_lookup(self):
  1872. class Model(models.Model):
  1873. height = models.IntegerField()
  1874. weight = models.IntegerField()
  1875. class Meta:
  1876. constraints = [
  1877. models.UniqueConstraint(
  1878. models.F('height') / (models.F('weight__abs') + models.Value(5)),
  1879. name='name',
  1880. ),
  1881. ]
  1882. with register_lookup(models.IntegerField, Abs):
  1883. self.assertEqual(Model.check(databases=self.databases), [])
  1884. @skipUnlessDBFeature('supports_expression_indexes')
  1885. def test_func_unique_constraint_pointing_to_missing_field(self):
  1886. class Model(models.Model):
  1887. class Meta:
  1888. constraints = [
  1889. models.UniqueConstraint(Lower('missing_field').desc(), name='name'),
  1890. ]
  1891. self.assertEqual(Model.check(databases=self.databases), [
  1892. Error(
  1893. "'constraints' refers to the nonexistent field "
  1894. "'missing_field'.",
  1895. obj=Model,
  1896. id='models.E012',
  1897. ),
  1898. ])
  1899. @skipUnlessDBFeature('supports_expression_indexes')
  1900. def test_func_unique_constraint_pointing_to_missing_field_nested(self):
  1901. class Model(models.Model):
  1902. class Meta:
  1903. constraints = [
  1904. models.UniqueConstraint(Abs(Round('missing_field')), name='name'),
  1905. ]
  1906. self.assertEqual(Model.check(databases=self.databases), [
  1907. Error(
  1908. "'constraints' refers to the nonexistent field "
  1909. "'missing_field'.",
  1910. obj=Model,
  1911. id='models.E012',
  1912. ),
  1913. ])
  1914. @skipUnlessDBFeature('supports_expression_indexes')
  1915. def test_func_unique_constraint_pointing_to_m2m_field(self):
  1916. class Model(models.Model):
  1917. m2m = models.ManyToManyField('self')
  1918. class Meta:
  1919. constraints = [models.UniqueConstraint(Lower('m2m'), name='name')]
  1920. self.assertEqual(Model.check(databases=self.databases), [
  1921. Error(
  1922. "'constraints' refers to a ManyToManyField 'm2m', but "
  1923. "ManyToManyFields are not permitted in 'constraints'.",
  1924. obj=Model,
  1925. id='models.E013',
  1926. ),
  1927. ])
  1928. @skipUnlessDBFeature('supports_expression_indexes')
  1929. def test_func_unique_constraint_pointing_to_non_local_field(self):
  1930. class Foo(models.Model):
  1931. field1 = models.CharField(max_length=15)
  1932. class Bar(Foo):
  1933. class Meta:
  1934. constraints = [models.UniqueConstraint(Lower('field1'), name='name')]
  1935. self.assertEqual(Bar.check(databases=self.databases), [
  1936. Error(
  1937. "'constraints' refers to field 'field1' which is not local to "
  1938. "model 'Bar'.",
  1939. hint='This issue may be caused by multi-table inheritance.',
  1940. obj=Bar,
  1941. id='models.E016',
  1942. ),
  1943. ])
  1944. @skipUnlessDBFeature('supports_expression_indexes')
  1945. def test_func_unique_constraint_pointing_to_fk(self):
  1946. class Foo(models.Model):
  1947. id = models.CharField(primary_key=True, max_length=255)
  1948. class Bar(models.Model):
  1949. foo_1 = models.ForeignKey(Foo, models.CASCADE, related_name='bar_1')
  1950. foo_2 = models.ForeignKey(Foo, models.CASCADE, related_name='bar_2')
  1951. class Meta:
  1952. constraints = [
  1953. models.UniqueConstraint(
  1954. Lower('foo_1_id'),
  1955. Lower('foo_2'),
  1956. name='name',
  1957. ),
  1958. ]
  1959. self.assertEqual(Bar.check(databases=self.databases), [])