test_models.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. # -*- encoding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import unittest
  4. from django.conf import settings
  5. from django.core.checks import Error
  6. from django.db import connections, models
  7. from django.test import SimpleTestCase
  8. from django.test.utils import isolate_apps, override_settings
  9. def get_max_column_name_length():
  10. allowed_len = None
  11. db_alias = None
  12. for db in settings.DATABASES.keys():
  13. connection = connections[db]
  14. max_name_length = connection.ops.max_name_length()
  15. if max_name_length is None or connection.features.truncates_names:
  16. continue
  17. else:
  18. if allowed_len is None:
  19. allowed_len = max_name_length
  20. db_alias = db
  21. elif max_name_length < allowed_len:
  22. allowed_len = max_name_length
  23. db_alias = db
  24. return (allowed_len, db_alias)
  25. @isolate_apps('invalid_models_tests')
  26. class IndexTogetherTests(SimpleTestCase):
  27. def test_non_iterable(self):
  28. class Model(models.Model):
  29. class Meta:
  30. index_together = 42
  31. errors = Model.check()
  32. expected = [
  33. Error(
  34. "'index_together' must be a list or tuple.",
  35. obj=Model,
  36. id='models.E008',
  37. ),
  38. ]
  39. self.assertEqual(errors, expected)
  40. def test_non_list(self):
  41. class Model(models.Model):
  42. class Meta:
  43. index_together = 'not-a-list'
  44. errors = Model.check()
  45. expected = [
  46. Error(
  47. "'index_together' must be a list or tuple.",
  48. obj=Model,
  49. id='models.E008',
  50. ),
  51. ]
  52. self.assertEqual(errors, expected)
  53. def test_list_containing_non_iterable(self):
  54. class Model(models.Model):
  55. class Meta:
  56. index_together = [('a', 'b'), 42]
  57. errors = Model.check()
  58. expected = [
  59. Error(
  60. "All 'index_together' elements must be lists or tuples.",
  61. obj=Model,
  62. id='models.E009',
  63. ),
  64. ]
  65. self.assertEqual(errors, expected)
  66. def test_pointing_to_missing_field(self):
  67. class Model(models.Model):
  68. class Meta:
  69. index_together = [
  70. ["missing_field"],
  71. ]
  72. errors = Model.check()
  73. expected = [
  74. Error(
  75. "'index_together' refers to the non-existent field 'missing_field'.",
  76. obj=Model,
  77. id='models.E012',
  78. ),
  79. ]
  80. self.assertEqual(errors, expected)
  81. def test_pointing_to_non_local_field(self):
  82. class Foo(models.Model):
  83. field1 = models.IntegerField()
  84. class Bar(Foo):
  85. field2 = models.IntegerField()
  86. class Meta:
  87. index_together = [
  88. ["field2", "field1"],
  89. ]
  90. errors = Bar.check()
  91. expected = [
  92. Error(
  93. "'index_together' refers to field 'field1' which is not "
  94. "local to model 'Bar'.",
  95. hint=("This issue may be caused by multi-table inheritance."),
  96. obj=Bar,
  97. id='models.E016',
  98. ),
  99. ]
  100. self.assertEqual(errors, expected)
  101. def test_pointing_to_m2m_field(self):
  102. class Model(models.Model):
  103. m2m = models.ManyToManyField('self')
  104. class Meta:
  105. index_together = [
  106. ["m2m"],
  107. ]
  108. errors = Model.check()
  109. expected = [
  110. Error(
  111. "'index_together' refers to a ManyToManyField 'm2m', but "
  112. "ManyToManyFields are not permitted in 'index_together'.",
  113. obj=Model,
  114. id='models.E013',
  115. ),
  116. ]
  117. self.assertEqual(errors, expected)
  118. # unique_together tests are very similar to index_together tests.
  119. @isolate_apps('invalid_models_tests')
  120. class UniqueTogetherTests(SimpleTestCase):
  121. def test_non_iterable(self):
  122. class Model(models.Model):
  123. class Meta:
  124. unique_together = 42
  125. errors = Model.check()
  126. expected = [
  127. Error(
  128. "'unique_together' must be a list or tuple.",
  129. obj=Model,
  130. id='models.E010',
  131. ),
  132. ]
  133. self.assertEqual(errors, expected)
  134. def test_list_containing_non_iterable(self):
  135. class Model(models.Model):
  136. one = models.IntegerField()
  137. two = models.IntegerField()
  138. class Meta:
  139. unique_together = [('a', 'b'), 42]
  140. errors = Model.check()
  141. expected = [
  142. Error(
  143. "All 'unique_together' elements must be lists or tuples.",
  144. obj=Model,
  145. id='models.E011',
  146. ),
  147. ]
  148. self.assertEqual(errors, expected)
  149. def test_non_list(self):
  150. class Model(models.Model):
  151. class Meta:
  152. unique_together = 'not-a-list'
  153. errors = Model.check()
  154. expected = [
  155. Error(
  156. "'unique_together' must be a list or tuple.",
  157. obj=Model,
  158. id='models.E010',
  159. ),
  160. ]
  161. self.assertEqual(errors, expected)
  162. def test_valid_model(self):
  163. class Model(models.Model):
  164. one = models.IntegerField()
  165. two = models.IntegerField()
  166. class Meta:
  167. # unique_together can be a simple tuple
  168. unique_together = ('one', 'two')
  169. errors = Model.check()
  170. self.assertEqual(errors, [])
  171. def test_pointing_to_missing_field(self):
  172. class Model(models.Model):
  173. class Meta:
  174. unique_together = [
  175. ["missing_field"],
  176. ]
  177. errors = Model.check()
  178. expected = [
  179. Error(
  180. "'unique_together' refers to the non-existent field 'missing_field'.",
  181. obj=Model,
  182. id='models.E012',
  183. ),
  184. ]
  185. self.assertEqual(errors, expected)
  186. def test_pointing_to_m2m(self):
  187. class Model(models.Model):
  188. m2m = models.ManyToManyField('self')
  189. class Meta:
  190. unique_together = [
  191. ["m2m"],
  192. ]
  193. errors = Model.check()
  194. expected = [
  195. Error(
  196. "'unique_together' refers to a ManyToManyField 'm2m', but "
  197. "ManyToManyFields are not permitted in 'unique_together'.",
  198. obj=Model,
  199. id='models.E013',
  200. ),
  201. ]
  202. self.assertEqual(errors, expected)
  203. @isolate_apps('invalid_models_tests')
  204. class FieldNamesTests(SimpleTestCase):
  205. def test_ending_with_underscore(self):
  206. class Model(models.Model):
  207. field_ = models.CharField(max_length=10)
  208. m2m_ = models.ManyToManyField('self')
  209. errors = Model.check()
  210. expected = [
  211. Error(
  212. 'Field names must not end with an underscore.',
  213. obj=Model._meta.get_field('field_'),
  214. id='fields.E001',
  215. ),
  216. Error(
  217. 'Field names must not end with an underscore.',
  218. obj=Model._meta.get_field('m2m_'),
  219. id='fields.E001',
  220. ),
  221. ]
  222. self.assertEqual(errors, expected)
  223. max_column_name_length, column_limit_db_alias = get_max_column_name_length()
  224. @unittest.skipIf(max_column_name_length is None,
  225. "The database doesn't have a column name length limit.")
  226. def test_M2M_long_column_name(self):
  227. """
  228. #13711 -- Model check for long M2M column names when database has
  229. column name length limits.
  230. """
  231. allowed_len, db_alias = get_max_column_name_length()
  232. # A model with very long name which will be used to set relations to.
  233. class VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(models.Model):
  234. title = models.CharField(max_length=11)
  235. # Main model for which checks will be performed.
  236. class ModelWithLongField(models.Model):
  237. m2m_field = models.ManyToManyField(
  238. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  239. related_name="rn1"
  240. )
  241. m2m_field2 = models.ManyToManyField(
  242. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  243. related_name="rn2", through='m2msimple'
  244. )
  245. m2m_field3 = models.ManyToManyField(
  246. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  247. related_name="rn3",
  248. through='m2mcomplex'
  249. )
  250. fk = models.ForeignKey(
  251. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  252. models.CASCADE,
  253. related_name="rn4",
  254. )
  255. # Models used for setting `through` in M2M field.
  256. class m2msimple(models.Model):
  257. id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
  258. class m2mcomplex(models.Model):
  259. id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
  260. long_field_name = 'a' * (self.max_column_name_length + 1)
  261. models.ForeignKey(
  262. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  263. models.CASCADE,
  264. ).contribute_to_class(m2msimple, long_field_name)
  265. models.ForeignKey(
  266. VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
  267. models.CASCADE,
  268. db_column=long_field_name
  269. ).contribute_to_class(m2mcomplex, long_field_name)
  270. errors = ModelWithLongField.check()
  271. # First error because of M2M field set on the model with long name.
  272. m2m_long_name = "verylongmodelnamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz_id"
  273. if self.max_column_name_length > len(m2m_long_name):
  274. # Some databases support names longer than the test name.
  275. expected = []
  276. else:
  277. expected = [
  278. Error(
  279. 'Autogenerated column name too long for M2M field "%s". '
  280. 'Maximum length is "%s" for database "%s".'
  281. % (m2m_long_name, self.max_column_name_length, self.column_limit_db_alias),
  282. hint=("Use 'through' to create a separate model for "
  283. "M2M and then set column_name using 'db_column'."),
  284. obj=ModelWithLongField,
  285. id='models.E019',
  286. )
  287. ]
  288. # Second error because the FK specified in the `through` model
  289. # `m2msimple` has auto-generated name longer than allowed.
  290. # There will be no check errors in the other M2M because it
  291. # specifies db_column for the FK in `through` model even if the actual
  292. # name is longer than the limits of the database.
  293. expected.append(
  294. Error(
  295. 'Autogenerated column name too long for M2M field "%s_id". '
  296. 'Maximum length is "%s" for database "%s".'
  297. % (long_field_name, self.max_column_name_length, self.column_limit_db_alias),
  298. hint=("Use 'through' to create a separate model for "
  299. "M2M and then set column_name using 'db_column'."),
  300. obj=ModelWithLongField,
  301. id='models.E019',
  302. )
  303. )
  304. self.assertEqual(errors, expected)
  305. @unittest.skipIf(max_column_name_length is None,
  306. "The database doesn't have a column name length limit.")
  307. def test_local_field_long_column_name(self):
  308. """
  309. #13711 -- Model check for long column names
  310. when database does not support long names.
  311. """
  312. allowed_len, db_alias = get_max_column_name_length()
  313. class ModelWithLongField(models.Model):
  314. title = models.CharField(max_length=11)
  315. long_field_name = 'a' * (self.max_column_name_length + 1)
  316. long_field_name2 = 'b' * (self.max_column_name_length + 1)
  317. models.CharField(max_length=11).contribute_to_class(ModelWithLongField, long_field_name)
  318. models.CharField(max_length=11, db_column='vlmn').contribute_to_class(ModelWithLongField, long_field_name2)
  319. errors = ModelWithLongField.check()
  320. # Error because of the field with long name added to the model
  321. # without specifying db_column
  322. expected = [
  323. Error(
  324. 'Autogenerated column name too long for field "%s". '
  325. 'Maximum length is "%s" for database "%s".'
  326. % (long_field_name, self.max_column_name_length, self.column_limit_db_alias),
  327. hint="Set the column name manually using 'db_column'.",
  328. obj=ModelWithLongField,
  329. id='models.E018',
  330. )
  331. ]
  332. self.assertEqual(errors, expected)
  333. def test_including_separator(self):
  334. class Model(models.Model):
  335. some__field = models.IntegerField()
  336. errors = Model.check()
  337. expected = [
  338. Error(
  339. 'Field names must not contain "__".',
  340. obj=Model._meta.get_field('some__field'),
  341. id='fields.E002',
  342. )
  343. ]
  344. self.assertEqual(errors, expected)
  345. def test_pk(self):
  346. class Model(models.Model):
  347. pk = models.IntegerField()
  348. errors = Model.check()
  349. expected = [
  350. Error(
  351. "'pk' is a reserved word that cannot be used as a field name.",
  352. obj=Model._meta.get_field('pk'),
  353. id='fields.E003',
  354. )
  355. ]
  356. self.assertEqual(errors, expected)
  357. @isolate_apps('invalid_models_tests')
  358. class ShadowingFieldsTests(SimpleTestCase):
  359. def test_field_name_clash_with_child_accessor(self):
  360. class Parent(models.Model):
  361. pass
  362. class Child(Parent):
  363. child = models.CharField(max_length=100)
  364. errors = Child.check()
  365. expected = [
  366. Error(
  367. "The field 'child' clashes with the field "
  368. "'child' from model 'invalid_models_tests.parent'.",
  369. obj=Child._meta.get_field('child'),
  370. id='models.E006',
  371. )
  372. ]
  373. self.assertEqual(errors, expected)
  374. def test_multiinheritance_clash(self):
  375. class Mother(models.Model):
  376. clash = models.IntegerField()
  377. class Father(models.Model):
  378. clash = models.IntegerField()
  379. class Child(Mother, Father):
  380. # Here we have two clashed: id (automatic field) and clash, because
  381. # both parents define these fields.
  382. pass
  383. errors = Child.check()
  384. expected = [
  385. Error(
  386. "The field 'id' from parent model "
  387. "'invalid_models_tests.mother' clashes with the field 'id' "
  388. "from parent model 'invalid_models_tests.father'.",
  389. obj=Child,
  390. id='models.E005',
  391. ),
  392. Error(
  393. "The field 'clash' from parent model "
  394. "'invalid_models_tests.mother' clashes with the field 'clash' "
  395. "from parent model 'invalid_models_tests.father'.",
  396. obj=Child,
  397. id='models.E005',
  398. )
  399. ]
  400. self.assertEqual(errors, expected)
  401. def test_inheritance_clash(self):
  402. class Parent(models.Model):
  403. f_id = models.IntegerField()
  404. class Target(models.Model):
  405. # This field doesn't result in a clash.
  406. f_id = models.IntegerField()
  407. class Child(Parent):
  408. # This field clashes with parent "f_id" field.
  409. f = models.ForeignKey(Target, models.CASCADE)
  410. errors = Child.check()
  411. expected = [
  412. Error(
  413. "The field 'f' clashes with the field 'f_id' "
  414. "from model 'invalid_models_tests.parent'.",
  415. obj=Child._meta.get_field('f'),
  416. id='models.E006',
  417. )
  418. ]
  419. self.assertEqual(errors, expected)
  420. def test_multigeneration_inheritance(self):
  421. class GrandParent(models.Model):
  422. clash = models.IntegerField()
  423. class Parent(GrandParent):
  424. pass
  425. class Child(Parent):
  426. pass
  427. class GrandChild(Child):
  428. clash = models.IntegerField()
  429. errors = GrandChild.check()
  430. expected = [
  431. Error(
  432. "The field 'clash' clashes with the field 'clash' "
  433. "from model 'invalid_models_tests.grandparent'.",
  434. obj=GrandChild._meta.get_field('clash'),
  435. id='models.E006',
  436. )
  437. ]
  438. self.assertEqual(errors, expected)
  439. def test_id_clash(self):
  440. class Target(models.Model):
  441. pass
  442. class Model(models.Model):
  443. fk = models.ForeignKey(Target, models.CASCADE)
  444. fk_id = models.IntegerField()
  445. errors = Model.check()
  446. expected = [
  447. Error(
  448. "The field 'fk_id' clashes with the field 'fk' from model "
  449. "'invalid_models_tests.model'.",
  450. obj=Model._meta.get_field('fk_id'),
  451. id='models.E006',
  452. )
  453. ]
  454. self.assertEqual(errors, expected)
  455. @isolate_apps('invalid_models_tests')
  456. class OtherModelTests(SimpleTestCase):
  457. def test_unique_primary_key(self):
  458. invalid_id = models.IntegerField(primary_key=False)
  459. class Model(models.Model):
  460. id = invalid_id
  461. errors = Model.check()
  462. expected = [
  463. Error(
  464. "'id' can only be used as a field name if the field also sets "
  465. "'primary_key=True'.",
  466. obj=Model,
  467. id='models.E004',
  468. ),
  469. ]
  470. self.assertEqual(errors, expected)
  471. def test_ordering_non_iterable(self):
  472. class Model(models.Model):
  473. class Meta:
  474. ordering = "missing_field"
  475. errors = Model.check()
  476. expected = [
  477. Error(
  478. "'ordering' must be a tuple or list "
  479. "(even if you want to order by only one field).",
  480. obj=Model,
  481. id='models.E014',
  482. ),
  483. ]
  484. self.assertEqual(errors, expected)
  485. def test_just_ordering_no_errors(self):
  486. class Model(models.Model):
  487. order = models.PositiveIntegerField()
  488. class Meta:
  489. ordering = ['order']
  490. self.assertEqual(Model.check(), [])
  491. def test_just_order_with_respect_to_no_errors(self):
  492. class Question(models.Model):
  493. pass
  494. class Answer(models.Model):
  495. question = models.ForeignKey(Question, models.CASCADE)
  496. class Meta:
  497. order_with_respect_to = 'question'
  498. self.assertEqual(Answer.check(), [])
  499. def test_ordering_with_order_with_respect_to(self):
  500. class Question(models.Model):
  501. pass
  502. class Answer(models.Model):
  503. question = models.ForeignKey(Question, models.CASCADE)
  504. order = models.IntegerField()
  505. class Meta:
  506. order_with_respect_to = 'question'
  507. ordering = ['order']
  508. errors = Answer.check()
  509. expected = [
  510. Error(
  511. "'ordering' and 'order_with_respect_to' cannot be used together.",
  512. obj=Answer,
  513. id='models.E021',
  514. ),
  515. ]
  516. self.assertEqual(errors, expected)
  517. def test_non_valid(self):
  518. class RelationModel(models.Model):
  519. pass
  520. class Model(models.Model):
  521. relation = models.ManyToManyField(RelationModel)
  522. class Meta:
  523. ordering = ['relation']
  524. errors = Model.check()
  525. expected = [
  526. Error(
  527. "'ordering' refers to the non-existent field 'relation'.",
  528. obj=Model,
  529. id='models.E015',
  530. ),
  531. ]
  532. self.assertEqual(errors, expected)
  533. def test_ordering_pointing_to_missing_field(self):
  534. class Model(models.Model):
  535. class Meta:
  536. ordering = ("missing_field",)
  537. errors = Model.check()
  538. expected = [
  539. Error(
  540. "'ordering' refers to the non-existent field 'missing_field'.",
  541. obj=Model,
  542. id='models.E015',
  543. )
  544. ]
  545. self.assertEqual(errors, expected)
  546. def test_ordering_pointing_to_missing_foreignkey_field(self):
  547. # refs #22711
  548. class Model(models.Model):
  549. missing_fk_field = models.IntegerField()
  550. class Meta:
  551. ordering = ("missing_fk_field_id",)
  552. errors = Model.check()
  553. expected = [
  554. Error(
  555. "'ordering' refers to the non-existent field 'missing_fk_field_id'.",
  556. obj=Model,
  557. id='models.E015',
  558. )
  559. ]
  560. self.assertEqual(errors, expected)
  561. def test_ordering_pointing_to_existing_foreignkey_field(self):
  562. # refs #22711
  563. class Parent(models.Model):
  564. pass
  565. class Child(models.Model):
  566. parent = models.ForeignKey(Parent, models.CASCADE)
  567. class Meta:
  568. ordering = ("parent_id",)
  569. self.assertFalse(Child.check())
  570. @override_settings(TEST_SWAPPED_MODEL_BAD_VALUE='not-a-model')
  571. def test_swappable_missing_app_name(self):
  572. class Model(models.Model):
  573. class Meta:
  574. swappable = 'TEST_SWAPPED_MODEL_BAD_VALUE'
  575. errors = Model.check()
  576. expected = [
  577. Error(
  578. "'TEST_SWAPPED_MODEL_BAD_VALUE' is not of the form 'app_label.app_name'.",
  579. id='models.E001',
  580. ),
  581. ]
  582. self.assertEqual(errors, expected)
  583. @override_settings(TEST_SWAPPED_MODEL_BAD_MODEL='not_an_app.Target')
  584. def test_swappable_missing_app(self):
  585. class Model(models.Model):
  586. class Meta:
  587. swappable = 'TEST_SWAPPED_MODEL_BAD_MODEL'
  588. errors = Model.check()
  589. expected = [
  590. Error(
  591. "'TEST_SWAPPED_MODEL_BAD_MODEL' references 'not_an_app.Target', "
  592. 'which has not been installed, or is abstract.',
  593. id='models.E002',
  594. ),
  595. ]
  596. self.assertEqual(errors, expected)
  597. def test_two_m2m_through_same_relationship(self):
  598. class Person(models.Model):
  599. pass
  600. class Group(models.Model):
  601. primary = models.ManyToManyField(Person,
  602. through="Membership", related_name="primary")
  603. secondary = models.ManyToManyField(Person, through="Membership",
  604. related_name="secondary")
  605. class Membership(models.Model):
  606. person = models.ForeignKey(Person, models.CASCADE)
  607. group = models.ForeignKey(Group, models.CASCADE)
  608. errors = Group.check()
  609. expected = [
  610. Error(
  611. "The model has two many-to-many relations through "
  612. "the intermediate model 'invalid_models_tests.Membership'.",
  613. obj=Group,
  614. id='models.E003',
  615. )
  616. ]
  617. self.assertEqual(errors, expected)