test_relative_fields.py 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617
  1. from django.core.checks import Error, Warning as DjangoWarning
  2. from django.db import models
  3. from django.db.models.fields.related import ForeignObject
  4. from django.test.testcases import SimpleTestCase, skipIfDBFeature
  5. from django.test.utils import isolate_apps, override_settings
  6. @isolate_apps('invalid_models_tests')
  7. class RelativeFieldTests(SimpleTestCase):
  8. def test_valid_foreign_key_without_accessor(self):
  9. class Target(models.Model):
  10. # There would be a clash if Model.field installed an accessor.
  11. model = models.IntegerField()
  12. class Model(models.Model):
  13. field = models.ForeignKey(Target, models.CASCADE, related_name='+')
  14. field = Model._meta.get_field('field')
  15. errors = field.check()
  16. self.assertEqual(errors, [])
  17. def test_foreign_key_to_missing_model(self):
  18. # Model names are resolved when a model is being created, so we cannot
  19. # test relative fields in isolation and we need to attach them to a
  20. # model.
  21. class Model(models.Model):
  22. foreign_key = models.ForeignKey('Rel1', models.CASCADE)
  23. field = Model._meta.get_field('foreign_key')
  24. errors = field.check()
  25. expected = [
  26. Error(
  27. "Field defines a relation with model 'Rel1', "
  28. "which is either not installed, or is abstract.",
  29. obj=field,
  30. id='fields.E300',
  31. ),
  32. ]
  33. self.assertEqual(errors, expected)
  34. @isolate_apps('invalid_models_tests')
  35. def test_foreign_key_to_isolate_apps_model(self):
  36. """
  37. #25723 - Referenced model registration lookup should be run against the
  38. field's model registry.
  39. """
  40. class OtherModel(models.Model):
  41. pass
  42. class Model(models.Model):
  43. foreign_key = models.ForeignKey('OtherModel', models.CASCADE)
  44. field = Model._meta.get_field('foreign_key')
  45. self.assertEqual(field.check(from_model=Model), [])
  46. def test_many_to_many_to_missing_model(self):
  47. class Model(models.Model):
  48. m2m = models.ManyToManyField("Rel2")
  49. field = Model._meta.get_field('m2m')
  50. errors = field.check(from_model=Model)
  51. expected = [
  52. Error(
  53. "Field defines a relation with model 'Rel2', "
  54. "which is either not installed, or is abstract.",
  55. obj=field,
  56. id='fields.E300',
  57. ),
  58. ]
  59. self.assertEqual(errors, expected)
  60. @isolate_apps('invalid_models_tests')
  61. def test_many_to_many_to_isolate_apps_model(self):
  62. """
  63. #25723 - Referenced model registration lookup should be run against the
  64. field's model registry.
  65. """
  66. class OtherModel(models.Model):
  67. pass
  68. class Model(models.Model):
  69. m2m = models.ManyToManyField('OtherModel')
  70. field = Model._meta.get_field('m2m')
  71. self.assertEqual(field.check(from_model=Model), [])
  72. def test_many_to_many_with_limit_choices_auto_created_no_warning(self):
  73. class Model(models.Model):
  74. name = models.CharField(max_length=20)
  75. class ModelM2M(models.Model):
  76. m2m = models.ManyToManyField(Model, limit_choices_to={'name': 'test_name'})
  77. self.assertEqual(ModelM2M.check(), [])
  78. def test_many_to_many_with_useless_options(self):
  79. class Model(models.Model):
  80. name = models.CharField(max_length=20)
  81. class ModelM2M(models.Model):
  82. m2m = models.ManyToManyField(
  83. Model,
  84. null=True,
  85. validators=[lambda x: x],
  86. limit_choices_to={'name': 'test_name'},
  87. through='ThroughModel',
  88. through_fields=('modelm2m', 'model'),
  89. )
  90. class ThroughModel(models.Model):
  91. model = models.ForeignKey('Model', models.CASCADE)
  92. modelm2m = models.ForeignKey('ModelM2M', models.CASCADE)
  93. errors = ModelM2M.check()
  94. field = ModelM2M._meta.get_field('m2m')
  95. expected = [
  96. DjangoWarning(
  97. 'null has no effect on ManyToManyField.',
  98. obj=field,
  99. id='fields.W340',
  100. ),
  101. DjangoWarning(
  102. 'ManyToManyField does not support validators.',
  103. obj=field,
  104. id='fields.W341',
  105. ),
  106. DjangoWarning(
  107. 'limit_choices_to has no effect on ManyToManyField '
  108. 'with a through model.',
  109. obj=field,
  110. id='fields.W343',
  111. ),
  112. ]
  113. self.assertEqual(errors, expected)
  114. def test_ambiguous_relationship_model(self):
  115. class Person(models.Model):
  116. pass
  117. class Group(models.Model):
  118. field = models.ManyToManyField('Person', through="AmbiguousRelationship", related_name='tertiary')
  119. class AmbiguousRelationship(models.Model):
  120. # Too much foreign keys to Person.
  121. first_person = models.ForeignKey(Person, models.CASCADE, related_name="first")
  122. second_person = models.ForeignKey(Person, models.CASCADE, related_name="second")
  123. second_model = models.ForeignKey(Group, models.CASCADE)
  124. field = Group._meta.get_field('field')
  125. errors = field.check(from_model=Group)
  126. expected = [
  127. Error(
  128. "The model is used as an intermediate model by "
  129. "'invalid_models_tests.Group.field', but it has more than one "
  130. "foreign key to 'Person', which is ambiguous. You must specify "
  131. "which foreign key Django should use via the through_fields "
  132. "keyword argument.",
  133. hint=(
  134. 'If you want to create a recursive relationship, use '
  135. 'ForeignKey("self", symmetrical=False, through="AmbiguousRelationship").'
  136. ),
  137. obj=field,
  138. id='fields.E335',
  139. ),
  140. ]
  141. self.assertEqual(errors, expected)
  142. def test_relationship_model_with_foreign_key_to_wrong_model(self):
  143. class WrongModel(models.Model):
  144. pass
  145. class Person(models.Model):
  146. pass
  147. class Group(models.Model):
  148. members = models.ManyToManyField('Person', through="InvalidRelationship")
  149. class InvalidRelationship(models.Model):
  150. person = models.ForeignKey(Person, models.CASCADE)
  151. wrong_foreign_key = models.ForeignKey(WrongModel, models.CASCADE)
  152. # The last foreign key should point to Group model.
  153. field = Group._meta.get_field('members')
  154. errors = field.check(from_model=Group)
  155. expected = [
  156. Error(
  157. "The model is used as an intermediate model by "
  158. "'invalid_models_tests.Group.members', but it does not "
  159. "have a foreign key to 'Group' or 'Person'.",
  160. obj=InvalidRelationship,
  161. id='fields.E336',
  162. ),
  163. ]
  164. self.assertEqual(errors, expected)
  165. def test_relationship_model_missing_foreign_key(self):
  166. class Person(models.Model):
  167. pass
  168. class Group(models.Model):
  169. members = models.ManyToManyField('Person', through="InvalidRelationship")
  170. class InvalidRelationship(models.Model):
  171. group = models.ForeignKey(Group, models.CASCADE)
  172. # No foreign key to Person
  173. field = Group._meta.get_field('members')
  174. errors = field.check(from_model=Group)
  175. expected = [
  176. Error(
  177. "The model is used as an intermediate model by "
  178. "'invalid_models_tests.Group.members', but it does not have "
  179. "a foreign key to 'Group' or 'Person'.",
  180. obj=InvalidRelationship,
  181. id='fields.E336',
  182. ),
  183. ]
  184. self.assertEqual(errors, expected)
  185. def test_missing_relationship_model(self):
  186. class Person(models.Model):
  187. pass
  188. class Group(models.Model):
  189. members = models.ManyToManyField('Person', through="MissingM2MModel")
  190. field = Group._meta.get_field('members')
  191. errors = field.check(from_model=Group)
  192. expected = [
  193. Error(
  194. "Field specifies a many-to-many relation through model "
  195. "'MissingM2MModel', which has not been installed.",
  196. obj=field,
  197. id='fields.E331',
  198. ),
  199. ]
  200. self.assertEqual(errors, expected)
  201. def test_missing_relationship_model_on_model_check(self):
  202. class Person(models.Model):
  203. pass
  204. class Group(models.Model):
  205. members = models.ManyToManyField('Person', through='MissingM2MModel')
  206. self.assertEqual(Group.check(), [
  207. Error(
  208. "Field specifies a many-to-many relation through model "
  209. "'MissingM2MModel', which has not been installed.",
  210. obj=Group._meta.get_field('members'),
  211. id='fields.E331',
  212. ),
  213. ])
  214. @isolate_apps('invalid_models_tests')
  215. def test_many_to_many_through_isolate_apps_model(self):
  216. """
  217. #25723 - Through model registration lookup should be run against the
  218. field's model registry.
  219. """
  220. class GroupMember(models.Model):
  221. person = models.ForeignKey('Person', models.CASCADE)
  222. group = models.ForeignKey('Group', models.CASCADE)
  223. class Person(models.Model):
  224. pass
  225. class Group(models.Model):
  226. members = models.ManyToManyField('Person', through='GroupMember')
  227. field = Group._meta.get_field('members')
  228. self.assertEqual(field.check(from_model=Group), [])
  229. def test_symmetrical_self_referential_field(self):
  230. class Person(models.Model):
  231. # Implicit symmetrical=False.
  232. friends = models.ManyToManyField('self', through="Relationship")
  233. class Relationship(models.Model):
  234. first = models.ForeignKey(Person, models.CASCADE, related_name="rel_from_set")
  235. second = models.ForeignKey(Person, models.CASCADE, related_name="rel_to_set")
  236. field = Person._meta.get_field('friends')
  237. errors = field.check(from_model=Person)
  238. expected = [
  239. Error(
  240. 'Many-to-many fields with intermediate tables must not be symmetrical.',
  241. obj=field,
  242. id='fields.E332',
  243. ),
  244. ]
  245. self.assertEqual(errors, expected)
  246. def test_too_many_foreign_keys_in_self_referential_model(self):
  247. class Person(models.Model):
  248. friends = models.ManyToManyField('self', through="InvalidRelationship", symmetrical=False)
  249. class InvalidRelationship(models.Model):
  250. first = models.ForeignKey(Person, models.CASCADE, related_name="rel_from_set_2")
  251. second = models.ForeignKey(Person, models.CASCADE, related_name="rel_to_set_2")
  252. third = models.ForeignKey(Person, models.CASCADE, related_name="too_many_by_far")
  253. field = Person._meta.get_field('friends')
  254. errors = field.check(from_model=Person)
  255. expected = [
  256. Error(
  257. "The model is used as an intermediate model by "
  258. "'invalid_models_tests.Person.friends', but it has more than two "
  259. "foreign keys to 'Person', which is ambiguous. You must specify "
  260. "which two foreign keys Django should use via the through_fields "
  261. "keyword argument.",
  262. hint='Use through_fields to specify which two foreign keys Django should use.',
  263. obj=InvalidRelationship,
  264. id='fields.E333',
  265. ),
  266. ]
  267. self.assertEqual(errors, expected)
  268. def test_symmetric_self_reference_with_intermediate_table(self):
  269. class Person(models.Model):
  270. # Explicit symmetrical=True.
  271. friends = models.ManyToManyField('self', through="Relationship", symmetrical=True)
  272. class Relationship(models.Model):
  273. first = models.ForeignKey(Person, models.CASCADE, related_name="rel_from_set")
  274. second = models.ForeignKey(Person, models.CASCADE, related_name="rel_to_set")
  275. field = Person._meta.get_field('friends')
  276. errors = field.check(from_model=Person)
  277. expected = [
  278. Error(
  279. 'Many-to-many fields with intermediate tables must not be symmetrical.',
  280. obj=field,
  281. id='fields.E332',
  282. ),
  283. ]
  284. self.assertEqual(errors, expected)
  285. def test_symmetric_self_reference_with_intermediate_table_and_through_fields(self):
  286. """
  287. Using through_fields in a m2m with an intermediate model shouldn't
  288. mask its incompatibility with symmetry.
  289. """
  290. class Person(models.Model):
  291. # Explicit symmetrical=True.
  292. friends = models.ManyToManyField(
  293. 'self',
  294. symmetrical=True,
  295. through="Relationship",
  296. through_fields=('first', 'second'),
  297. )
  298. class Relationship(models.Model):
  299. first = models.ForeignKey(Person, models.CASCADE, related_name="rel_from_set")
  300. second = models.ForeignKey(Person, models.CASCADE, related_name="rel_to_set")
  301. referee = models.ForeignKey(Person, models.CASCADE, related_name="referred")
  302. field = Person._meta.get_field('friends')
  303. errors = field.check(from_model=Person)
  304. expected = [
  305. Error(
  306. 'Many-to-many fields with intermediate tables must not be symmetrical.',
  307. obj=field,
  308. id='fields.E332',
  309. ),
  310. ]
  311. self.assertEqual(errors, expected)
  312. def test_foreign_key_to_abstract_model(self):
  313. class AbstractModel(models.Model):
  314. class Meta:
  315. abstract = True
  316. class Model(models.Model):
  317. rel_string_foreign_key = models.ForeignKey('AbstractModel', models.CASCADE)
  318. rel_class_foreign_key = models.ForeignKey(AbstractModel, models.CASCADE)
  319. fields = [
  320. Model._meta.get_field('rel_string_foreign_key'),
  321. Model._meta.get_field('rel_class_foreign_key'),
  322. ]
  323. expected_error = Error(
  324. "Field defines a relation with model 'AbstractModel', "
  325. "which is either not installed, or is abstract.",
  326. id='fields.E300',
  327. )
  328. for field in fields:
  329. expected_error.obj = field
  330. errors = field.check()
  331. self.assertEqual(errors, [expected_error])
  332. def test_m2m_to_abstract_model(self):
  333. class AbstractModel(models.Model):
  334. class Meta:
  335. abstract = True
  336. class Model(models.Model):
  337. rel_string_m2m = models.ManyToManyField('AbstractModel')
  338. rel_class_m2m = models.ManyToManyField(AbstractModel)
  339. fields = [
  340. Model._meta.get_field('rel_string_m2m'),
  341. Model._meta.get_field('rel_class_m2m'),
  342. ]
  343. expected_error = Error(
  344. "Field defines a relation with model 'AbstractModel', "
  345. "which is either not installed, or is abstract.",
  346. id='fields.E300',
  347. )
  348. for field in fields:
  349. expected_error.obj = field
  350. errors = field.check(from_model=Model)
  351. self.assertEqual(errors, [expected_error])
  352. def test_unique_m2m(self):
  353. class Person(models.Model):
  354. name = models.CharField(max_length=5)
  355. class Group(models.Model):
  356. members = models.ManyToManyField('Person', unique=True)
  357. field = Group._meta.get_field('members')
  358. errors = field.check(from_model=Group)
  359. expected = [
  360. Error(
  361. 'ManyToManyFields cannot be unique.',
  362. obj=field,
  363. id='fields.E330',
  364. ),
  365. ]
  366. self.assertEqual(errors, expected)
  367. def test_foreign_key_to_non_unique_field(self):
  368. class Target(models.Model):
  369. bad = models.IntegerField() # No unique=True
  370. class Model(models.Model):
  371. foreign_key = models.ForeignKey('Target', models.CASCADE, to_field='bad')
  372. field = Model._meta.get_field('foreign_key')
  373. errors = field.check()
  374. expected = [
  375. Error(
  376. "'Target.bad' must set unique=True because it is referenced by a foreign key.",
  377. obj=field,
  378. id='fields.E311',
  379. ),
  380. ]
  381. self.assertEqual(errors, expected)
  382. def test_foreign_key_to_non_unique_field_under_explicit_model(self):
  383. class Target(models.Model):
  384. bad = models.IntegerField()
  385. class Model(models.Model):
  386. field = models.ForeignKey(Target, models.CASCADE, to_field='bad')
  387. field = Model._meta.get_field('field')
  388. errors = field.check()
  389. expected = [
  390. Error(
  391. "'Target.bad' must set unique=True because it is referenced by a foreign key.",
  392. obj=field,
  393. id='fields.E311',
  394. ),
  395. ]
  396. self.assertEqual(errors, expected)
  397. def test_foreign_object_to_non_unique_fields(self):
  398. class Person(models.Model):
  399. # Note that both fields are not unique.
  400. country_id = models.IntegerField()
  401. city_id = models.IntegerField()
  402. class MMembership(models.Model):
  403. person_country_id = models.IntegerField()
  404. person_city_id = models.IntegerField()
  405. person = models.ForeignObject(
  406. Person,
  407. on_delete=models.CASCADE,
  408. from_fields=['person_country_id', 'person_city_id'],
  409. to_fields=['country_id', 'city_id'],
  410. )
  411. field = MMembership._meta.get_field('person')
  412. errors = field.check()
  413. expected = [
  414. Error(
  415. "No subset of the fields 'country_id', 'city_id' on model 'Person' is unique.",
  416. hint=(
  417. "Add unique=True on any of those fields or add at least "
  418. "a subset of them to a unique_together constraint."
  419. ),
  420. obj=field,
  421. id='fields.E310',
  422. )
  423. ]
  424. self.assertEqual(errors, expected)
  425. def test_on_delete_set_null_on_non_nullable_field(self):
  426. class Person(models.Model):
  427. pass
  428. class Model(models.Model):
  429. foreign_key = models.ForeignKey('Person', models.SET_NULL)
  430. field = Model._meta.get_field('foreign_key')
  431. errors = field.check()
  432. expected = [
  433. Error(
  434. 'Field specifies on_delete=SET_NULL, but cannot be null.',
  435. hint='Set null=True argument on the field, or change the on_delete rule.',
  436. obj=field,
  437. id='fields.E320',
  438. ),
  439. ]
  440. self.assertEqual(errors, expected)
  441. def test_on_delete_set_default_without_default_value(self):
  442. class Person(models.Model):
  443. pass
  444. class Model(models.Model):
  445. foreign_key = models.ForeignKey('Person', models.SET_DEFAULT)
  446. field = Model._meta.get_field('foreign_key')
  447. errors = field.check()
  448. expected = [
  449. Error(
  450. 'Field specifies on_delete=SET_DEFAULT, but has no default value.',
  451. hint='Set a default value, or change the on_delete rule.',
  452. obj=field,
  453. id='fields.E321',
  454. ),
  455. ]
  456. self.assertEqual(errors, expected)
  457. @skipIfDBFeature('interprets_empty_strings_as_nulls')
  458. def test_nullable_primary_key(self):
  459. class Model(models.Model):
  460. field = models.IntegerField(primary_key=True, null=True)
  461. field = Model._meta.get_field('field')
  462. errors = field.check()
  463. expected = [
  464. Error(
  465. 'Primary keys must not have null=True.',
  466. hint='Set null=False on the field, or remove primary_key=True argument.',
  467. obj=field,
  468. id='fields.E007',
  469. ),
  470. ]
  471. self.assertEqual(errors, expected)
  472. def test_not_swapped_model(self):
  473. class SwappableModel(models.Model):
  474. # A model that can be, but isn't swapped out. References to this
  475. # model should *not* raise any validation error.
  476. class Meta:
  477. swappable = 'TEST_SWAPPABLE_MODEL'
  478. class Model(models.Model):
  479. explicit_fk = models.ForeignKey(
  480. SwappableModel,
  481. models.CASCADE,
  482. related_name='explicit_fk',
  483. )
  484. implicit_fk = models.ForeignKey(
  485. 'invalid_models_tests.SwappableModel',
  486. models.CASCADE,
  487. related_name='implicit_fk',
  488. )
  489. explicit_m2m = models.ManyToManyField(SwappableModel, related_name='explicit_m2m')
  490. implicit_m2m = models.ManyToManyField(
  491. 'invalid_models_tests.SwappableModel',
  492. related_name='implicit_m2m',
  493. )
  494. explicit_fk = Model._meta.get_field('explicit_fk')
  495. self.assertEqual(explicit_fk.check(), [])
  496. implicit_fk = Model._meta.get_field('implicit_fk')
  497. self.assertEqual(implicit_fk.check(), [])
  498. explicit_m2m = Model._meta.get_field('explicit_m2m')
  499. self.assertEqual(explicit_m2m.check(from_model=Model), [])
  500. implicit_m2m = Model._meta.get_field('implicit_m2m')
  501. self.assertEqual(implicit_m2m.check(from_model=Model), [])
  502. @override_settings(TEST_SWAPPED_MODEL='invalid_models_tests.Replacement')
  503. def test_referencing_to_swapped_model(self):
  504. class Replacement(models.Model):
  505. pass
  506. class SwappedModel(models.Model):
  507. class Meta:
  508. swappable = 'TEST_SWAPPED_MODEL'
  509. class Model(models.Model):
  510. explicit_fk = models.ForeignKey(
  511. SwappedModel,
  512. models.CASCADE,
  513. related_name='explicit_fk',
  514. )
  515. implicit_fk = models.ForeignKey(
  516. 'invalid_models_tests.SwappedModel',
  517. models.CASCADE,
  518. related_name='implicit_fk',
  519. )
  520. explicit_m2m = models.ManyToManyField(SwappedModel, related_name='explicit_m2m')
  521. implicit_m2m = models.ManyToManyField(
  522. 'invalid_models_tests.SwappedModel',
  523. related_name='implicit_m2m',
  524. )
  525. fields = [
  526. Model._meta.get_field('explicit_fk'),
  527. Model._meta.get_field('implicit_fk'),
  528. Model._meta.get_field('explicit_m2m'),
  529. Model._meta.get_field('implicit_m2m'),
  530. ]
  531. expected_error = Error(
  532. ("Field defines a relation with the model "
  533. "'invalid_models_tests.SwappedModel', which has been swapped out."),
  534. hint="Update the relation to point at 'settings.TEST_SWAPPED_MODEL'.",
  535. id='fields.E301',
  536. )
  537. for field in fields:
  538. expected_error.obj = field
  539. errors = field.check(from_model=Model)
  540. self.assertEqual(errors, [expected_error])
  541. def test_related_field_has_invalid_related_name(self):
  542. digit = 0
  543. illegal_non_alphanumeric = '!'
  544. whitespace = '\t'
  545. invalid_related_names = [
  546. '%s_begins_with_digit' % digit,
  547. '%s_begins_with_illegal_non_alphanumeric' % illegal_non_alphanumeric,
  548. '%s_begins_with_whitespace' % whitespace,
  549. 'contains_%s_illegal_non_alphanumeric' % illegal_non_alphanumeric,
  550. 'contains_%s_whitespace' % whitespace,
  551. 'ends_with_with_illegal_non_alphanumeric_%s' % illegal_non_alphanumeric,
  552. 'ends_with_whitespace_%s' % whitespace,
  553. 'with', # a Python keyword
  554. 'related_name\n',
  555. '',
  556. ',', # non-ASCII
  557. ]
  558. class Parent(models.Model):
  559. pass
  560. for invalid_related_name in invalid_related_names:
  561. Child = type('Child%s' % invalid_related_name, (models.Model,), {
  562. 'parent': models.ForeignKey('Parent', models.CASCADE, related_name=invalid_related_name),
  563. '__module__': Parent.__module__,
  564. })
  565. field = Child._meta.get_field('parent')
  566. errors = Child.check()
  567. expected = [
  568. Error(
  569. "The name '%s' is invalid related_name for field Child%s.parent"
  570. % (invalid_related_name, invalid_related_name),
  571. hint="Related name must be a valid Python identifier or end with a '+'",
  572. obj=field,
  573. id='fields.E306',
  574. ),
  575. ]
  576. self.assertEqual(errors, expected)
  577. def test_related_field_has_valid_related_name(self):
  578. lowercase = 'a'
  579. uppercase = 'A'
  580. digit = 0
  581. related_names = [
  582. '%s_starts_with_lowercase' % lowercase,
  583. '%s_tarts_with_uppercase' % uppercase,
  584. '_starts_with_underscore',
  585. 'contains_%s_digit' % digit,
  586. 'ends_with_plus+',
  587. '_+',
  588. '+',
  589. '試',
  590. '試驗+',
  591. ]
  592. class Parent(models.Model):
  593. pass
  594. for related_name in related_names:
  595. Child = type('Child%s' % related_name, (models.Model,), {
  596. 'parent': models.ForeignKey('Parent', models.CASCADE, related_name=related_name),
  597. '__module__': Parent.__module__,
  598. })
  599. errors = Child.check()
  600. self.assertFalse(errors)
  601. def test_to_fields_exist(self):
  602. class Parent(models.Model):
  603. pass
  604. class Child(models.Model):
  605. a = models.PositiveIntegerField()
  606. b = models.PositiveIntegerField()
  607. parent = ForeignObject(
  608. Parent,
  609. on_delete=models.SET_NULL,
  610. from_fields=('a', 'b'),
  611. to_fields=('a', 'b'),
  612. )
  613. field = Child._meta.get_field('parent')
  614. expected = [
  615. Error(
  616. "The to_field 'a' doesn't exist on the related model 'invalid_models_tests.Parent'.",
  617. obj=field,
  618. id='fields.E312',
  619. ),
  620. Error(
  621. "The to_field 'b' doesn't exist on the related model 'invalid_models_tests.Parent'.",
  622. obj=field,
  623. id='fields.E312',
  624. ),
  625. ]
  626. self.assertEqual(field.check(), expected)
  627. def test_to_fields_not_checked_if_related_model_doesnt_exist(self):
  628. class Child(models.Model):
  629. a = models.PositiveIntegerField()
  630. b = models.PositiveIntegerField()
  631. parent = ForeignObject(
  632. 'invalid_models_tests.Parent',
  633. on_delete=models.SET_NULL,
  634. from_fields=('a', 'b'),
  635. to_fields=('a', 'b'),
  636. )
  637. field = Child._meta.get_field('parent')
  638. self.assertEqual(field.check(), [
  639. Error(
  640. "Field defines a relation with model 'invalid_models_tests.Parent', "
  641. "which is either not installed, or is abstract.",
  642. id='fields.E300',
  643. obj=field,
  644. ),
  645. ])
  646. def test_invalid_related_query_name(self):
  647. class Target(models.Model):
  648. pass
  649. class Model(models.Model):
  650. first = models.ForeignKey(Target, models.CASCADE, related_name='contains__double')
  651. second = models.ForeignKey(Target, models.CASCADE, related_query_name='ends_underscore_')
  652. self.assertEqual(Model.check(), [
  653. Error(
  654. "Reverse query name 'contains__double' must not contain '__'.",
  655. hint=("Add or change a related_name or related_query_name "
  656. "argument for this field."),
  657. obj=Model._meta.get_field('first'),
  658. id='fields.E309',
  659. ),
  660. Error(
  661. "Reverse query name 'ends_underscore_' must not end with an "
  662. "underscore.",
  663. hint=("Add or change a related_name or related_query_name "
  664. "argument for this field."),
  665. obj=Model._meta.get_field('second'),
  666. id='fields.E308',
  667. ),
  668. ])
  669. @isolate_apps('invalid_models_tests')
  670. class AccessorClashTests(SimpleTestCase):
  671. def test_fk_to_integer(self):
  672. self._test_accessor_clash(
  673. target=models.IntegerField(),
  674. relative=models.ForeignKey('Target', models.CASCADE))
  675. def test_fk_to_fk(self):
  676. self._test_accessor_clash(
  677. target=models.ForeignKey('Another', models.CASCADE),
  678. relative=models.ForeignKey('Target', models.CASCADE))
  679. def test_fk_to_m2m(self):
  680. self._test_accessor_clash(
  681. target=models.ManyToManyField('Another'),
  682. relative=models.ForeignKey('Target', models.CASCADE))
  683. def test_m2m_to_integer(self):
  684. self._test_accessor_clash(
  685. target=models.IntegerField(),
  686. relative=models.ManyToManyField('Target'))
  687. def test_m2m_to_fk(self):
  688. self._test_accessor_clash(
  689. target=models.ForeignKey('Another', models.CASCADE),
  690. relative=models.ManyToManyField('Target'))
  691. def test_m2m_to_m2m(self):
  692. self._test_accessor_clash(
  693. target=models.ManyToManyField('Another'),
  694. relative=models.ManyToManyField('Target'))
  695. def _test_accessor_clash(self, target, relative):
  696. class Another(models.Model):
  697. pass
  698. class Target(models.Model):
  699. model_set = target
  700. class Model(models.Model):
  701. rel = relative
  702. errors = Model.check()
  703. expected = [
  704. Error(
  705. "Reverse accessor for 'Model.rel' clashes with field name 'Target.model_set'.",
  706. hint=("Rename field 'Target.model_set', or add/change "
  707. "a related_name argument to the definition "
  708. "for field 'Model.rel'."),
  709. obj=Model._meta.get_field('rel'),
  710. id='fields.E302',
  711. ),
  712. ]
  713. self.assertEqual(errors, expected)
  714. def test_clash_between_accessors(self):
  715. class Target(models.Model):
  716. pass
  717. class Model(models.Model):
  718. foreign = models.ForeignKey(Target, models.CASCADE)
  719. m2m = models.ManyToManyField(Target)
  720. errors = Model.check()
  721. expected = [
  722. Error(
  723. "Reverse accessor for 'Model.foreign' clashes with reverse accessor for 'Model.m2m'.",
  724. hint=(
  725. "Add or change a related_name argument to the definition "
  726. "for 'Model.foreign' or 'Model.m2m'."
  727. ),
  728. obj=Model._meta.get_field('foreign'),
  729. id='fields.E304',
  730. ),
  731. Error(
  732. "Reverse accessor for 'Model.m2m' clashes with reverse accessor for 'Model.foreign'.",
  733. hint=(
  734. "Add or change a related_name argument to the definition "
  735. "for 'Model.m2m' or 'Model.foreign'."
  736. ),
  737. obj=Model._meta.get_field('m2m'),
  738. id='fields.E304',
  739. ),
  740. ]
  741. self.assertEqual(errors, expected)
  742. def test_m2m_to_m2m_with_inheritance(self):
  743. """ Ref #22047. """
  744. class Target(models.Model):
  745. pass
  746. class Model(models.Model):
  747. children = models.ManyToManyField('Child', related_name="m2m_clash", related_query_name="no_clash")
  748. class Parent(models.Model):
  749. m2m_clash = models.ManyToManyField('Target')
  750. class Child(Parent):
  751. pass
  752. errors = Model.check()
  753. expected = [
  754. Error(
  755. "Reverse accessor for 'Model.children' clashes with field name 'Child.m2m_clash'.",
  756. hint=(
  757. "Rename field 'Child.m2m_clash', or add/change a related_name "
  758. "argument to the definition for field 'Model.children'."
  759. ),
  760. obj=Model._meta.get_field('children'),
  761. id='fields.E302',
  762. )
  763. ]
  764. self.assertEqual(errors, expected)
  765. def test_no_clash_for_hidden_related_name(self):
  766. class Stub(models.Model):
  767. pass
  768. class ManyToManyRel(models.Model):
  769. thing1 = models.ManyToManyField(Stub, related_name='+')
  770. thing2 = models.ManyToManyField(Stub, related_name='+')
  771. class FKRel(models.Model):
  772. thing1 = models.ForeignKey(Stub, models.CASCADE, related_name='+')
  773. thing2 = models.ForeignKey(Stub, models.CASCADE, related_name='+')
  774. self.assertEqual(ManyToManyRel.check(), [])
  775. self.assertEqual(FKRel.check(), [])
  776. @isolate_apps('invalid_models_tests')
  777. class ReverseQueryNameClashTests(SimpleTestCase):
  778. def test_fk_to_integer(self):
  779. self._test_reverse_query_name_clash(
  780. target=models.IntegerField(),
  781. relative=models.ForeignKey('Target', models.CASCADE))
  782. def test_fk_to_fk(self):
  783. self._test_reverse_query_name_clash(
  784. target=models.ForeignKey('Another', models.CASCADE),
  785. relative=models.ForeignKey('Target', models.CASCADE))
  786. def test_fk_to_m2m(self):
  787. self._test_reverse_query_name_clash(
  788. target=models.ManyToManyField('Another'),
  789. relative=models.ForeignKey('Target', models.CASCADE))
  790. def test_m2m_to_integer(self):
  791. self._test_reverse_query_name_clash(
  792. target=models.IntegerField(),
  793. relative=models.ManyToManyField('Target'))
  794. def test_m2m_to_fk(self):
  795. self._test_reverse_query_name_clash(
  796. target=models.ForeignKey('Another', models.CASCADE),
  797. relative=models.ManyToManyField('Target'))
  798. def test_m2m_to_m2m(self):
  799. self._test_reverse_query_name_clash(
  800. target=models.ManyToManyField('Another'),
  801. relative=models.ManyToManyField('Target'))
  802. def _test_reverse_query_name_clash(self, target, relative):
  803. class Another(models.Model):
  804. pass
  805. class Target(models.Model):
  806. model = target
  807. class Model(models.Model):
  808. rel = relative
  809. errors = Model.check()
  810. expected = [
  811. Error(
  812. "Reverse query name for 'Model.rel' clashes with field name 'Target.model'.",
  813. hint=(
  814. "Rename field 'Target.model', or add/change a related_name "
  815. "argument to the definition for field 'Model.rel'."
  816. ),
  817. obj=Model._meta.get_field('rel'),
  818. id='fields.E303',
  819. ),
  820. ]
  821. self.assertEqual(errors, expected)
  822. @isolate_apps('invalid_models_tests')
  823. class ExplicitRelatedNameClashTests(SimpleTestCase):
  824. def test_fk_to_integer(self):
  825. self._test_explicit_related_name_clash(
  826. target=models.IntegerField(),
  827. relative=models.ForeignKey('Target', models.CASCADE, related_name='clash'))
  828. def test_fk_to_fk(self):
  829. self._test_explicit_related_name_clash(
  830. target=models.ForeignKey('Another', models.CASCADE),
  831. relative=models.ForeignKey('Target', models.CASCADE, related_name='clash'))
  832. def test_fk_to_m2m(self):
  833. self._test_explicit_related_name_clash(
  834. target=models.ManyToManyField('Another'),
  835. relative=models.ForeignKey('Target', models.CASCADE, related_name='clash'))
  836. def test_m2m_to_integer(self):
  837. self._test_explicit_related_name_clash(
  838. target=models.IntegerField(),
  839. relative=models.ManyToManyField('Target', related_name='clash'))
  840. def test_m2m_to_fk(self):
  841. self._test_explicit_related_name_clash(
  842. target=models.ForeignKey('Another', models.CASCADE),
  843. relative=models.ManyToManyField('Target', related_name='clash'))
  844. def test_m2m_to_m2m(self):
  845. self._test_explicit_related_name_clash(
  846. target=models.ManyToManyField('Another'),
  847. relative=models.ManyToManyField('Target', related_name='clash'))
  848. def _test_explicit_related_name_clash(self, target, relative):
  849. class Another(models.Model):
  850. pass
  851. class Target(models.Model):
  852. clash = target
  853. class Model(models.Model):
  854. rel = relative
  855. errors = Model.check()
  856. expected = [
  857. Error(
  858. "Reverse accessor for 'Model.rel' clashes with field name 'Target.clash'.",
  859. hint=(
  860. "Rename field 'Target.clash', or add/change a related_name "
  861. "argument to the definition for field 'Model.rel'."
  862. ),
  863. obj=Model._meta.get_field('rel'),
  864. id='fields.E302',
  865. ),
  866. Error(
  867. "Reverse query name for 'Model.rel' clashes with field name 'Target.clash'.",
  868. hint=(
  869. "Rename field 'Target.clash', or add/change a related_name "
  870. "argument to the definition for field 'Model.rel'."
  871. ),
  872. obj=Model._meta.get_field('rel'),
  873. id='fields.E303',
  874. ),
  875. ]
  876. self.assertEqual(errors, expected)
  877. @isolate_apps('invalid_models_tests')
  878. class ExplicitRelatedQueryNameClashTests(SimpleTestCase):
  879. def test_fk_to_integer(self, related_name=None):
  880. self._test_explicit_related_query_name_clash(
  881. target=models.IntegerField(),
  882. relative=models.ForeignKey(
  883. 'Target',
  884. models.CASCADE,
  885. related_name=related_name,
  886. related_query_name='clash',
  887. )
  888. )
  889. def test_hidden_fk_to_integer(self, related_name=None):
  890. self.test_fk_to_integer(related_name='+')
  891. def test_fk_to_fk(self, related_name=None):
  892. self._test_explicit_related_query_name_clash(
  893. target=models.ForeignKey('Another', models.CASCADE),
  894. relative=models.ForeignKey(
  895. 'Target',
  896. models.CASCADE,
  897. related_name=related_name,
  898. related_query_name='clash',
  899. )
  900. )
  901. def test_hidden_fk_to_fk(self):
  902. self.test_fk_to_fk(related_name='+')
  903. def test_fk_to_m2m(self, related_name=None):
  904. self._test_explicit_related_query_name_clash(
  905. target=models.ManyToManyField('Another'),
  906. relative=models.ForeignKey(
  907. 'Target',
  908. models.CASCADE,
  909. related_name=related_name,
  910. related_query_name='clash',
  911. )
  912. )
  913. def test_hidden_fk_to_m2m(self):
  914. self.test_fk_to_m2m(related_name='+')
  915. def test_m2m_to_integer(self, related_name=None):
  916. self._test_explicit_related_query_name_clash(
  917. target=models.IntegerField(),
  918. relative=models.ManyToManyField('Target', related_name=related_name, related_query_name='clash'))
  919. def test_hidden_m2m_to_integer(self):
  920. self.test_m2m_to_integer(related_name='+')
  921. def test_m2m_to_fk(self, related_name=None):
  922. self._test_explicit_related_query_name_clash(
  923. target=models.ForeignKey('Another', models.CASCADE),
  924. relative=models.ManyToManyField('Target', related_name=related_name, related_query_name='clash'))
  925. def test_hidden_m2m_to_fk(self):
  926. self.test_m2m_to_fk(related_name='+')
  927. def test_m2m_to_m2m(self, related_name=None):
  928. self._test_explicit_related_query_name_clash(
  929. target=models.ManyToManyField('Another'),
  930. relative=models.ManyToManyField(
  931. 'Target',
  932. related_name=related_name,
  933. related_query_name='clash',
  934. )
  935. )
  936. def test_hidden_m2m_to_m2m(self):
  937. self.test_m2m_to_m2m(related_name='+')
  938. def _test_explicit_related_query_name_clash(self, target, relative):
  939. class Another(models.Model):
  940. pass
  941. class Target(models.Model):
  942. clash = target
  943. class Model(models.Model):
  944. rel = relative
  945. errors = Model.check()
  946. expected = [
  947. Error(
  948. "Reverse query name for 'Model.rel' clashes with field name 'Target.clash'.",
  949. hint=(
  950. "Rename field 'Target.clash', or add/change a related_name "
  951. "argument to the definition for field 'Model.rel'."
  952. ),
  953. obj=Model._meta.get_field('rel'),
  954. id='fields.E303',
  955. ),
  956. ]
  957. self.assertEqual(errors, expected)
  958. @isolate_apps('invalid_models_tests')
  959. class SelfReferentialM2MClashTests(SimpleTestCase):
  960. def test_clash_between_accessors(self):
  961. class Model(models.Model):
  962. first_m2m = models.ManyToManyField('self', symmetrical=False)
  963. second_m2m = models.ManyToManyField('self', symmetrical=False)
  964. errors = Model.check()
  965. expected = [
  966. Error(
  967. "Reverse accessor for 'Model.first_m2m' clashes with reverse accessor for 'Model.second_m2m'.",
  968. hint=(
  969. "Add or change a related_name argument to the definition "
  970. "for 'Model.first_m2m' or 'Model.second_m2m'."
  971. ),
  972. obj=Model._meta.get_field('first_m2m'),
  973. id='fields.E304',
  974. ),
  975. Error(
  976. "Reverse accessor for 'Model.second_m2m' clashes with reverse accessor for 'Model.first_m2m'.",
  977. hint=(
  978. "Add or change a related_name argument to the definition "
  979. "for 'Model.second_m2m' or 'Model.first_m2m'."
  980. ),
  981. obj=Model._meta.get_field('second_m2m'),
  982. id='fields.E304',
  983. ),
  984. ]
  985. self.assertEqual(errors, expected)
  986. def test_accessor_clash(self):
  987. class Model(models.Model):
  988. model_set = models.ManyToManyField("self", symmetrical=False)
  989. errors = Model.check()
  990. expected = [
  991. Error(
  992. "Reverse accessor for 'Model.model_set' clashes with field name 'Model.model_set'.",
  993. hint=(
  994. "Rename field 'Model.model_set', or add/change a related_name "
  995. "argument to the definition for field 'Model.model_set'."
  996. ),
  997. obj=Model._meta.get_field('model_set'),
  998. id='fields.E302',
  999. ),
  1000. ]
  1001. self.assertEqual(errors, expected)
  1002. def test_reverse_query_name_clash(self):
  1003. class Model(models.Model):
  1004. model = models.ManyToManyField("self", symmetrical=False)
  1005. errors = Model.check()
  1006. expected = [
  1007. Error(
  1008. "Reverse query name for 'Model.model' clashes with field name 'Model.model'.",
  1009. hint=(
  1010. "Rename field 'Model.model', or add/change a related_name "
  1011. "argument to the definition for field 'Model.model'."
  1012. ),
  1013. obj=Model._meta.get_field('model'),
  1014. id='fields.E303',
  1015. ),
  1016. ]
  1017. self.assertEqual(errors, expected)
  1018. def test_clash_under_explicit_related_name(self):
  1019. class Model(models.Model):
  1020. clash = models.IntegerField()
  1021. m2m = models.ManyToManyField("self", symmetrical=False, related_name='clash')
  1022. errors = Model.check()
  1023. expected = [
  1024. Error(
  1025. "Reverse accessor for 'Model.m2m' clashes with field name 'Model.clash'.",
  1026. hint=(
  1027. "Rename field 'Model.clash', or add/change a related_name "
  1028. "argument to the definition for field 'Model.m2m'."
  1029. ),
  1030. obj=Model._meta.get_field('m2m'),
  1031. id='fields.E302',
  1032. ),
  1033. Error(
  1034. "Reverse query name for 'Model.m2m' clashes with field name 'Model.clash'.",
  1035. hint=(
  1036. "Rename field 'Model.clash', or add/change a related_name "
  1037. "argument to the definition for field 'Model.m2m'."
  1038. ),
  1039. obj=Model._meta.get_field('m2m'),
  1040. id='fields.E303',
  1041. ),
  1042. ]
  1043. self.assertEqual(errors, expected)
  1044. def test_valid_model(self):
  1045. class Model(models.Model):
  1046. first = models.ManyToManyField("self", symmetrical=False, related_name='first_accessor')
  1047. second = models.ManyToManyField("self", symmetrical=False, related_name='second_accessor')
  1048. errors = Model.check()
  1049. self.assertEqual(errors, [])
  1050. @isolate_apps('invalid_models_tests')
  1051. class SelfReferentialFKClashTests(SimpleTestCase):
  1052. def test_accessor_clash(self):
  1053. class Model(models.Model):
  1054. model_set = models.ForeignKey("Model", models.CASCADE)
  1055. errors = Model.check()
  1056. expected = [
  1057. Error(
  1058. "Reverse accessor for 'Model.model_set' clashes with field name 'Model.model_set'.",
  1059. hint=(
  1060. "Rename field 'Model.model_set', or add/change "
  1061. "a related_name argument to the definition "
  1062. "for field 'Model.model_set'."
  1063. ),
  1064. obj=Model._meta.get_field('model_set'),
  1065. id='fields.E302',
  1066. ),
  1067. ]
  1068. self.assertEqual(errors, expected)
  1069. def test_reverse_query_name_clash(self):
  1070. class Model(models.Model):
  1071. model = models.ForeignKey("Model", models.CASCADE)
  1072. errors = Model.check()
  1073. expected = [
  1074. Error(
  1075. "Reverse query name for 'Model.model' clashes with field name 'Model.model'.",
  1076. hint=(
  1077. "Rename field 'Model.model', or add/change a related_name "
  1078. "argument to the definition for field 'Model.model'."
  1079. ),
  1080. obj=Model._meta.get_field('model'),
  1081. id='fields.E303',
  1082. ),
  1083. ]
  1084. self.assertEqual(errors, expected)
  1085. def test_clash_under_explicit_related_name(self):
  1086. class Model(models.Model):
  1087. clash = models.CharField(max_length=10)
  1088. foreign = models.ForeignKey("Model", models.CASCADE, related_name='clash')
  1089. errors = Model.check()
  1090. expected = [
  1091. Error(
  1092. "Reverse accessor for 'Model.foreign' clashes with field name 'Model.clash'.",
  1093. hint=(
  1094. "Rename field 'Model.clash', or add/change a related_name "
  1095. "argument to the definition for field 'Model.foreign'."
  1096. ),
  1097. obj=Model._meta.get_field('foreign'),
  1098. id='fields.E302',
  1099. ),
  1100. Error(
  1101. "Reverse query name for 'Model.foreign' clashes with field name 'Model.clash'.",
  1102. hint=(
  1103. "Rename field 'Model.clash', or add/change a related_name "
  1104. "argument to the definition for field 'Model.foreign'."
  1105. ),
  1106. obj=Model._meta.get_field('foreign'),
  1107. id='fields.E303',
  1108. ),
  1109. ]
  1110. self.assertEqual(errors, expected)
  1111. @isolate_apps('invalid_models_tests')
  1112. class ComplexClashTests(SimpleTestCase):
  1113. # New tests should not be included here, because this is a single,
  1114. # self-contained sanity check, not a test of everything.
  1115. def test_complex_clash(self):
  1116. class Target(models.Model):
  1117. tgt_safe = models.CharField(max_length=10)
  1118. clash = models.CharField(max_length=10)
  1119. model = models.CharField(max_length=10)
  1120. clash1_set = models.CharField(max_length=10)
  1121. class Model(models.Model):
  1122. src_safe = models.CharField(max_length=10)
  1123. foreign_1 = models.ForeignKey(Target, models.CASCADE, related_name='id')
  1124. foreign_2 = models.ForeignKey(Target, models.CASCADE, related_name='src_safe')
  1125. m2m_1 = models.ManyToManyField(Target, related_name='id')
  1126. m2m_2 = models.ManyToManyField(Target, related_name='src_safe')
  1127. errors = Model.check()
  1128. expected = [
  1129. Error(
  1130. "Reverse accessor for 'Model.foreign_1' clashes with field name 'Target.id'.",
  1131. hint=("Rename field 'Target.id', or add/change a related_name "
  1132. "argument to the definition for field 'Model.foreign_1'."),
  1133. obj=Model._meta.get_field('foreign_1'),
  1134. id='fields.E302',
  1135. ),
  1136. Error(
  1137. "Reverse query name for 'Model.foreign_1' clashes with field name 'Target.id'.",
  1138. hint=("Rename field 'Target.id', or add/change a related_name "
  1139. "argument to the definition for field 'Model.foreign_1'."),
  1140. obj=Model._meta.get_field('foreign_1'),
  1141. id='fields.E303',
  1142. ),
  1143. Error(
  1144. "Reverse accessor for 'Model.foreign_1' clashes with reverse accessor for 'Model.m2m_1'.",
  1145. hint=("Add or change a related_name argument to "
  1146. "the definition for 'Model.foreign_1' or 'Model.m2m_1'."),
  1147. obj=Model._meta.get_field('foreign_1'),
  1148. id='fields.E304',
  1149. ),
  1150. Error(
  1151. "Reverse query name for 'Model.foreign_1' clashes with reverse query name for 'Model.m2m_1'.",
  1152. hint=("Add or change a related_name argument to "
  1153. "the definition for 'Model.foreign_1' or 'Model.m2m_1'."),
  1154. obj=Model._meta.get_field('foreign_1'),
  1155. id='fields.E305',
  1156. ),
  1157. Error(
  1158. "Reverse accessor for 'Model.foreign_2' clashes with reverse accessor for 'Model.m2m_2'.",
  1159. hint=("Add or change a related_name argument "
  1160. "to the definition for 'Model.foreign_2' or 'Model.m2m_2'."),
  1161. obj=Model._meta.get_field('foreign_2'),
  1162. id='fields.E304',
  1163. ),
  1164. Error(
  1165. "Reverse query name for 'Model.foreign_2' clashes with reverse query name for 'Model.m2m_2'.",
  1166. hint=("Add or change a related_name argument to "
  1167. "the definition for 'Model.foreign_2' or 'Model.m2m_2'."),
  1168. obj=Model._meta.get_field('foreign_2'),
  1169. id='fields.E305',
  1170. ),
  1171. Error(
  1172. "Reverse accessor for 'Model.m2m_1' clashes with field name 'Target.id'.",
  1173. hint=("Rename field 'Target.id', or add/change a related_name "
  1174. "argument to the definition for field 'Model.m2m_1'."),
  1175. obj=Model._meta.get_field('m2m_1'),
  1176. id='fields.E302',
  1177. ),
  1178. Error(
  1179. "Reverse query name for 'Model.m2m_1' clashes with field name 'Target.id'.",
  1180. hint=("Rename field 'Target.id', or add/change a related_name "
  1181. "argument to the definition for field 'Model.m2m_1'."),
  1182. obj=Model._meta.get_field('m2m_1'),
  1183. id='fields.E303',
  1184. ),
  1185. Error(
  1186. "Reverse accessor for 'Model.m2m_1' clashes with reverse accessor for 'Model.foreign_1'.",
  1187. hint=("Add or change a related_name argument to the definition "
  1188. "for 'Model.m2m_1' or 'Model.foreign_1'."),
  1189. obj=Model._meta.get_field('m2m_1'),
  1190. id='fields.E304',
  1191. ),
  1192. Error(
  1193. "Reverse query name for 'Model.m2m_1' clashes with reverse query name for 'Model.foreign_1'.",
  1194. hint=("Add or change a related_name argument to "
  1195. "the definition for 'Model.m2m_1' or 'Model.foreign_1'."),
  1196. obj=Model._meta.get_field('m2m_1'),
  1197. id='fields.E305',
  1198. ),
  1199. Error(
  1200. "Reverse accessor for 'Model.m2m_2' clashes with reverse accessor for 'Model.foreign_2'.",
  1201. hint=("Add or change a related_name argument to the definition "
  1202. "for 'Model.m2m_2' or 'Model.foreign_2'."),
  1203. obj=Model._meta.get_field('m2m_2'),
  1204. id='fields.E304',
  1205. ),
  1206. Error(
  1207. "Reverse query name for 'Model.m2m_2' clashes with reverse query name for 'Model.foreign_2'.",
  1208. hint=("Add or change a related_name argument to the definition "
  1209. "for 'Model.m2m_2' or 'Model.foreign_2'."),
  1210. obj=Model._meta.get_field('m2m_2'),
  1211. id='fields.E305',
  1212. ),
  1213. ]
  1214. self.assertEqual(errors, expected)
  1215. @isolate_apps('invalid_models_tests')
  1216. class M2mThroughFieldsTests(SimpleTestCase):
  1217. def test_m2m_field_argument_validation(self):
  1218. """
  1219. ManyToManyField accepts the ``through_fields`` kwarg
  1220. only if an intermediary table is specified.
  1221. """
  1222. class Fan(models.Model):
  1223. pass
  1224. with self.assertRaisesMessage(ValueError, 'Cannot specify through_fields without a through model'):
  1225. models.ManyToManyField(Fan, through_fields=('f1', 'f2'))
  1226. def test_invalid_order(self):
  1227. """
  1228. Mixing up the order of link fields to ManyToManyField.through_fields
  1229. triggers validation errors.
  1230. """
  1231. class Fan(models.Model):
  1232. pass
  1233. class Event(models.Model):
  1234. invitees = models.ManyToManyField(Fan, through='Invitation', through_fields=('invitee', 'event'))
  1235. class Invitation(models.Model):
  1236. event = models.ForeignKey(Event, models.CASCADE)
  1237. invitee = models.ForeignKey(Fan, models.CASCADE)
  1238. inviter = models.ForeignKey(Fan, models.CASCADE, related_name='+')
  1239. field = Event._meta.get_field('invitees')
  1240. errors = field.check(from_model=Event)
  1241. expected = [
  1242. Error(
  1243. "'Invitation.invitee' is not a foreign key to 'Event'.",
  1244. hint="Did you mean one of the following foreign keys to 'Event': event?",
  1245. obj=field,
  1246. id='fields.E339',
  1247. ),
  1248. Error(
  1249. "'Invitation.event' is not a foreign key to 'Fan'.",
  1250. hint="Did you mean one of the following foreign keys to 'Fan': invitee, inviter?",
  1251. obj=field,
  1252. id='fields.E339',
  1253. ),
  1254. ]
  1255. self.assertEqual(expected, errors)
  1256. def test_invalid_field(self):
  1257. """
  1258. Providing invalid field names to ManyToManyField.through_fields
  1259. triggers validation errors.
  1260. """
  1261. class Fan(models.Model):
  1262. pass
  1263. class Event(models.Model):
  1264. invitees = models.ManyToManyField(
  1265. Fan,
  1266. through='Invitation',
  1267. through_fields=('invalid_field_1', 'invalid_field_2'),
  1268. )
  1269. class Invitation(models.Model):
  1270. event = models.ForeignKey(Event, models.CASCADE)
  1271. invitee = models.ForeignKey(Fan, models.CASCADE)
  1272. inviter = models.ForeignKey(Fan, models.CASCADE, related_name='+')
  1273. field = Event._meta.get_field('invitees')
  1274. errors = field.check(from_model=Event)
  1275. expected = [
  1276. Error(
  1277. "The intermediary model 'invalid_models_tests.Invitation' has no field 'invalid_field_1'.",
  1278. hint="Did you mean one of the following foreign keys to 'Event': event?",
  1279. obj=field,
  1280. id='fields.E338',
  1281. ),
  1282. Error(
  1283. "The intermediary model 'invalid_models_tests.Invitation' has no field 'invalid_field_2'.",
  1284. hint="Did you mean one of the following foreign keys to 'Fan': invitee, inviter?",
  1285. obj=field,
  1286. id='fields.E338',
  1287. ),
  1288. ]
  1289. self.assertEqual(expected, errors)
  1290. def test_explicit_field_names(self):
  1291. """
  1292. If ``through_fields`` kwarg is given, it must specify both
  1293. link fields of the intermediary table.
  1294. """
  1295. class Fan(models.Model):
  1296. pass
  1297. class Event(models.Model):
  1298. invitees = models.ManyToManyField(Fan, through='Invitation', through_fields=(None, 'invitee'))
  1299. class Invitation(models.Model):
  1300. event = models.ForeignKey(Event, models.CASCADE)
  1301. invitee = models.ForeignKey(Fan, models.CASCADE)
  1302. inviter = models.ForeignKey(Fan, models.CASCADE, related_name='+')
  1303. field = Event._meta.get_field('invitees')
  1304. errors = field.check(from_model=Event)
  1305. expected = [
  1306. Error(
  1307. "Field specifies 'through_fields' but does not provide the names "
  1308. "of the two link fields that should be used for the relation "
  1309. "through model 'invalid_models_tests.Invitation'.",
  1310. hint="Make sure you specify 'through_fields' as through_fields=('field1', 'field2')",
  1311. obj=field,
  1312. id='fields.E337')]
  1313. self.assertEqual(expected, errors)
  1314. def test_superset_foreign_object(self):
  1315. class Parent(models.Model):
  1316. a = models.PositiveIntegerField()
  1317. b = models.PositiveIntegerField()
  1318. c = models.PositiveIntegerField()
  1319. class Meta:
  1320. unique_together = (('a', 'b', 'c'),)
  1321. class Child(models.Model):
  1322. a = models.PositiveIntegerField()
  1323. b = models.PositiveIntegerField()
  1324. value = models.CharField(max_length=255)
  1325. parent = ForeignObject(
  1326. Parent,
  1327. on_delete=models.SET_NULL,
  1328. from_fields=('a', 'b'),
  1329. to_fields=('a', 'b'),
  1330. related_name='children',
  1331. )
  1332. field = Child._meta.get_field('parent')
  1333. errors = field.check(from_model=Child)
  1334. expected = [
  1335. Error(
  1336. "No subset of the fields 'a', 'b' on model 'Parent' is unique.",
  1337. hint=(
  1338. "Add unique=True on any of those fields or add at least "
  1339. "a subset of them to a unique_together constraint."
  1340. ),
  1341. obj=field,
  1342. id='fields.E310',
  1343. ),
  1344. ]
  1345. self.assertEqual(expected, errors)
  1346. def test_intersection_foreign_object(self):
  1347. class Parent(models.Model):
  1348. a = models.PositiveIntegerField()
  1349. b = models.PositiveIntegerField()
  1350. c = models.PositiveIntegerField()
  1351. d = models.PositiveIntegerField()
  1352. class Meta:
  1353. unique_together = (('a', 'b', 'c'),)
  1354. class Child(models.Model):
  1355. a = models.PositiveIntegerField()
  1356. b = models.PositiveIntegerField()
  1357. d = models.PositiveIntegerField()
  1358. value = models.CharField(max_length=255)
  1359. parent = ForeignObject(
  1360. Parent,
  1361. on_delete=models.SET_NULL,
  1362. from_fields=('a', 'b', 'd'),
  1363. to_fields=('a', 'b', 'd'),
  1364. related_name='children',
  1365. )
  1366. field = Child._meta.get_field('parent')
  1367. errors = field.check(from_model=Child)
  1368. expected = [
  1369. Error(
  1370. "No subset of the fields 'a', 'b', 'd' on model 'Parent' is unique.",
  1371. hint=(
  1372. "Add unique=True on any of those fields or add at least "
  1373. "a subset of them to a unique_together constraint."
  1374. ),
  1375. obj=field,
  1376. id='fields.E310',
  1377. ),
  1378. ]
  1379. self.assertEqual(expected, errors)