test_relative_fields.py 63 KB

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