1. from django.contrib.contenttypes.models import ContentType
    
  2. from django.core.exceptions import FieldError
    
  3. from django.db.models import Q
    
  4. from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
    
  5. 
    
  6. from .models import (
    
  7.     AllowsNullGFK,
    
  8.     Animal,
    
  9.     Carrot,
    
  10.     Comparison,
    
  11.     ConcreteRelatedModel,
    
  12.     ForConcreteModelModel,
    
  13.     ForProxyModelModel,
    
  14.     Gecko,
    
  15.     ManualPK,
    
  16.     Mineral,
    
  17.     ProxyRelatedModel,
    
  18.     Rock,
    
  19.     TaggedItem,
    
  20.     ValuableRock,
    
  21.     ValuableTaggedItem,
    
  22.     Vegetable,
    
  23. )
    
  24. 
    
  25. 
    
  26. class GenericRelationsTests(TestCase):
    
  27.     @classmethod
    
  28.     def setUpTestData(cls):
    
  29.         cls.lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo")
    
  30.         cls.platypus = Animal.objects.create(
    
  31.             common_name="Platypus",
    
  32.             latin_name="Ornithorhynchus anatinus",
    
  33.         )
    
  34.         Vegetable.objects.create(name="Eggplant", is_yucky=True)
    
  35.         cls.bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
    
  36.         cls.quartz = Mineral.objects.create(name="Quartz", hardness=7)
    
  37. 
    
  38.         # Tagging stuff.
    
  39.         cls.fatty = cls.bacon.tags.create(tag="fatty")
    
  40.         cls.salty = cls.bacon.tags.create(tag="salty")
    
  41.         cls.yellow = cls.lion.tags.create(tag="yellow")
    
  42.         cls.hairy = cls.lion.tags.create(tag="hairy")
    
  43. 
    
  44.     def comp_func(self, obj):
    
  45.         # Original list of tags:
    
  46.         return obj.tag, obj.content_type.model_class(), obj.object_id
    
  47. 
    
  48.     async def test_generic_async_acreate(self):
    
  49.         await self.bacon.tags.acreate(tag="orange")
    
  50.         self.assertEqual(await self.bacon.tags.acount(), 3)
    
  51. 
    
  52.     def test_generic_update_or_create_when_created(self):
    
  53.         """
    
  54.         Should be able to use update_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.update_or_create(tag="stinky")
    
  59.         self.assertTrue(created)
    
  60.         self.assertEqual(count + 1, self.bacon.tags.count())
    
  61. 
    
  62.     def test_generic_update_or_create_when_updated(self):
    
  63.         """
    
  64.         Should be able to use update_or_create from the generic related manager
    
  65.         to update a tag. Refs #23611.
    
  66.         """
    
  67.         count = self.bacon.tags.count()
    
  68.         tag = self.bacon.tags.create(tag="stinky")
    
  69.         self.assertEqual(count + 1, self.bacon.tags.count())
    
  70.         tag, created = self.bacon.tags.update_or_create(
    
  71.             defaults={"tag": "juicy"}, id=tag.id
    
  72.         )
    
  73.         self.assertFalse(created)
    
  74.         self.assertEqual(count + 1, self.bacon.tags.count())
    
  75.         self.assertEqual(tag.tag, "juicy")
    
  76. 
    
  77.     async def test_generic_async_aupdate_or_create(self):
    
  78.         tag, created = await self.bacon.tags.aupdate_or_create(
    
  79.             id=self.fatty.id, defaults={"tag": "orange"}
    
  80.         )
    
  81.         self.assertIs(created, False)
    
  82.         self.assertEqual(tag.tag, "orange")
    
  83.         self.assertEqual(await self.bacon.tags.acount(), 2)
    
  84.         tag, created = await self.bacon.tags.aupdate_or_create(tag="pink")
    
  85.         self.assertIs(created, True)
    
  86.         self.assertEqual(await self.bacon.tags.acount(), 3)
    
  87.         self.assertEqual(tag.tag, "pink")
    
  88. 
    
  89.     def test_generic_get_or_create_when_created(self):
    
  90.         """
    
  91.         Should be able to use get_or_create from the generic related manager
    
  92.         to create a tag. Refs #23611.
    
  93.         """
    
  94.         count = self.bacon.tags.count()
    
  95.         tag, created = self.bacon.tags.get_or_create(tag="stinky")
    
  96.         self.assertTrue(created)
    
  97.         self.assertEqual(count + 1, self.bacon.tags.count())
    
  98. 
    
  99.     def test_generic_get_or_create_when_exists(self):
    
  100.         """
    
  101.         Should be able to use get_or_create from the generic related manager
    
  102.         to get a tag. Refs #23611.
    
  103.         """
    
  104.         count = self.bacon.tags.count()
    
  105.         tag = self.bacon.tags.create(tag="stinky")
    
  106.         self.assertEqual(count + 1, self.bacon.tags.count())
    
  107.         tag, created = self.bacon.tags.get_or_create(
    
  108.             id=tag.id, defaults={"tag": "juicy"}
    
  109.         )
    
  110.         self.assertFalse(created)
    
  111.         self.assertEqual(count + 1, self.bacon.tags.count())
    
  112.         # shouldn't had changed the tag
    
  113.         self.assertEqual(tag.tag, "stinky")
    
  114. 
    
  115.     async def test_generic_async_aget_or_create(self):
    
  116.         tag, created = await self.bacon.tags.aget_or_create(
    
  117.             id=self.fatty.id, defaults={"tag": "orange"}
    
  118.         )
    
  119.         self.assertIs(created, False)
    
  120.         self.assertEqual(tag.tag, "fatty")
    
  121.         self.assertEqual(await self.bacon.tags.acount(), 2)
    
  122.         tag, created = await self.bacon.tags.aget_or_create(tag="orange")
    
  123.         self.assertIs(created, True)
    
  124.         self.assertEqual(await self.bacon.tags.acount(), 3)
    
  125.         self.assertEqual(tag.tag, "orange")
    
  126. 
    
  127.     def test_generic_relations_m2m_mimic(self):
    
  128.         """
    
  129.         Objects with declared GenericRelations can be tagged directly -- the
    
  130.         API mimics the many-to-many API.
    
  131.         """
    
  132.         self.assertSequenceEqual(self.lion.tags.all(), [self.hairy, self.yellow])
    
  133.         self.assertSequenceEqual(self.bacon.tags.all(), [self.fatty, self.salty])
    
  134. 
    
  135.     def test_access_content_object(self):
    
  136.         """
    
  137.         Test accessing the content object like a foreign key.
    
  138.         """
    
  139.         tagged_item = TaggedItem.objects.get(tag="salty")
    
  140.         self.assertEqual(tagged_item.content_object, self.bacon)
    
  141. 
    
  142.     def test_query_content_object(self):
    
  143.         qs = TaggedItem.objects.filter(animal__isnull=False).order_by(
    
  144.             "animal__common_name", "tag"
    
  145.         )
    
  146.         self.assertSequenceEqual(qs, [self.hairy, self.yellow])
    
  147. 
    
  148.         mpk = ManualPK.objects.create(id=1)
    
  149.         mpk.tags.create(tag="mpk")
    
  150.         qs = TaggedItem.objects.filter(
    
  151.             Q(animal__isnull=False) | Q(manualpk__id=1)
    
  152.         ).order_by("tag")
    
  153.         self.assertQuerysetEqual(qs, ["hairy", "mpk", "yellow"], lambda x: x.tag)
    
  154. 
    
  155.     def test_exclude_generic_relations(self):
    
  156.         """
    
  157.         Test lookups over an object without GenericRelations.
    
  158.         """
    
  159.         # Recall that the Mineral class doesn't have an explicit GenericRelation
    
  160.         # defined. That's OK, because you can create TaggedItems explicitly.
    
  161.         # However, excluding GenericRelations means your lookups have to be a
    
  162.         # bit more explicit.
    
  163.         shiny = TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
    
  164.         clearish = TaggedItem.objects.create(content_object=self.quartz, tag="clearish")
    
  165. 
    
  166.         ctype = ContentType.objects.get_for_model(self.quartz)
    
  167.         q = TaggedItem.objects.filter(
    
  168.             content_type__pk=ctype.id, object_id=self.quartz.id
    
  169.         )
    
  170.         self.assertSequenceEqual(q, [clearish, shiny])
    
  171. 
    
  172.     def test_access_via_content_type(self):
    
  173.         """
    
  174.         Test lookups through content type.
    
  175.         """
    
  176.         self.lion.delete()
    
  177.         self.platypus.tags.create(tag="fatty")
    
  178. 
    
  179.         ctype = ContentType.objects.get_for_model(self.platypus)
    
  180. 
    
  181.         self.assertSequenceEqual(
    
  182.             Animal.objects.filter(tags__content_type=ctype),
    
  183.             [self.platypus],
    
  184.         )
    
  185. 
    
  186.     def test_set_foreign_key(self):
    
  187.         """
    
  188.         You can set a generic foreign key in the way you'd expect.
    
  189.         """
    
  190.         tag1 = TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
    
  191.         tag1.content_object = self.platypus
    
  192.         tag1.save()
    
  193. 
    
  194.         self.assertSequenceEqual(self.platypus.tags.all(), [tag1])
    
  195. 
    
  196.     def test_queries_across_generic_relations(self):
    
  197.         """
    
  198.         Queries across generic relations respect the content types. Even though
    
  199.         there are two TaggedItems with a tag of "fatty", this query only pulls
    
  200.         out the one with the content type related to Animals.
    
  201.         """
    
  202.         self.assertSequenceEqual(
    
  203.             Animal.objects.order_by("common_name"),
    
  204.             [self.lion, self.platypus],
    
  205.         )
    
  206. 
    
  207.     def test_queries_content_type_restriction(self):
    
  208.         """
    
  209.         Create another fatty tagged instance with different PK to ensure there
    
  210.         is a content type restriction in the generated queries below.
    
  211.         """
    
  212.         mpk = ManualPK.objects.create(id=self.lion.pk)
    
  213.         mpk.tags.create(tag="fatty")
    
  214.         self.platypus.tags.create(tag="fatty")
    
  215. 
    
  216.         self.assertSequenceEqual(
    
  217.             Animal.objects.filter(tags__tag="fatty"),
    
  218.             [self.platypus],
    
  219.         )
    
  220.         self.assertSequenceEqual(
    
  221.             Animal.objects.exclude(tags__tag="fatty"),
    
  222.             [self.lion],
    
  223.         )
    
  224. 
    
  225.     def test_object_deletion_with_generic_relation(self):
    
  226.         """
    
  227.         If you delete an object with an explicit Generic relation, the related
    
  228.         objects are deleted when the source object is deleted.
    
  229.         """
    
  230.         self.assertQuerysetEqual(
    
  231.             TaggedItem.objects.all(),
    
  232.             [
    
  233.                 ("fatty", Vegetable, self.bacon.pk),
    
  234.                 ("hairy", Animal, self.lion.pk),
    
  235.                 ("salty", Vegetable, self.bacon.pk),
    
  236.                 ("yellow", Animal, self.lion.pk),
    
  237.             ],
    
  238.             self.comp_func,
    
  239.         )
    
  240.         self.lion.delete()
    
  241. 
    
  242.         self.assertQuerysetEqual(
    
  243.             TaggedItem.objects.all(),
    
  244.             [
    
  245.                 ("fatty", Vegetable, self.bacon.pk),
    
  246.                 ("salty", Vegetable, self.bacon.pk),
    
  247.             ],
    
  248.             self.comp_func,
    
  249.         )
    
  250. 
    
  251.     def test_object_deletion_without_generic_relation(self):
    
  252.         """
    
  253.         If Generic Relation is not explicitly defined, any related objects
    
  254.         remain after deletion of the source object.
    
  255.         """
    
  256.         TaggedItem.objects.create(content_object=self.quartz, tag="clearish")
    
  257.         quartz_pk = self.quartz.pk
    
  258.         self.quartz.delete()
    
  259.         self.assertQuerysetEqual(
    
  260.             TaggedItem.objects.all(),
    
  261.             [
    
  262.                 ("clearish", Mineral, quartz_pk),
    
  263.                 ("fatty", Vegetable, self.bacon.pk),
    
  264.                 ("hairy", Animal, self.lion.pk),
    
  265.                 ("salty", Vegetable, self.bacon.pk),
    
  266.                 ("yellow", Animal, self.lion.pk),
    
  267.             ],
    
  268.             self.comp_func,
    
  269.         )
    
  270. 
    
  271.     def test_tag_deletion_related_objects_unaffected(self):
    
  272.         """
    
  273.         If you delete a tag, the objects using the tag are unaffected (other
    
  274.         than losing a tag).
    
  275.         """
    
  276.         ctype = ContentType.objects.get_for_model(self.lion)
    
  277.         tag = TaggedItem.objects.get(
    
  278.             content_type__pk=ctype.id, object_id=self.lion.id, tag="hairy"
    
  279.         )
    
  280.         tag.delete()
    
  281. 
    
  282.         self.assertSequenceEqual(self.lion.tags.all(), [self.yellow])
    
  283.         self.assertQuerysetEqual(
    
  284.             TaggedItem.objects.all(),
    
  285.             [
    
  286.                 ("fatty", Vegetable, self.bacon.pk),
    
  287.                 ("salty", Vegetable, self.bacon.pk),
    
  288.                 ("yellow", Animal, self.lion.pk),
    
  289.             ],
    
  290.             self.comp_func,
    
  291.         )
    
  292. 
    
  293.     def test_add_bulk(self):
    
  294.         bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
    
  295.         t1 = TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
    
  296.         t2 = TaggedItem.objects.create(content_object=self.quartz, tag="clearish")
    
  297.         # One update() query.
    
  298.         with self.assertNumQueries(1):
    
  299.             bacon.tags.add(t1, t2)
    
  300.         self.assertEqual(t1.content_object, bacon)
    
  301.         self.assertEqual(t2.content_object, bacon)
    
  302. 
    
  303.     def test_add_bulk_false(self):
    
  304.         bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
    
  305.         t1 = TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
    
  306.         t2 = TaggedItem.objects.create(content_object=self.quartz, tag="clearish")
    
  307.         # One save() for each object.
    
  308.         with self.assertNumQueries(2):
    
  309.             bacon.tags.add(t1, t2, bulk=False)
    
  310.         self.assertEqual(t1.content_object, bacon)
    
  311.         self.assertEqual(t2.content_object, bacon)
    
  312. 
    
  313.     def test_add_rejects_unsaved_objects(self):
    
  314.         t1 = TaggedItem(content_object=self.quartz, tag="shiny")
    
  315.         msg = (
    
  316.             "<TaggedItem: shiny> instance isn't saved. Use bulk=False or save the "
    
  317.             "object first."
    
  318.         )
    
  319.         with self.assertRaisesMessage(ValueError, msg):
    
  320.             self.bacon.tags.add(t1)
    
  321. 
    
  322.     def test_add_rejects_wrong_instances(self):
    
  323.         msg = "'TaggedItem' instance expected, got <Animal: Lion>"
    
  324.         with self.assertRaisesMessage(TypeError, msg):
    
  325.             self.bacon.tags.add(self.lion)
    
  326. 
    
  327.     def test_set(self):
    
  328.         bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
    
  329.         fatty = bacon.tags.create(tag="fatty")
    
  330.         salty = bacon.tags.create(tag="salty")
    
  331. 
    
  332.         bacon.tags.set([fatty, salty])
    
  333.         self.assertSequenceEqual(bacon.tags.all(), [fatty, salty])
    
  334. 
    
  335.         bacon.tags.set([fatty])
    
  336.         self.assertSequenceEqual(bacon.tags.all(), [fatty])
    
  337. 
    
  338.         bacon.tags.set([])
    
  339.         self.assertSequenceEqual(bacon.tags.all(), [])
    
  340. 
    
  341.         bacon.tags.set([fatty, salty], bulk=False, clear=True)
    
  342.         self.assertSequenceEqual(bacon.tags.all(), [fatty, salty])
    
  343. 
    
  344.         bacon.tags.set([fatty], bulk=False, clear=True)
    
  345.         self.assertSequenceEqual(bacon.tags.all(), [fatty])
    
  346. 
    
  347.         bacon.tags.set([], clear=True)
    
  348.         self.assertSequenceEqual(bacon.tags.all(), [])
    
  349. 
    
  350.     def test_assign(self):
    
  351.         bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
    
  352.         fatty = bacon.tags.create(tag="fatty")
    
  353.         salty = bacon.tags.create(tag="salty")
    
  354. 
    
  355.         bacon.tags.set([fatty, salty])
    
  356.         self.assertSequenceEqual(bacon.tags.all(), [fatty, salty])
    
  357. 
    
  358.         bacon.tags.set([fatty])
    
  359.         self.assertSequenceEqual(bacon.tags.all(), [fatty])
    
  360. 
    
  361.         bacon.tags.set([])
    
  362.         self.assertSequenceEqual(bacon.tags.all(), [])
    
  363. 
    
  364.     def test_assign_with_queryset(self):
    
  365.         # Querysets used in reverse GFK assignments are pre-evaluated so their
    
  366.         # value isn't affected by the clearing operation
    
  367.         # in ManyRelatedManager.set() (#19816).
    
  368.         bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
    
  369.         bacon.tags.create(tag="fatty")
    
  370.         bacon.tags.create(tag="salty")
    
  371.         self.assertEqual(2, bacon.tags.count())
    
  372. 
    
  373.         qs = bacon.tags.filter(tag="fatty")
    
  374.         bacon.tags.set(qs)
    
  375. 
    
  376.         self.assertEqual(1, bacon.tags.count())
    
  377.         self.assertEqual(1, qs.count())
    
  378. 
    
  379.     def test_clear(self):
    
  380.         self.assertSequenceEqual(
    
  381.             TaggedItem.objects.order_by("tag"),
    
  382.             [self.fatty, self.hairy, self.salty, self.yellow],
    
  383.         )
    
  384.         self.bacon.tags.clear()
    
  385.         self.assertSequenceEqual(self.bacon.tags.all(), [])
    
  386.         self.assertSequenceEqual(
    
  387.             TaggedItem.objects.order_by("tag"),
    
  388.             [self.hairy, self.yellow],
    
  389.         )
    
  390. 
    
  391.     def test_remove(self):
    
  392.         self.assertSequenceEqual(
    
  393.             TaggedItem.objects.order_by("tag"),
    
  394.             [self.fatty, self.hairy, self.salty, self.yellow],
    
  395.         )
    
  396.         self.bacon.tags.remove(self.fatty)
    
  397.         self.assertSequenceEqual(self.bacon.tags.all(), [self.salty])
    
  398.         self.assertSequenceEqual(
    
  399.             TaggedItem.objects.order_by("tag"),
    
  400.             [self.hairy, self.salty, self.yellow],
    
  401.         )
    
  402. 
    
  403.     def test_generic_relation_related_name_default(self):
    
  404.         # GenericRelation isn't usable from the reverse side by default.
    
  405.         msg = (
    
  406.             "Cannot resolve keyword 'vegetable' into field. Choices are: "
    
  407.             "animal, content_object, content_type, content_type_id, id, "
    
  408.             "manualpk, object_id, tag, valuabletaggeditem"
    
  409.         )
    
  410.         with self.assertRaisesMessage(FieldError, msg):
    
  411.             TaggedItem.objects.filter(vegetable__isnull=True)
    
  412. 
    
  413.     def test_multiple_gfk(self):
    
  414.         # Simple tests for multiple GenericForeignKeys
    
  415.         # only uses one model, since the above tests should be sufficient.
    
  416.         tiger = Animal.objects.create(common_name="tiger")
    
  417.         cheetah = Animal.objects.create(common_name="cheetah")
    
  418.         bear = Animal.objects.create(common_name="bear")
    
  419. 
    
  420.         # Create directly
    
  421.         c1 = Comparison.objects.create(
    
  422.             first_obj=cheetah, other_obj=tiger, comparative="faster"
    
  423.         )
    
  424.         c2 = Comparison.objects.create(
    
  425.             first_obj=tiger, other_obj=cheetah, comparative="cooler"
    
  426.         )
    
  427. 
    
  428.         # Create using GenericRelation
    
  429.         c3 = tiger.comparisons.create(other_obj=bear, comparative="cooler")
    
  430.         c4 = tiger.comparisons.create(other_obj=cheetah, comparative="stronger")
    
  431.         self.assertSequenceEqual(cheetah.comparisons.all(), [c1])
    
  432. 
    
  433.         # Filtering works
    
  434.         self.assertCountEqual(
    
  435.             tiger.comparisons.filter(comparative="cooler"),
    
  436.             [c2, c3],
    
  437.         )
    
  438. 
    
  439.         # Filtering and deleting works
    
  440.         subjective = ["cooler"]
    
  441.         tiger.comparisons.filter(comparative__in=subjective).delete()
    
  442.         self.assertCountEqual(Comparison.objects.all(), [c1, c4])
    
  443. 
    
  444.         # If we delete cheetah, Comparisons with cheetah as 'first_obj' will be
    
  445.         # deleted since Animal has an explicit GenericRelation to Comparison
    
  446.         # through first_obj. Comparisons with cheetah as 'other_obj' will not
    
  447.         # be deleted.
    
  448.         cheetah.delete()
    
  449.         self.assertSequenceEqual(Comparison.objects.all(), [c4])
    
  450. 
    
  451.     def test_gfk_subclasses(self):
    
  452.         # GenericForeignKey should work with subclasses (see #8309)
    
  453.         quartz = Mineral.objects.create(name="Quartz", hardness=7)
    
  454.         valuedtag = ValuableTaggedItem.objects.create(
    
  455.             content_object=quartz, tag="shiny", value=10
    
  456.         )
    
  457.         self.assertEqual(valuedtag.content_object, quartz)
    
  458. 
    
  459.     def test_generic_relation_to_inherited_child(self):
    
  460.         # GenericRelations to models that use multi-table inheritance work.
    
  461.         granite = ValuableRock.objects.create(name="granite", hardness=5)
    
  462.         ValuableTaggedItem.objects.create(
    
  463.             content_object=granite, tag="countertop", value=1
    
  464.         )
    
  465.         self.assertEqual(ValuableRock.objects.filter(tags__value=1).count(), 1)
    
  466.         # We're generating a slightly inefficient query for tags__tag - we
    
  467.         # first join ValuableRock -> TaggedItem -> ValuableTaggedItem, and then
    
  468.         # we fetch tag by joining TaggedItem from ValuableTaggedItem. The last
    
  469.         # join isn't necessary, as TaggedItem <-> ValuableTaggedItem is a
    
  470.         # one-to-one join.
    
  471.         self.assertEqual(ValuableRock.objects.filter(tags__tag="countertop").count(), 1)
    
  472.         granite.delete()  # deleting the rock should delete the related tag.
    
  473.         self.assertEqual(ValuableTaggedItem.objects.count(), 0)
    
  474. 
    
  475.     def test_gfk_manager(self):
    
  476.         # GenericForeignKey should not use the default manager (which may
    
  477.         # filter objects).
    
  478.         tailless = Gecko.objects.create(has_tail=False)
    
  479.         tag = TaggedItem.objects.create(content_object=tailless, tag="lizard")
    
  480.         self.assertEqual(tag.content_object, tailless)
    
  481. 
    
  482.     def test_subclasses_with_gen_rel(self):
    
  483.         """
    
  484.         Concrete model subclasses with generic relations work
    
  485.         correctly (ticket 11263).
    
  486.         """
    
  487.         granite = Rock.objects.create(name="granite", hardness=5)
    
  488.         TaggedItem.objects.create(content_object=granite, tag="countertop")
    
  489.         self.assertEqual(Rock.objects.get(tags__tag="countertop"), granite)
    
  490. 
    
  491.     def test_subclasses_with_parent_gen_rel(self):
    
  492.         """
    
  493.         Generic relations on a base class (Vegetable) work correctly in
    
  494.         subclasses (Carrot).
    
  495.         """
    
  496.         bear = Carrot.objects.create(name="carrot")
    
  497.         TaggedItem.objects.create(content_object=bear, tag="orange")
    
  498.         self.assertEqual(Carrot.objects.get(tags__tag="orange"), bear)
    
  499. 
    
  500.     def test_get_or_create(self):
    
  501.         # get_or_create should work with virtual fields (content_object)
    
  502.         quartz = Mineral.objects.create(name="Quartz", hardness=7)
    
  503.         tag, created = TaggedItem.objects.get_or_create(
    
  504.             tag="shiny", defaults={"content_object": quartz}
    
  505.         )
    
  506.         self.assertTrue(created)
    
  507.         self.assertEqual(tag.tag, "shiny")
    
  508.         self.assertEqual(tag.content_object.id, quartz.id)
    
  509. 
    
  510.     def test_update_or_create_defaults(self):
    
  511.         # update_or_create should work with virtual fields (content_object)
    
  512.         quartz = Mineral.objects.create(name="Quartz", hardness=7)
    
  513.         diamond = Mineral.objects.create(name="Diamond", hardness=7)
    
  514.         tag, created = TaggedItem.objects.update_or_create(
    
  515.             tag="shiny", defaults={"content_object": quartz}
    
  516.         )
    
  517.         self.assertTrue(created)
    
  518.         self.assertEqual(tag.content_object.id, quartz.id)
    
  519. 
    
  520.         tag, created = TaggedItem.objects.update_or_create(
    
  521.             tag="shiny", defaults={"content_object": diamond}
    
  522.         )
    
  523.         self.assertFalse(created)
    
  524.         self.assertEqual(tag.content_object.id, diamond.id)
    
  525. 
    
  526.     def test_query_content_type(self):
    
  527.         msg = "Field 'content_object' does not generate an automatic reverse relation"
    
  528.         with self.assertRaisesMessage(FieldError, msg):
    
  529.             TaggedItem.objects.get(content_object="")
    
  530. 
    
  531.     def test_unsaved_generic_foreign_key_parent_save(self):
    
  532.         quartz = Mineral(name="Quartz", hardness=7)
    
  533.         tagged_item = TaggedItem(tag="shiny", content_object=quartz)
    
  534.         msg = (
    
  535.             "save() prohibited to prevent data loss due to unsaved related object "
    
  536.             "'content_object'."
    
  537.         )
    
  538.         with self.assertRaisesMessage(ValueError, msg):
    
  539.             tagged_item.save()
    
  540. 
    
  541.     @skipUnlessDBFeature("has_bulk_insert")
    
  542.     def test_unsaved_generic_foreign_key_parent_bulk_create(self):
    
  543.         quartz = Mineral(name="Quartz", hardness=7)
    
  544.         tagged_item = TaggedItem(tag="shiny", content_object=quartz)
    
  545.         msg = (
    
  546.             "bulk_create() prohibited to prevent data loss due to unsaved related "
    
  547.             "object 'content_object'."
    
  548.         )
    
  549.         with self.assertRaisesMessage(ValueError, msg):
    
  550.             TaggedItem.objects.bulk_create([tagged_item])
    
  551. 
    
  552.     def test_cache_invalidation_for_content_type_id(self):
    
  553.         # Create a Vegetable and Mineral with the same id.
    
  554.         new_id = (
    
  555.             max(
    
  556.                 Vegetable.objects.order_by("-id")[0].id,
    
  557.                 Mineral.objects.order_by("-id")[0].id,
    
  558.             )
    
  559.             + 1
    
  560.         )
    
  561.         broccoli = Vegetable.objects.create(id=new_id, name="Broccoli")
    
  562.         diamond = Mineral.objects.create(id=new_id, name="Diamond", hardness=7)
    
  563.         tag = TaggedItem.objects.create(content_object=broccoli, tag="yummy")
    
  564.         tag.content_type = ContentType.objects.get_for_model(diamond)
    
  565.         self.assertEqual(tag.content_object, diamond)
    
  566. 
    
  567.     def test_cache_invalidation_for_object_id(self):
    
  568.         broccoli = Vegetable.objects.create(name="Broccoli")
    
  569.         cauliflower = Vegetable.objects.create(name="Cauliflower")
    
  570.         tag = TaggedItem.objects.create(content_object=broccoli, tag="yummy")
    
  571.         tag.object_id = cauliflower.id
    
  572.         self.assertEqual(tag.content_object, cauliflower)
    
  573. 
    
  574.     def test_assign_content_object_in_init(self):
    
  575.         spinach = Vegetable(name="spinach")
    
  576.         tag = TaggedItem(content_object=spinach)
    
  577.         self.assertEqual(tag.content_object, spinach)
    
  578. 
    
  579.     def test_create_after_prefetch(self):
    
  580.         platypus = Animal.objects.prefetch_related("tags").get(pk=self.platypus.pk)
    
  581.         self.assertSequenceEqual(platypus.tags.all(), [])
    
  582.         weird_tag = platypus.tags.create(tag="weird")
    
  583.         self.assertSequenceEqual(platypus.tags.all(), [weird_tag])
    
  584. 
    
  585.     def test_add_after_prefetch(self):
    
  586.         platypus = Animal.objects.prefetch_related("tags").get(pk=self.platypus.pk)
    
  587.         self.assertSequenceEqual(platypus.tags.all(), [])
    
  588.         weird_tag = TaggedItem.objects.create(tag="weird", content_object=platypus)
    
  589.         platypus.tags.add(weird_tag)
    
  590.         self.assertSequenceEqual(platypus.tags.all(), [weird_tag])
    
  591. 
    
  592.     def test_remove_after_prefetch(self):
    
  593.         weird_tag = self.platypus.tags.create(tag="weird")
    
  594.         platypus = Animal.objects.prefetch_related("tags").get(pk=self.platypus.pk)
    
  595.         self.assertSequenceEqual(platypus.tags.all(), [weird_tag])
    
  596.         platypus.tags.remove(weird_tag)
    
  597.         self.assertSequenceEqual(platypus.tags.all(), [])
    
  598. 
    
  599.     def test_clear_after_prefetch(self):
    
  600.         weird_tag = self.platypus.tags.create(tag="weird")
    
  601.         platypus = Animal.objects.prefetch_related("tags").get(pk=self.platypus.pk)
    
  602.         self.assertSequenceEqual(platypus.tags.all(), [weird_tag])
    
  603.         platypus.tags.clear()
    
  604.         self.assertSequenceEqual(platypus.tags.all(), [])
    
  605. 
    
  606.     def test_set_after_prefetch(self):
    
  607.         platypus = Animal.objects.prefetch_related("tags").get(pk=self.platypus.pk)
    
  608.         self.assertSequenceEqual(platypus.tags.all(), [])
    
  609.         furry_tag = TaggedItem.objects.create(tag="furry", content_object=platypus)
    
  610.         platypus.tags.set([furry_tag])
    
  611.         self.assertSequenceEqual(platypus.tags.all(), [furry_tag])
    
  612.         weird_tag = TaggedItem.objects.create(tag="weird", content_object=platypus)
    
  613.         platypus.tags.set([weird_tag])
    
  614.         self.assertSequenceEqual(platypus.tags.all(), [weird_tag])
    
  615. 
    
  616.     def test_add_then_remove_after_prefetch(self):
    
  617.         furry_tag = self.platypus.tags.create(tag="furry")
    
  618.         platypus = Animal.objects.prefetch_related("tags").get(pk=self.platypus.pk)
    
  619.         self.assertSequenceEqual(platypus.tags.all(), [furry_tag])
    
  620.         weird_tag = self.platypus.tags.create(tag="weird")
    
  621.         platypus.tags.add(weird_tag)
    
  622.         self.assertSequenceEqual(platypus.tags.all(), [furry_tag, weird_tag])
    
  623.         platypus.tags.remove(weird_tag)
    
  624.         self.assertSequenceEqual(platypus.tags.all(), [furry_tag])
    
  625. 
    
  626.     def test_prefetch_related_different_content_types(self):
    
  627.         TaggedItem.objects.create(content_object=self.platypus, tag="prefetch_tag_1")
    
  628.         TaggedItem.objects.create(
    
  629.             content_object=Vegetable.objects.create(name="Broccoli"),
    
  630.             tag="prefetch_tag_2",
    
  631.         )
    
  632.         TaggedItem.objects.create(
    
  633.             content_object=Animal.objects.create(common_name="Bear"),
    
  634.             tag="prefetch_tag_3",
    
  635.         )
    
  636.         qs = TaggedItem.objects.filter(
    
  637.             tag__startswith="prefetch_tag_",
    
  638.         ).prefetch_related("content_object", "content_object__tags")
    
  639.         with self.assertNumQueries(4):
    
  640.             tags = list(qs)
    
  641.         for tag in tags:
    
  642.             self.assertSequenceEqual(tag.content_object.tags.all(), [tag])
    
  643. 
    
  644.     def test_prefetch_related_custom_object_id(self):
    
  645.         tiger = Animal.objects.create(common_name="tiger")
    
  646.         cheetah = Animal.objects.create(common_name="cheetah")
    
  647.         Comparison.objects.create(
    
  648.             first_obj=cheetah,
    
  649.             other_obj=tiger,
    
  650.             comparative="faster",
    
  651.         )
    
  652.         Comparison.objects.create(
    
  653.             first_obj=tiger,
    
  654.             other_obj=cheetah,
    
  655.             comparative="cooler",
    
  656.         )
    
  657.         qs = Comparison.objects.prefetch_related("first_obj__comparisons")
    
  658.         for comparison in qs:
    
  659.             self.assertSequenceEqual(
    
  660.                 comparison.first_obj.comparisons.all(), [comparison]
    
  661.             )
    
  662. 
    
  663. 
    
  664. class ProxyRelatedModelTest(TestCase):
    
  665.     def test_default_behavior(self):
    
  666.         """
    
  667.         The default for for_concrete_model should be True
    
  668.         """
    
  669.         base = ForConcreteModelModel()
    
  670.         base.obj = rel = ProxyRelatedModel.objects.create()
    
  671.         base.save()
    
  672. 
    
  673.         base = ForConcreteModelModel.objects.get(pk=base.pk)
    
  674.         rel = ConcreteRelatedModel.objects.get(pk=rel.pk)
    
  675.         self.assertEqual(base.obj, rel)
    
  676. 
    
  677.     def test_works_normally(self):
    
  678.         """
    
  679.         When for_concrete_model is False, we should still be able to get
    
  680.         an instance of the concrete class.
    
  681.         """
    
  682.         base = ForProxyModelModel()
    
  683.         base.obj = rel = ConcreteRelatedModel.objects.create()
    
  684.         base.save()
    
  685. 
    
  686.         base = ForProxyModelModel.objects.get(pk=base.pk)
    
  687.         self.assertEqual(base.obj, rel)
    
  688. 
    
  689.     def test_proxy_is_returned(self):
    
  690.         """
    
  691.         Instances of the proxy should be returned when
    
  692.         for_concrete_model is False.
    
  693.         """
    
  694.         base = ForProxyModelModel()
    
  695.         base.obj = ProxyRelatedModel.objects.create()
    
  696.         base.save()
    
  697. 
    
  698.         base = ForProxyModelModel.objects.get(pk=base.pk)
    
  699.         self.assertIsInstance(base.obj, ProxyRelatedModel)
    
  700. 
    
  701.     def test_query(self):
    
  702.         base = ForProxyModelModel()
    
  703.         base.obj = rel = ConcreteRelatedModel.objects.create()
    
  704.         base.save()
    
  705. 
    
  706.         self.assertEqual(rel, ConcreteRelatedModel.objects.get(bases__id=base.id))
    
  707. 
    
  708.     def test_query_proxy(self):
    
  709.         base = ForProxyModelModel()
    
  710.         base.obj = rel = ProxyRelatedModel.objects.create()
    
  711.         base.save()
    
  712. 
    
  713.         self.assertEqual(rel, ProxyRelatedModel.objects.get(bases__id=base.id))
    
  714. 
    
  715.     def test_generic_relation(self):
    
  716.         base = ForProxyModelModel()
    
  717.         base.obj = ProxyRelatedModel.objects.create()
    
  718.         base.save()
    
  719. 
    
  720.         base = ForProxyModelModel.objects.get(pk=base.pk)
    
  721.         rel = ProxyRelatedModel.objects.get(pk=base.obj.pk)
    
  722.         self.assertEqual(base, rel.bases.get())
    
  723. 
    
  724.     def test_generic_relation_set(self):
    
  725.         base = ForProxyModelModel()
    
  726.         base.obj = ConcreteRelatedModel.objects.create()
    
  727.         base.save()
    
  728.         newrel = ConcreteRelatedModel.objects.create()
    
  729. 
    
  730.         newrel.bases.set([base])
    
  731.         newrel = ConcreteRelatedModel.objects.get(pk=newrel.pk)
    
  732.         self.assertEqual(base, newrel.bases.get())
    
  733. 
    
  734. 
    
  735. class TestInitWithNoneArgument(SimpleTestCase):
    
  736.     def test_none_allowed(self):
    
  737.         # AllowsNullGFK doesn't require a content_type, so None argument should
    
  738.         # also be allowed.
    
  739.         AllowsNullGFK(content_object=None)
    
  740.         # TaggedItem requires a content_type but initializing with None should
    
  741.         # be allowed.
    
  742.         TaggedItem(content_object=None)