test_relative_fields.py 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574
  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. ]
  631. # Python 2 crashes on non-ASCII strings.
  632. if six.PY3:
  633. related_names.extend(['試', '試驗+'])
  634. class Parent(models.Model):
  635. pass
  636. for related_name in related_names:
  637. Child = type(str('Child_%s') % str(related_name), (models.Model,), {
  638. 'parent': models.ForeignKey('Parent', models.CASCADE, related_name=related_name),
  639. '__module__': Parent.__module__,
  640. })
  641. errors = Child.check()
  642. self.assertFalse(errors)
  643. @isolate_apps('invalid_models_tests')
  644. class AccessorClashTests(SimpleTestCase):
  645. def test_fk_to_integer(self):
  646. self._test_accessor_clash(
  647. target=models.IntegerField(),
  648. relative=models.ForeignKey('Target', models.CASCADE))
  649. def test_fk_to_fk(self):
  650. self._test_accessor_clash(
  651. target=models.ForeignKey('Another', models.CASCADE),
  652. relative=models.ForeignKey('Target', models.CASCADE))
  653. def test_fk_to_m2m(self):
  654. self._test_accessor_clash(
  655. target=models.ManyToManyField('Another'),
  656. relative=models.ForeignKey('Target', models.CASCADE))
  657. def test_m2m_to_integer(self):
  658. self._test_accessor_clash(
  659. target=models.IntegerField(),
  660. relative=models.ManyToManyField('Target'))
  661. def test_m2m_to_fk(self):
  662. self._test_accessor_clash(
  663. target=models.ForeignKey('Another', models.CASCADE),
  664. relative=models.ManyToManyField('Target'))
  665. def test_m2m_to_m2m(self):
  666. self._test_accessor_clash(
  667. target=models.ManyToManyField('Another'),
  668. relative=models.ManyToManyField('Target'))
  669. def _test_accessor_clash(self, target, relative):
  670. class Another(models.Model):
  671. pass
  672. class Target(models.Model):
  673. model_set = target
  674. class Model(models.Model):
  675. rel = relative
  676. errors = Model.check()
  677. expected = [
  678. Error(
  679. "Reverse accessor for 'Model.rel' clashes with field name 'Target.model_set'.",
  680. hint=("Rename field 'Target.model_set', or add/change "
  681. "a related_name argument to the definition "
  682. "for field 'Model.rel'."),
  683. obj=Model._meta.get_field('rel'),
  684. id='fields.E302',
  685. ),
  686. ]
  687. self.assertEqual(errors, expected)
  688. def test_clash_between_accessors(self):
  689. class Target(models.Model):
  690. pass
  691. class Model(models.Model):
  692. foreign = models.ForeignKey(Target, models.CASCADE)
  693. m2m = models.ManyToManyField(Target)
  694. errors = Model.check()
  695. expected = [
  696. Error(
  697. "Reverse accessor for 'Model.foreign' clashes with reverse accessor for 'Model.m2m'.",
  698. hint=(
  699. "Add or change a related_name argument to the definition "
  700. "for 'Model.foreign' or 'Model.m2m'."
  701. ),
  702. obj=Model._meta.get_field('foreign'),
  703. id='fields.E304',
  704. ),
  705. Error(
  706. "Reverse accessor for 'Model.m2m' clashes with reverse accessor for 'Model.foreign'.",
  707. hint=(
  708. "Add or change a related_name argument to the definition "
  709. "for 'Model.m2m' or 'Model.foreign'."
  710. ),
  711. obj=Model._meta.get_field('m2m'),
  712. id='fields.E304',
  713. ),
  714. ]
  715. self.assertEqual(errors, expected)
  716. def test_m2m_to_m2m_with_inheritance(self):
  717. """ Ref #22047. """
  718. class Target(models.Model):
  719. pass
  720. class Model(models.Model):
  721. children = models.ManyToManyField('Child',
  722. related_name="m2m_clash", related_query_name="no_clash")
  723. class Parent(models.Model):
  724. m2m_clash = models.ManyToManyField('Target')
  725. class Child(Parent):
  726. pass
  727. errors = Model.check()
  728. expected = [
  729. Error(
  730. "Reverse accessor for 'Model.children' clashes with field name 'Child.m2m_clash'.",
  731. hint=(
  732. "Rename field 'Child.m2m_clash', or add/change a related_name "
  733. "argument to the definition for field 'Model.children'."
  734. ),
  735. obj=Model._meta.get_field('children'),
  736. id='fields.E302',
  737. )
  738. ]
  739. self.assertEqual(errors, expected)
  740. @isolate_apps('invalid_models_tests')
  741. class ReverseQueryNameClashTests(SimpleTestCase):
  742. def test_fk_to_integer(self):
  743. self._test_reverse_query_name_clash(
  744. target=models.IntegerField(),
  745. relative=models.ForeignKey('Target', models.CASCADE))
  746. def test_fk_to_fk(self):
  747. self._test_reverse_query_name_clash(
  748. target=models.ForeignKey('Another', models.CASCADE),
  749. relative=models.ForeignKey('Target', models.CASCADE))
  750. def test_fk_to_m2m(self):
  751. self._test_reverse_query_name_clash(
  752. target=models.ManyToManyField('Another'),
  753. relative=models.ForeignKey('Target', models.CASCADE))
  754. def test_m2m_to_integer(self):
  755. self._test_reverse_query_name_clash(
  756. target=models.IntegerField(),
  757. relative=models.ManyToManyField('Target'))
  758. def test_m2m_to_fk(self):
  759. self._test_reverse_query_name_clash(
  760. target=models.ForeignKey('Another', models.CASCADE),
  761. relative=models.ManyToManyField('Target'))
  762. def test_m2m_to_m2m(self):
  763. self._test_reverse_query_name_clash(
  764. target=models.ManyToManyField('Another'),
  765. relative=models.ManyToManyField('Target'))
  766. def _test_reverse_query_name_clash(self, target, relative):
  767. class Another(models.Model):
  768. pass
  769. class Target(models.Model):
  770. model = target
  771. class Model(models.Model):
  772. rel = relative
  773. errors = Model.check()
  774. expected = [
  775. Error(
  776. "Reverse query name for 'Model.rel' clashes with field name 'Target.model'.",
  777. hint=(
  778. "Rename field 'Target.model', or add/change a related_name "
  779. "argument to the definition for field 'Model.rel'."
  780. ),
  781. obj=Model._meta.get_field('rel'),
  782. id='fields.E303',
  783. ),
  784. ]
  785. self.assertEqual(errors, expected)
  786. @isolate_apps('invalid_models_tests')
  787. class ExplicitRelatedNameClashTests(SimpleTestCase):
  788. def test_fk_to_integer(self):
  789. self._test_explicit_related_name_clash(
  790. target=models.IntegerField(),
  791. relative=models.ForeignKey('Target', models.CASCADE, related_name='clash'))
  792. def test_fk_to_fk(self):
  793. self._test_explicit_related_name_clash(
  794. target=models.ForeignKey('Another', models.CASCADE),
  795. relative=models.ForeignKey('Target', models.CASCADE, related_name='clash'))
  796. def test_fk_to_m2m(self):
  797. self._test_explicit_related_name_clash(
  798. target=models.ManyToManyField('Another'),
  799. relative=models.ForeignKey('Target', models.CASCADE, related_name='clash'))
  800. def test_m2m_to_integer(self):
  801. self._test_explicit_related_name_clash(
  802. target=models.IntegerField(),
  803. relative=models.ManyToManyField('Target', related_name='clash'))
  804. def test_m2m_to_fk(self):
  805. self._test_explicit_related_name_clash(
  806. target=models.ForeignKey('Another', models.CASCADE),
  807. relative=models.ManyToManyField('Target', related_name='clash'))
  808. def test_m2m_to_m2m(self):
  809. self._test_explicit_related_name_clash(
  810. target=models.ManyToManyField('Another'),
  811. relative=models.ManyToManyField('Target', related_name='clash'))
  812. def _test_explicit_related_name_clash(self, target, relative):
  813. class Another(models.Model):
  814. pass
  815. class Target(models.Model):
  816. clash = target
  817. class Model(models.Model):
  818. rel = relative
  819. errors = Model.check()
  820. expected = [
  821. Error(
  822. "Reverse accessor for 'Model.rel' clashes with field name 'Target.clash'.",
  823. hint=(
  824. "Rename field 'Target.clash', or add/change a related_name "
  825. "argument to the definition for field 'Model.rel'."
  826. ),
  827. obj=Model._meta.get_field('rel'),
  828. id='fields.E302',
  829. ),
  830. Error(
  831. "Reverse query name for 'Model.rel' clashes with field name 'Target.clash'.",
  832. hint=(
  833. "Rename field 'Target.clash', or add/change a related_name "
  834. "argument to the definition for field 'Model.rel'."
  835. ),
  836. obj=Model._meta.get_field('rel'),
  837. id='fields.E303',
  838. ),
  839. ]
  840. self.assertEqual(errors, expected)
  841. @isolate_apps('invalid_models_tests')
  842. class ExplicitRelatedQueryNameClashTests(SimpleTestCase):
  843. def test_fk_to_integer(self, related_name=None):
  844. self._test_explicit_related_query_name_clash(
  845. target=models.IntegerField(),
  846. relative=models.ForeignKey('Target',
  847. models.CASCADE,
  848. related_name=related_name,
  849. related_query_name='clash'))
  850. def test_hidden_fk_to_integer(self, related_name=None):
  851. self.test_fk_to_integer(related_name='+')
  852. def test_fk_to_fk(self, related_name=None):
  853. self._test_explicit_related_query_name_clash(
  854. target=models.ForeignKey('Another', models.CASCADE),
  855. relative=models.ForeignKey('Target',
  856. models.CASCADE,
  857. related_name=related_name,
  858. related_query_name='clash'))
  859. def test_hidden_fk_to_fk(self):
  860. self.test_fk_to_fk(related_name='+')
  861. def test_fk_to_m2m(self, related_name=None):
  862. self._test_explicit_related_query_name_clash(
  863. target=models.ManyToManyField('Another'),
  864. relative=models.ForeignKey('Target',
  865. models.CASCADE,
  866. related_name=related_name,
  867. related_query_name='clash'))
  868. def test_hidden_fk_to_m2m(self):
  869. self.test_fk_to_m2m(related_name='+')
  870. def test_m2m_to_integer(self, related_name=None):
  871. self._test_explicit_related_query_name_clash(
  872. target=models.IntegerField(),
  873. relative=models.ManyToManyField('Target',
  874. related_name=related_name,
  875. related_query_name='clash'))
  876. def test_hidden_m2m_to_integer(self):
  877. self.test_m2m_to_integer(related_name='+')
  878. def test_m2m_to_fk(self, related_name=None):
  879. self._test_explicit_related_query_name_clash(
  880. target=models.ForeignKey('Another', models.CASCADE),
  881. relative=models.ManyToManyField('Target',
  882. related_name=related_name,
  883. related_query_name='clash'))
  884. def test_hidden_m2m_to_fk(self):
  885. self.test_m2m_to_fk(related_name='+')
  886. def test_m2m_to_m2m(self, related_name=None):
  887. self._test_explicit_related_query_name_clash(
  888. target=models.ManyToManyField('Another'),
  889. relative=models.ManyToManyField('Target',
  890. related_name=related_name,
  891. related_query_name='clash'))
  892. def test_hidden_m2m_to_m2m(self):
  893. self.test_m2m_to_m2m(related_name='+')
  894. def _test_explicit_related_query_name_clash(self, target, relative):
  895. class Another(models.Model):
  896. pass
  897. class Target(models.Model):
  898. clash = target
  899. class Model(models.Model):
  900. rel = relative
  901. errors = Model.check()
  902. expected = [
  903. Error(
  904. "Reverse query name for 'Model.rel' clashes with field name 'Target.clash'.",
  905. hint=(
  906. "Rename field 'Target.clash', or add/change a related_name "
  907. "argument to the definition for field 'Model.rel'."
  908. ),
  909. obj=Model._meta.get_field('rel'),
  910. id='fields.E303',
  911. ),
  912. ]
  913. self.assertEqual(errors, expected)
  914. @isolate_apps('invalid_models_tests')
  915. class SelfReferentialM2MClashTests(SimpleTestCase):
  916. def test_clash_between_accessors(self):
  917. class Model(models.Model):
  918. first_m2m = models.ManyToManyField('self', symmetrical=False)
  919. second_m2m = models.ManyToManyField('self', symmetrical=False)
  920. errors = Model.check()
  921. expected = [
  922. Error(
  923. "Reverse accessor for 'Model.first_m2m' clashes with reverse accessor for 'Model.second_m2m'.",
  924. hint=(
  925. "Add or change a related_name argument to the definition "
  926. "for 'Model.first_m2m' or 'Model.second_m2m'."
  927. ),
  928. obj=Model._meta.get_field('first_m2m'),
  929. id='fields.E304',
  930. ),
  931. Error(
  932. "Reverse accessor for 'Model.second_m2m' clashes with reverse accessor for 'Model.first_m2m'.",
  933. hint=(
  934. "Add or change a related_name argument to the definition "
  935. "for 'Model.second_m2m' or 'Model.first_m2m'."
  936. ),
  937. obj=Model._meta.get_field('second_m2m'),
  938. id='fields.E304',
  939. ),
  940. ]
  941. self.assertEqual(errors, expected)
  942. def test_accessor_clash(self):
  943. class Model(models.Model):
  944. model_set = models.ManyToManyField("self", symmetrical=False)
  945. errors = Model.check()
  946. expected = [
  947. Error(
  948. "Reverse accessor for 'Model.model_set' clashes with field name 'Model.model_set'.",
  949. hint=(
  950. "Rename field 'Model.model_set', or add/change a related_name "
  951. "argument to the definition for field 'Model.model_set'."
  952. ),
  953. obj=Model._meta.get_field('model_set'),
  954. id='fields.E302',
  955. ),
  956. ]
  957. self.assertEqual(errors, expected)
  958. def test_reverse_query_name_clash(self):
  959. class Model(models.Model):
  960. model = models.ManyToManyField("self", symmetrical=False)
  961. errors = Model.check()
  962. expected = [
  963. Error(
  964. "Reverse query name for 'Model.model' clashes with field name 'Model.model'.",
  965. hint=(
  966. "Rename field 'Model.model', or add/change a related_name "
  967. "argument to the definition for field 'Model.model'."
  968. ),
  969. obj=Model._meta.get_field('model'),
  970. id='fields.E303',
  971. ),
  972. ]
  973. self.assertEqual(errors, expected)
  974. def test_clash_under_explicit_related_name(self):
  975. class Model(models.Model):
  976. clash = models.IntegerField()
  977. m2m = models.ManyToManyField("self", symmetrical=False, related_name='clash')
  978. errors = Model.check()
  979. expected = [
  980. Error(
  981. "Reverse accessor for 'Model.m2m' clashes with field name 'Model.clash'.",
  982. hint=(
  983. "Rename field 'Model.clash', or add/change a related_name "
  984. "argument to the definition for field 'Model.m2m'."
  985. ),
  986. obj=Model._meta.get_field('m2m'),
  987. id='fields.E302',
  988. ),
  989. Error(
  990. "Reverse query name for 'Model.m2m' clashes with field name 'Model.clash'.",
  991. hint=(
  992. "Rename field 'Model.clash', or add/change a related_name "
  993. "argument to the definition for field 'Model.m2m'."
  994. ),
  995. obj=Model._meta.get_field('m2m'),
  996. id='fields.E303',
  997. ),
  998. ]
  999. self.assertEqual(errors, expected)
  1000. def test_valid_model(self):
  1001. class Model(models.Model):
  1002. first = models.ManyToManyField("self", symmetrical=False, related_name='first_accessor')
  1003. second = models.ManyToManyField("self", symmetrical=False, related_name='second_accessor')
  1004. errors = Model.check()
  1005. self.assertEqual(errors, [])
  1006. @isolate_apps('invalid_models_tests')
  1007. class SelfReferentialFKClashTests(SimpleTestCase):
  1008. def test_accessor_clash(self):
  1009. class Model(models.Model):
  1010. model_set = models.ForeignKey("Model", models.CASCADE)
  1011. errors = Model.check()
  1012. expected = [
  1013. Error(
  1014. "Reverse accessor for 'Model.model_set' clashes with field name 'Model.model_set'.",
  1015. hint=(
  1016. "Rename field 'Model.model_set', or add/change "
  1017. "a related_name argument to the definition "
  1018. "for field 'Model.model_set'."
  1019. ),
  1020. obj=Model._meta.get_field('model_set'),
  1021. id='fields.E302',
  1022. ),
  1023. ]
  1024. self.assertEqual(errors, expected)
  1025. def test_reverse_query_name_clash(self):
  1026. class Model(models.Model):
  1027. model = models.ForeignKey("Model", models.CASCADE)
  1028. errors = Model.check()
  1029. expected = [
  1030. Error(
  1031. "Reverse query name for 'Model.model' clashes with field name 'Model.model'.",
  1032. hint=(
  1033. "Rename field 'Model.model', or add/change a related_name "
  1034. "argument to the definition for field 'Model.model'."
  1035. ),
  1036. obj=Model._meta.get_field('model'),
  1037. id='fields.E303',
  1038. ),
  1039. ]
  1040. self.assertEqual(errors, expected)
  1041. def test_clash_under_explicit_related_name(self):
  1042. class Model(models.Model):
  1043. clash = models.CharField(max_length=10)
  1044. foreign = models.ForeignKey("Model", models.CASCADE, related_name='clash')
  1045. errors = Model.check()
  1046. expected = [
  1047. Error(
  1048. "Reverse accessor for 'Model.foreign' clashes with field name 'Model.clash'.",
  1049. hint=(
  1050. "Rename field 'Model.clash', or add/change a related_name "
  1051. "argument to the definition for field 'Model.foreign'."
  1052. ),
  1053. obj=Model._meta.get_field('foreign'),
  1054. id='fields.E302',
  1055. ),
  1056. Error(
  1057. "Reverse query name for 'Model.foreign' clashes with field name 'Model.clash'.",
  1058. hint=(
  1059. "Rename field 'Model.clash', or add/change a related_name "
  1060. "argument to the definition for field 'Model.foreign'."
  1061. ),
  1062. obj=Model._meta.get_field('foreign'),
  1063. id='fields.E303',
  1064. ),
  1065. ]
  1066. self.assertEqual(errors, expected)
  1067. @isolate_apps('invalid_models_tests')
  1068. class ComplexClashTests(SimpleTestCase):
  1069. # New tests should not be included here, because this is a single,
  1070. # self-contained sanity check, not a test of everything.
  1071. def test_complex_clash(self):
  1072. class Target(models.Model):
  1073. tgt_safe = models.CharField(max_length=10)
  1074. clash = models.CharField(max_length=10)
  1075. model = models.CharField(max_length=10)
  1076. clash1_set = models.CharField(max_length=10)
  1077. class Model(models.Model):
  1078. src_safe = models.CharField(max_length=10)
  1079. foreign_1 = models.ForeignKey(Target, models.CASCADE, related_name='id')
  1080. foreign_2 = models.ForeignKey(Target, models.CASCADE, related_name='src_safe')
  1081. m2m_1 = models.ManyToManyField(Target, related_name='id')
  1082. m2m_2 = models.ManyToManyField(Target, related_name='src_safe')
  1083. errors = Model.check()
  1084. expected = [
  1085. Error(
  1086. "Reverse accessor for 'Model.foreign_1' clashes with field name 'Target.id'.",
  1087. hint=("Rename field 'Target.id', or add/change a related_name "
  1088. "argument to the definition for field 'Model.foreign_1'."),
  1089. obj=Model._meta.get_field('foreign_1'),
  1090. id='fields.E302',
  1091. ),
  1092. Error(
  1093. "Reverse query name for 'Model.foreign_1' clashes with field name 'Target.id'.",
  1094. hint=("Rename field 'Target.id', or add/change a related_name "
  1095. "argument to the definition for field 'Model.foreign_1'."),
  1096. obj=Model._meta.get_field('foreign_1'),
  1097. id='fields.E303',
  1098. ),
  1099. Error(
  1100. "Reverse accessor for 'Model.foreign_1' clashes with reverse accessor for 'Model.m2m_1'.",
  1101. hint=("Add or change a related_name argument to "
  1102. "the definition for 'Model.foreign_1' or 'Model.m2m_1'."),
  1103. obj=Model._meta.get_field('foreign_1'),
  1104. id='fields.E304',
  1105. ),
  1106. Error(
  1107. "Reverse query name for 'Model.foreign_1' clashes with reverse query name for 'Model.m2m_1'.",
  1108. hint=("Add or change a related_name argument to "
  1109. "the definition for 'Model.foreign_1' or 'Model.m2m_1'."),
  1110. obj=Model._meta.get_field('foreign_1'),
  1111. id='fields.E305',
  1112. ),
  1113. Error(
  1114. "Reverse accessor for 'Model.foreign_2' clashes with reverse accessor for 'Model.m2m_2'.",
  1115. hint=("Add or change a related_name argument "
  1116. "to the definition for 'Model.foreign_2' or 'Model.m2m_2'."),
  1117. obj=Model._meta.get_field('foreign_2'),
  1118. id='fields.E304',
  1119. ),
  1120. Error(
  1121. "Reverse query name for 'Model.foreign_2' clashes with reverse query name for 'Model.m2m_2'.",
  1122. hint=("Add or change a related_name argument to "
  1123. "the definition for 'Model.foreign_2' or 'Model.m2m_2'."),
  1124. obj=Model._meta.get_field('foreign_2'),
  1125. id='fields.E305',
  1126. ),
  1127. Error(
  1128. "Reverse accessor for 'Model.m2m_1' clashes with field name 'Target.id'.",
  1129. hint=("Rename field 'Target.id', or add/change a related_name "
  1130. "argument to the definition for field 'Model.m2m_1'."),
  1131. obj=Model._meta.get_field('m2m_1'),
  1132. id='fields.E302',
  1133. ),
  1134. Error(
  1135. "Reverse query name for 'Model.m2m_1' clashes with field name 'Target.id'.",
  1136. hint=("Rename field 'Target.id', or add/change a related_name "
  1137. "argument to the definition for field 'Model.m2m_1'."),
  1138. obj=Model._meta.get_field('m2m_1'),
  1139. id='fields.E303',
  1140. ),
  1141. Error(
  1142. "Reverse accessor for 'Model.m2m_1' clashes with reverse accessor for 'Model.foreign_1'.",
  1143. hint=("Add or change a related_name argument to the definition "
  1144. "for 'Model.m2m_1' or 'Model.foreign_1'."),
  1145. obj=Model._meta.get_field('m2m_1'),
  1146. id='fields.E304',
  1147. ),
  1148. Error(
  1149. "Reverse query name for 'Model.m2m_1' clashes with reverse query name for 'Model.foreign_1'.",
  1150. hint=("Add or change a related_name argument to "
  1151. "the definition for 'Model.m2m_1' or 'Model.foreign_1'."),
  1152. obj=Model._meta.get_field('m2m_1'),
  1153. id='fields.E305',
  1154. ),
  1155. Error(
  1156. "Reverse accessor for 'Model.m2m_2' clashes with reverse accessor for 'Model.foreign_2'.",
  1157. hint=("Add or change a related_name argument to the definition "
  1158. "for 'Model.m2m_2' or 'Model.foreign_2'."),
  1159. obj=Model._meta.get_field('m2m_2'),
  1160. id='fields.E304',
  1161. ),
  1162. Error(
  1163. "Reverse query name for 'Model.m2m_2' clashes with reverse query name for 'Model.foreign_2'.",
  1164. hint=("Add or change a related_name argument to the definition "
  1165. "for 'Model.m2m_2' or 'Model.foreign_2'."),
  1166. obj=Model._meta.get_field('m2m_2'),
  1167. id='fields.E305',
  1168. ),
  1169. ]
  1170. self.assertEqual(errors, expected)
  1171. @isolate_apps('invalid_models_tests')
  1172. class M2mThroughFieldsTests(SimpleTestCase):
  1173. def test_m2m_field_argument_validation(self):
  1174. """
  1175. Tests that ManyToManyField accepts the ``through_fields`` kwarg
  1176. only if an intermediary table is specified.
  1177. """
  1178. class Fan(models.Model):
  1179. pass
  1180. with self.assertRaisesMessage(ValueError, 'Cannot specify through_fields without a through model'):
  1181. models.ManyToManyField(Fan, through_fields=('f1', 'f2'))
  1182. def test_invalid_order(self):
  1183. """
  1184. Tests that mixing up the order of link fields to ManyToManyField.through_fields
  1185. triggers validation errors.
  1186. """
  1187. class Fan(models.Model):
  1188. pass
  1189. class Event(models.Model):
  1190. invitees = models.ManyToManyField(Fan, through='Invitation', through_fields=('invitee', 'event'))
  1191. class Invitation(models.Model):
  1192. event = models.ForeignKey(Event, models.CASCADE)
  1193. invitee = models.ForeignKey(Fan, models.CASCADE)
  1194. inviter = models.ForeignKey(Fan, models.CASCADE, related_name='+')
  1195. field = Event._meta.get_field('invitees')
  1196. errors = field.check(from_model=Event)
  1197. expected = [
  1198. Error(
  1199. "'Invitation.invitee' is not a foreign key to 'Event'.",
  1200. hint="Did you mean one of the following foreign keys to 'Event': event?",
  1201. obj=field,
  1202. id='fields.E339',
  1203. ),
  1204. Error(
  1205. "'Invitation.event' is not a foreign key to 'Fan'.",
  1206. hint="Did you mean one of the following foreign keys to 'Fan': invitee, inviter?",
  1207. obj=field,
  1208. id='fields.E339',
  1209. ),
  1210. ]
  1211. self.assertEqual(expected, errors)
  1212. def test_invalid_field(self):
  1213. """
  1214. Tests that providing invalid field names to ManyToManyField.through_fields
  1215. triggers validation errors.
  1216. """
  1217. class Fan(models.Model):
  1218. pass
  1219. class Event(models.Model):
  1220. invitees = models.ManyToManyField(
  1221. Fan,
  1222. through='Invitation',
  1223. through_fields=('invalid_field_1', 'invalid_field_2'),
  1224. )
  1225. class Invitation(models.Model):
  1226. event = models.ForeignKey(Event, models.CASCADE)
  1227. invitee = models.ForeignKey(Fan, models.CASCADE)
  1228. inviter = models.ForeignKey(Fan, models.CASCADE, related_name='+')
  1229. field = Event._meta.get_field('invitees')
  1230. errors = field.check(from_model=Event)
  1231. expected = [
  1232. Error(
  1233. "The intermediary model 'invalid_models_tests.Invitation' has no field 'invalid_field_1'.",
  1234. hint="Did you mean one of the following foreign keys to 'Event': event?",
  1235. obj=field,
  1236. id='fields.E338',
  1237. ),
  1238. Error(
  1239. "The intermediary model 'invalid_models_tests.Invitation' has no field 'invalid_field_2'.",
  1240. hint="Did you mean one of the following foreign keys to 'Fan': invitee, inviter?",
  1241. obj=field,
  1242. id='fields.E338',
  1243. ),
  1244. ]
  1245. self.assertEqual(expected, errors)
  1246. def test_explicit_field_names(self):
  1247. """
  1248. Tests that if ``through_fields`` kwarg is given, it must specify both
  1249. link fields of the intermediary table.
  1250. """
  1251. class Fan(models.Model):
  1252. pass
  1253. class Event(models.Model):
  1254. invitees = models.ManyToManyField(Fan, through='Invitation', through_fields=(None, 'invitee'))
  1255. class Invitation(models.Model):
  1256. event = models.ForeignKey(Event, models.CASCADE)
  1257. invitee = models.ForeignKey(Fan, models.CASCADE)
  1258. inviter = models.ForeignKey(Fan, models.CASCADE, related_name='+')
  1259. field = Event._meta.get_field('invitees')
  1260. errors = field.check(from_model=Event)
  1261. expected = [
  1262. Error(
  1263. "Field specifies 'through_fields' but does not provide the names "
  1264. "of the two link fields that should be used for the relation "
  1265. "through model 'invalid_models_tests.Invitation'.",
  1266. hint="Make sure you specify 'through_fields' as through_fields=('field1', 'field2')",
  1267. obj=field,
  1268. id='fields.E337')]
  1269. self.assertEqual(expected, errors)
  1270. def test_superset_foreign_object(self):
  1271. class Parent(models.Model):
  1272. a = models.PositiveIntegerField()
  1273. b = models.PositiveIntegerField()
  1274. c = models.PositiveIntegerField()
  1275. class Meta:
  1276. unique_together = (('a', 'b', 'c'),)
  1277. class Child(models.Model):
  1278. a = models.PositiveIntegerField()
  1279. b = models.PositiveIntegerField()
  1280. value = models.CharField(max_length=255)
  1281. parent = ForeignObject(
  1282. Parent,
  1283. on_delete=models.SET_NULL,
  1284. from_fields=('a', 'b'),
  1285. to_fields=('a', 'b'),
  1286. related_name='children',
  1287. )
  1288. field = Child._meta.get_field('parent')
  1289. errors = field.check(from_model=Child)
  1290. expected = [
  1291. Error(
  1292. "No subset of the fields 'a', 'b' on model 'Parent' is unique.",
  1293. hint=(
  1294. "Add unique=True on any of those fields or add at least "
  1295. "a subset of them to a unique_together constraint."
  1296. ),
  1297. obj=field,
  1298. id='fields.E310',
  1299. ),
  1300. ]
  1301. self.assertEqual(expected, errors)
  1302. def test_intersection_foreign_object(self):
  1303. class Parent(models.Model):
  1304. a = models.PositiveIntegerField()
  1305. b = models.PositiveIntegerField()
  1306. c = models.PositiveIntegerField()
  1307. d = models.PositiveIntegerField()
  1308. class Meta:
  1309. unique_together = (('a', 'b', 'c'),)
  1310. class Child(models.Model):
  1311. a = models.PositiveIntegerField()
  1312. b = models.PositiveIntegerField()
  1313. d = models.PositiveIntegerField()
  1314. value = models.CharField(max_length=255)
  1315. parent = ForeignObject(
  1316. Parent,
  1317. on_delete=models.SET_NULL,
  1318. from_fields=('a', 'b', 'd'),
  1319. to_fields=('a', 'b', 'd'),
  1320. related_name='children',
  1321. )
  1322. field = Child._meta.get_field('parent')
  1323. errors = field.check(from_model=Child)
  1324. expected = [
  1325. Error(
  1326. "No subset of the fields 'a', 'b', 'd' on model 'Parent' is unique.",
  1327. hint=(
  1328. "Add unique=True on any of those fields or add at least "
  1329. "a subset of them to a unique_together constraint."
  1330. ),
  1331. obj=field,
  1332. id='fields.E310',
  1333. ),
  1334. ]
  1335. self.assertEqual(expected, errors)