tests.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. from __future__ import unicode_literals
  2. from django import forms
  3. from django.contrib.contenttypes.forms import generic_inlineformset_factory
  4. from django.contrib.contenttypes.models import ContentType
  5. from django.core.exceptions import FieldError
  6. from django.db.models import Q
  7. from django.test import TestCase
  8. from django.utils import six
  9. from .models import (TaggedItem, ValuableTaggedItem, Comparison, Animal,
  10. Vegetable, Mineral, Gecko, Rock, ManualPK,
  11. ForProxyModelModel, ForConcreteModelModel,
  12. ProxyRelatedModel, ConcreteRelatedModel, AllowsNullGFK)
  13. class GenericRelationsTests(TestCase):
  14. def setUp(self):
  15. self.lion = Animal.objects.create(
  16. common_name="Lion", latin_name="Panthera leo")
  17. self.platypus = Animal.objects.create(
  18. common_name="Platypus", latin_name="Ornithorhynchus anatinus")
  19. Vegetable.objects.create(name="Eggplant", is_yucky=True)
  20. self.bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
  21. self.quartz = Mineral.objects.create(name="Quartz", hardness=7)
  22. # Tagging stuff.
  23. self.bacon.tags.create(tag="fatty")
  24. self.bacon.tags.create(tag="salty")
  25. self.lion.tags.create(tag="yellow")
  26. self.lion.tags.create(tag="hairy")
  27. # Original list of tags:
  28. self.comp_func = lambda obj: (
  29. obj.tag, obj.content_type.model_class(), obj.object_id
  30. )
  31. def test_generic_update_or_create_when_created(self):
  32. """
  33. Should be able to use update_or_create from the generic related manager
  34. to create a tag. Refs #23611.
  35. """
  36. count = self.bacon.tags.count()
  37. tag, created = self.bacon.tags.update_or_create(tag='stinky')
  38. self.assertTrue(created)
  39. self.assertEqual(count + 1, self.bacon.tags.count())
  40. def test_generic_update_or_create_when_updated(self):
  41. """
  42. Should be able to use update_or_create from the generic related manager
  43. to update a tag. Refs #23611.
  44. """
  45. count = self.bacon.tags.count()
  46. tag = self.bacon.tags.create(tag='stinky')
  47. self.assertEqual(count + 1, self.bacon.tags.count())
  48. tag, created = self.bacon.tags.update_or_create(defaults={'tag': 'juicy'}, id=tag.id)
  49. self.assertFalse(created)
  50. self.assertEqual(count + 1, self.bacon.tags.count())
  51. self.assertEqual(tag.tag, 'juicy')
  52. def test_generic_get_or_create_when_created(self):
  53. """
  54. Should be able to use get_or_create from the generic related manager
  55. to create a tag. Refs #23611.
  56. """
  57. count = self.bacon.tags.count()
  58. tag, created = self.bacon.tags.get_or_create(tag='stinky')
  59. self.assertTrue(created)
  60. self.assertEqual(count + 1, self.bacon.tags.count())
  61. def test_generic_get_or_create_when_exists(self):
  62. """
  63. Should be able to use get_or_create from the generic related manager
  64. to get a tag. Refs #23611.
  65. """
  66. count = self.bacon.tags.count()
  67. tag = self.bacon.tags.create(tag="stinky")
  68. self.assertEqual(count + 1, self.bacon.tags.count())
  69. tag, created = self.bacon.tags.get_or_create(id=tag.id, defaults={'tag': 'juicy'})
  70. self.assertFalse(created)
  71. self.assertEqual(count + 1, self.bacon.tags.count())
  72. # shouldn't had changed the tag
  73. self.assertEqual(tag.tag, 'stinky')
  74. def test_generic_relations_m2m_mimic(self):
  75. """
  76. Objects with declared GenericRelations can be tagged directly -- the
  77. API mimics the many-to-many API.
  78. """
  79. self.assertQuerysetEqual(self.lion.tags.all(), [
  80. "<TaggedItem: hairy>",
  81. "<TaggedItem: yellow>"
  82. ])
  83. self.assertQuerysetEqual(self.bacon.tags.all(), [
  84. "<TaggedItem: fatty>",
  85. "<TaggedItem: salty>"
  86. ])
  87. def test_access_content_object(self):
  88. """
  89. Test accessing the content object like a foreign key.
  90. """
  91. tagged_item = TaggedItem.objects.get(tag="salty")
  92. self.assertEqual(tagged_item.content_object, self.bacon)
  93. def test_query_content_object(self):
  94. qs = TaggedItem.objects.filter(
  95. animal__isnull=False).order_by('animal__common_name', 'tag')
  96. self.assertQuerysetEqual(
  97. qs, ["<TaggedItem: hairy>", "<TaggedItem: yellow>"]
  98. )
  99. mpk = ManualPK.objects.create(id=1)
  100. mpk.tags.create(tag='mpk')
  101. qs = TaggedItem.objects.filter(
  102. Q(animal__isnull=False) | Q(manualpk__id=1)).order_by('tag')
  103. self.assertQuerysetEqual(
  104. qs, ["hairy", "mpk", "yellow"], lambda x: x.tag)
  105. def test_exclude_generic_relations(self):
  106. """
  107. Test lookups over an object without GenericRelations.
  108. """
  109. # Recall that the Mineral class doesn't have an explicit GenericRelation
  110. # defined. That's OK, because you can create TaggedItems explicitly.
  111. # However, excluding GenericRelations means your lookups have to be a
  112. # bit more explicit.
  113. TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
  114. TaggedItem.objects.create(content_object=self.quartz, tag="clearish")
  115. ctype = ContentType.objects.get_for_model(self.quartz)
  116. q = TaggedItem.objects.filter(
  117. content_type__pk=ctype.id, object_id=self.quartz.id
  118. )
  119. self.assertQuerysetEqual(q, [
  120. "<TaggedItem: clearish>",
  121. "<TaggedItem: shiny>"
  122. ])
  123. def test_access_via_content_type(self):
  124. """
  125. Test lookups through content type.
  126. """
  127. self.lion.delete()
  128. self.platypus.tags.create(tag="fatty")
  129. ctype = ContentType.objects.get_for_model(self.platypus)
  130. self.assertQuerysetEqual(
  131. Animal.objects.filter(tags__content_type=ctype),
  132. ["<Animal: Platypus>"])
  133. def test_set_foreign_key(self):
  134. """
  135. You can set a generic foreign key in the way you'd expect.
  136. """
  137. tag1 = TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
  138. tag1.content_object = self.platypus
  139. tag1.save()
  140. self.assertQuerysetEqual(
  141. self.platypus.tags.all(),
  142. ["<TaggedItem: shiny>"])
  143. def test_queries_across_generic_relations(self):
  144. """
  145. Queries across generic relations respect the content types. Even though
  146. there are two TaggedItems with a tag of "fatty", this query only pulls
  147. out the one with the content type related to Animals.
  148. """
  149. self.assertQuerysetEqual(Animal.objects.order_by('common_name'), [
  150. "<Animal: Lion>",
  151. "<Animal: Platypus>"
  152. ])
  153. def test_queries_content_type_restriction(self):
  154. """
  155. Create another fatty tagged instance with different PK to ensure there
  156. is a content type restriction in the generated queries below.
  157. """
  158. mpk = ManualPK.objects.create(id=self.lion.pk)
  159. mpk.tags.create(tag="fatty")
  160. self.platypus.tags.create(tag="fatty")
  161. self.assertQuerysetEqual(
  162. Animal.objects.filter(tags__tag='fatty'), ["<Animal: Platypus>"])
  163. self.assertQuerysetEqual(
  164. Animal.objects.exclude(tags__tag='fatty'), ["<Animal: Lion>"])
  165. def test_object_deletion_with_generic_relation(self):
  166. """
  167. If you delete an object with an explicit Generic relation, the related
  168. objects are deleted when the source object is deleted.
  169. """
  170. self.assertQuerysetEqual(TaggedItem.objects.all(), [
  171. ('fatty', Vegetable, self.bacon.pk),
  172. ('hairy', Animal, self.lion.pk),
  173. ('salty', Vegetable, self.bacon.pk),
  174. ('yellow', Animal, self.lion.pk)
  175. ],
  176. self.comp_func
  177. )
  178. self.lion.delete()
  179. self.assertQuerysetEqual(TaggedItem.objects.all(), [
  180. ('fatty', Vegetable, self.bacon.pk),
  181. ('salty', Vegetable, self.bacon.pk),
  182. ],
  183. self.comp_func
  184. )
  185. def test_object_deletion_without_generic_relation(self):
  186. """
  187. If Generic Relation is not explicitly defined, any related objects
  188. remain after deletion of the source object.
  189. """
  190. TaggedItem.objects.create(content_object=self.quartz, tag="clearish")
  191. quartz_pk = self.quartz.pk
  192. self.quartz.delete()
  193. self.assertQuerysetEqual(TaggedItem.objects.all(), [
  194. ('clearish', Mineral, quartz_pk),
  195. ('fatty', Vegetable, self.bacon.pk),
  196. ('hairy', Animal, self.lion.pk),
  197. ('salty', Vegetable, self.bacon.pk),
  198. ('yellow', Animal, self.lion.pk),
  199. ],
  200. self.comp_func
  201. )
  202. def test_tag_deletion_related_objects_unaffected(self):
  203. """
  204. If you delete a tag, the objects using the tag are unaffected (other
  205. than losing a tag).
  206. """
  207. ctype = ContentType.objects.get_for_model(self.lion)
  208. tag = TaggedItem.objects.get(
  209. content_type__pk=ctype.id, object_id=self.lion.id, tag="hairy")
  210. tag.delete()
  211. self.assertQuerysetEqual(self.lion.tags.all(), ["<TaggedItem: yellow>"])
  212. self.assertQuerysetEqual(TaggedItem.objects.all(), [
  213. ('fatty', Vegetable, self.bacon.pk),
  214. ('salty', Vegetable, self.bacon.pk),
  215. ('yellow', Animal, self.lion.pk)
  216. ],
  217. self.comp_func
  218. )
  219. def test_assign_with_queryset(self):
  220. # Ensure that querysets used in reverse GFK assignments are pre-evaluated
  221. # so their value isn't affected by the clearing operation in
  222. # ManyRelatedObjectsDescriptor.__set__. Refs #19816.
  223. bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
  224. bacon.tags.create(tag="fatty")
  225. bacon.tags.create(tag="salty")
  226. self.assertEqual(2, bacon.tags.count())
  227. qs = bacon.tags.filter(tag="fatty")
  228. bacon.tags = qs
  229. self.assertEqual(1, bacon.tags.count())
  230. self.assertEqual(1, qs.count())
  231. def test_generic_relation_related_name_default(self):
  232. # Test that GenericRelation by default isn't usable from
  233. # the reverse side.
  234. with self.assertRaises(FieldError):
  235. TaggedItem.objects.filter(vegetable__isnull=True)
  236. def test_multiple_gfk(self):
  237. # Simple tests for multiple GenericForeignKeys
  238. # only uses one model, since the above tests should be sufficient.
  239. tiger = Animal.objects.create(common_name="tiger")
  240. cheetah = Animal.objects.create(common_name="cheetah")
  241. bear = Animal.objects.create(common_name="bear")
  242. # Create directly
  243. Comparison.objects.create(
  244. first_obj=cheetah, other_obj=tiger, comparative="faster"
  245. )
  246. Comparison.objects.create(
  247. first_obj=tiger, other_obj=cheetah, comparative="cooler"
  248. )
  249. # Create using GenericRelation
  250. tiger.comparisons.create(other_obj=bear, comparative="cooler")
  251. tiger.comparisons.create(other_obj=cheetah, comparative="stronger")
  252. self.assertQuerysetEqual(cheetah.comparisons.all(), [
  253. "<Comparison: cheetah is faster than tiger>"
  254. ])
  255. # Filtering works
  256. self.assertQuerysetEqual(tiger.comparisons.filter(comparative="cooler"), [
  257. "<Comparison: tiger is cooler than cheetah>",
  258. "<Comparison: tiger is cooler than bear>",
  259. ], ordered=False)
  260. # Filtering and deleting works
  261. subjective = ["cooler"]
  262. tiger.comparisons.filter(comparative__in=subjective).delete()
  263. self.assertQuerysetEqual(Comparison.objects.all(), [
  264. "<Comparison: cheetah is faster than tiger>",
  265. "<Comparison: tiger is stronger than cheetah>"
  266. ], ordered=False)
  267. # If we delete cheetah, Comparisons with cheetah as 'first_obj' will be
  268. # deleted since Animal has an explicit GenericRelation to Comparison
  269. # through first_obj. Comparisons with cheetah as 'other_obj' will not
  270. # be deleted.
  271. cheetah.delete()
  272. self.assertQuerysetEqual(Comparison.objects.all(), [
  273. "<Comparison: tiger is stronger than None>"
  274. ])
  275. def test_gfk_subclasses(self):
  276. # GenericForeignKey should work with subclasses (see #8309)
  277. quartz = Mineral.objects.create(name="Quartz", hardness=7)
  278. valuedtag = ValuableTaggedItem.objects.create(
  279. content_object=quartz, tag="shiny", value=10
  280. )
  281. self.assertEqual(valuedtag.content_object, quartz)
  282. def test_generic_inline_formsets(self):
  283. GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
  284. formset = GenericFormSet()
  285. self.assertHTMLEqual(''.join(form.as_p() for form in formset.forms), """<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p>
  286. <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>""")
  287. formset = GenericFormSet(instance=Animal())
  288. self.assertHTMLEqual(''.join(form.as_p() for form in formset.forms), """<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p>
  289. <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>""")
  290. platypus = Animal.objects.create(
  291. common_name="Platypus", latin_name="Ornithorhynchus anatinus"
  292. )
  293. platypus.tags.create(tag="shiny")
  294. GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
  295. formset = GenericFormSet(instance=platypus)
  296. tagged_item_id = TaggedItem.objects.get(
  297. tag='shiny', object_id=platypus.id
  298. ).id
  299. self.assertHTMLEqual(''.join(form.as_p() for form in formset.forms), """<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" value="shiny" maxlength="50" /></p>
  300. <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" value="%s" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p><p><label for="id_generic_relations-taggeditem-content_type-object_id-1-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-1-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-1-tag" maxlength="50" /></p>
  301. <p><label for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-1-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id" id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>""" % tagged_item_id)
  302. lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo")
  303. formset = GenericFormSet(instance=lion, prefix='x')
  304. self.assertHTMLEqual(''.join(form.as_p() for form in formset.forms), """<p><label for="id_x-0-tag">Tag:</label> <input id="id_x-0-tag" type="text" name="x-0-tag" maxlength="50" /></p>
  305. <p><label for="id_x-0-DELETE">Delete:</label> <input type="checkbox" name="x-0-DELETE" id="id_x-0-DELETE" /><input type="hidden" name="x-0-id" id="id_x-0-id" /></p>""")
  306. def test_gfk_manager(self):
  307. # GenericForeignKey should not use the default manager (which may filter objects) #16048
  308. tailless = Gecko.objects.create(has_tail=False)
  309. tag = TaggedItem.objects.create(content_object=tailless, tag="lizard")
  310. self.assertEqual(tag.content_object, tailless)
  311. def test_subclasses_with_gen_rel(self):
  312. """
  313. Test that concrete model subclasses with generic relations work
  314. correctly (ticket 11263).
  315. """
  316. granite = Rock.objects.create(name='granite', hardness=5)
  317. TaggedItem.objects.create(content_object=granite, tag="countertop")
  318. self.assertEqual(Rock.objects.filter(tags__tag="countertop").count(), 1)
  319. def test_generic_inline_formsets_initial(self):
  320. """
  321. Test for #17927 Initial values support for BaseGenericInlineFormSet.
  322. """
  323. quartz = Mineral.objects.create(name="Quartz", hardness=7)
  324. GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
  325. ctype = ContentType.objects.get_for_model(quartz)
  326. initial_data = [{
  327. 'tag': 'lizard',
  328. 'content_type': ctype.pk,
  329. 'object_id': quartz.pk,
  330. }]
  331. formset = GenericFormSet(initial=initial_data)
  332. self.assertEqual(formset.forms[0].initial, initial_data[0])
  333. def test_get_or_create(self):
  334. # get_or_create should work with virtual fields (content_object)
  335. quartz = Mineral.objects.create(name="Quartz", hardness=7)
  336. tag, created = TaggedItem.objects.get_or_create(tag="shiny",
  337. defaults={'content_object': quartz})
  338. self.assertTrue(created)
  339. self.assertEqual(tag.tag, "shiny")
  340. self.assertEqual(tag.content_object.id, quartz.id)
  341. def test_update_or_create_defaults(self):
  342. # update_or_create should work with virtual fields (content_object)
  343. quartz = Mineral.objects.create(name="Quartz", hardness=7)
  344. diamond = Mineral.objects.create(name="Diamond", hardness=7)
  345. tag, created = TaggedItem.objects.update_or_create(tag="shiny",
  346. defaults={'content_object': quartz})
  347. self.assertTrue(created)
  348. self.assertEqual(tag.content_object.id, quartz.id)
  349. tag, created = TaggedItem.objects.update_or_create(tag="shiny",
  350. defaults={'content_object': diamond})
  351. self.assertFalse(created)
  352. self.assertEqual(tag.content_object.id, diamond.id)
  353. def test_query_content_type(self):
  354. msg = "Field 'content_object' does not generate an automatic reverse relation"
  355. with self.assertRaisesMessage(FieldError, msg):
  356. TaggedItem.objects.get(content_object='')
  357. class CustomWidget(forms.TextInput):
  358. pass
  359. class TaggedItemForm(forms.ModelForm):
  360. class Meta:
  361. model = TaggedItem
  362. fields = '__all__'
  363. widgets = {'tag': CustomWidget}
  364. class GenericInlineFormsetTest(TestCase):
  365. def test_generic_inlineformset_factory(self):
  366. """
  367. Regression for #14572: Using base forms with widgets
  368. defined in Meta should not raise errors.
  369. """
  370. Formset = generic_inlineformset_factory(TaggedItem, TaggedItemForm)
  371. form = Formset().forms[0]
  372. self.assertIsInstance(form['tag'].field.widget, CustomWidget)
  373. def test_save_new_uses_form_save(self):
  374. """
  375. Regression for #16260: save_new should call form.save()
  376. """
  377. class SaveTestForm(forms.ModelForm):
  378. def save(self, *args, **kwargs):
  379. self.instance.saved_by = "custom method"
  380. return super(SaveTestForm, self).save(*args, **kwargs)
  381. Formset = generic_inlineformset_factory(
  382. ForProxyModelModel, fields='__all__', form=SaveTestForm)
  383. instance = ProxyRelatedModel.objects.create()
  384. data = {
  385. 'form-TOTAL_FORMS': '1',
  386. 'form-INITIAL_FORMS': '0',
  387. 'form-MAX_NUM_FORMS': '',
  388. 'form-0-title': 'foo',
  389. }
  390. formset = Formset(data, instance=instance, prefix='form')
  391. self.assertTrue(formset.is_valid())
  392. new_obj = formset.save()[0]
  393. self.assertEqual(new_obj.saved_by, "custom method")
  394. def test_save_new_for_proxy(self):
  395. Formset = generic_inlineformset_factory(ForProxyModelModel,
  396. fields='__all__', for_concrete_model=False)
  397. instance = ProxyRelatedModel.objects.create()
  398. data = {
  399. 'form-TOTAL_FORMS': '1',
  400. 'form-INITIAL_FORMS': '0',
  401. 'form-MAX_NUM_FORMS': '',
  402. 'form-0-title': 'foo',
  403. }
  404. formset = Formset(data, instance=instance, prefix='form')
  405. self.assertTrue(formset.is_valid())
  406. new_obj, = formset.save()
  407. self.assertEqual(new_obj.obj, instance)
  408. def test_save_new_for_concrete(self):
  409. Formset = generic_inlineformset_factory(ForProxyModelModel,
  410. fields='__all__', for_concrete_model=True)
  411. instance = ProxyRelatedModel.objects.create()
  412. data = {
  413. 'form-TOTAL_FORMS': '1',
  414. 'form-INITIAL_FORMS': '0',
  415. 'form-MAX_NUM_FORMS': '',
  416. 'form-0-title': 'foo',
  417. }
  418. formset = Formset(data, instance=instance, prefix='form')
  419. self.assertTrue(formset.is_valid())
  420. new_obj, = formset.save()
  421. self.assertNotIsInstance(new_obj.obj, ProxyRelatedModel)
  422. class ProxyRelatedModelTest(TestCase):
  423. def test_default_behavior(self):
  424. """
  425. The default for for_concrete_model should be True
  426. """
  427. base = ForConcreteModelModel()
  428. base.obj = rel = ProxyRelatedModel.objects.create()
  429. base.save()
  430. base = ForConcreteModelModel.objects.get(pk=base.pk)
  431. rel = ConcreteRelatedModel.objects.get(pk=rel.pk)
  432. self.assertEqual(base.obj, rel)
  433. def test_works_normally(self):
  434. """
  435. When for_concrete_model is False, we should still be able to get
  436. an instance of the concrete class.
  437. """
  438. base = ForProxyModelModel()
  439. base.obj = rel = ConcreteRelatedModel.objects.create()
  440. base.save()
  441. base = ForProxyModelModel.objects.get(pk=base.pk)
  442. self.assertEqual(base.obj, rel)
  443. def test_proxy_is_returned(self):
  444. """
  445. Instances of the proxy should be returned when
  446. for_concrete_model is False.
  447. """
  448. base = ForProxyModelModel()
  449. base.obj = ProxyRelatedModel.objects.create()
  450. base.save()
  451. base = ForProxyModelModel.objects.get(pk=base.pk)
  452. self.assertIsInstance(base.obj, ProxyRelatedModel)
  453. def test_query(self):
  454. base = ForProxyModelModel()
  455. base.obj = rel = ConcreteRelatedModel.objects.create()
  456. base.save()
  457. self.assertEqual(rel, ConcreteRelatedModel.objects.get(bases__id=base.id))
  458. def test_query_proxy(self):
  459. base = ForProxyModelModel()
  460. base.obj = rel = ProxyRelatedModel.objects.create()
  461. base.save()
  462. self.assertEqual(rel, ProxyRelatedModel.objects.get(bases__id=base.id))
  463. def test_generic_relation(self):
  464. base = ForProxyModelModel()
  465. base.obj = ProxyRelatedModel.objects.create()
  466. base.save()
  467. base = ForProxyModelModel.objects.get(pk=base.pk)
  468. rel = ProxyRelatedModel.objects.get(pk=base.obj.pk)
  469. self.assertEqual(base, rel.bases.get())
  470. def test_generic_relation_set(self):
  471. base = ForProxyModelModel()
  472. base.obj = ConcreteRelatedModel.objects.create()
  473. base.save()
  474. newrel = ConcreteRelatedModel.objects.create()
  475. newrel.bases = [base]
  476. newrel = ConcreteRelatedModel.objects.get(pk=newrel.pk)
  477. self.assertEqual(base, newrel.bases.get())
  478. class TestInitWithNoneArgument(TestCase):
  479. def test_none_not_allowed(self):
  480. # TaggedItem requires a content_type, initializing with None should
  481. # raise a ValueError.
  482. with six.assertRaisesRegex(self, ValueError,
  483. 'Cannot assign None: "TaggedItem.content_type" does not allow null values'):
  484. TaggedItem(content_object=None)
  485. def test_none_allowed(self):
  486. # AllowsNullGFK doesn't require a content_type, so None argument should
  487. # also be allowed.
  488. AllowsNullGFK(content_object=None)