test_models.py 24 KB

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