test_relative_fields.py 61 KB

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