123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- from django.contrib.contenttypes.models import ContentType
- from django.db.models import ProtectedError, Q, Sum
- from django.forms.models import modelform_factory
- from django.test import TestCase, skipIfDBFeature
- from .models import (
- A,
- Address,
- B,
- Board,
- C,
- Cafe,
- CharLink,
- Company,
- Contact,
- Content,
- D,
- Guild,
- HasLinkThing,
- Link,
- Node,
- Note,
- OddRelation1,
- OddRelation2,
- Organization,
- Person,
- Place,
- Related,
- Restaurant,
- Tag,
- Team,
- TextLink,
- )
- class GenericRelationTests(TestCase):
- def test_inherited_models_content_type(self):
- """
- GenericRelations on inherited classes use the correct content type.
- """
- p = Place.objects.create(name="South Park")
- r = Restaurant.objects.create(name="Chubby's")
- l1 = Link.objects.create(content_object=p)
- l2 = Link.objects.create(content_object=r)
- self.assertEqual(list(p.links.all()), [l1])
- self.assertEqual(list(r.links.all()), [l2])
- def test_reverse_relation_pk(self):
- """
- The correct column name is used for the primary key on the
- originating model of a query. See #12664.
- """
- p = Person.objects.create(account=23, name="Chef")
- Address.objects.create(
- street="123 Anywhere Place",
- city="Conifer",
- state="CO",
- zipcode="80433",
- content_object=p,
- )
- qs = Person.objects.filter(addresses__zipcode="80433")
- self.assertEqual(1, qs.count())
- self.assertEqual("Chef", qs[0].name)
- def test_charlink_delete(self):
- oddrel = OddRelation1.objects.create(name="clink")
- CharLink.objects.create(content_object=oddrel)
- oddrel.delete()
- def test_textlink_delete(self):
- oddrel = OddRelation2.objects.create(name="tlink")
- TextLink.objects.create(content_object=oddrel)
- oddrel.delete()
- def test_charlink_filter(self):
- oddrel = OddRelation1.objects.create(name="clink")
- CharLink.objects.create(content_object=oddrel, value="value")
- self.assertSequenceEqual(
- OddRelation1.objects.filter(clinks__value="value"), [oddrel]
- )
- def test_textlink_filter(self):
- oddrel = OddRelation2.objects.create(name="clink")
- TextLink.objects.create(content_object=oddrel, value="value")
- self.assertSequenceEqual(
- OddRelation2.objects.filter(tlinks__value="value"), [oddrel]
- )
- def test_coerce_object_id_remote_field_cache_persistence(self):
- restaurant = Restaurant.objects.create()
- CharLink.objects.create(content_object=restaurant)
- charlink = CharLink.objects.latest("pk")
- self.assertIs(charlink.content_object, charlink.content_object)
-
- cafe = Cafe.objects.create()
- CharLink.objects.create(content_object=cafe)
- charlink = CharLink.objects.latest("pk")
- self.assertIs(charlink.content_object, charlink.content_object)
- def test_q_object_or(self):
- """
- SQL query parameters for generic relations are properly
- grouped when OR is used (#11535).
- In this bug the first query (below) works while the second, with the
- query parameters the same but in reverse order, does not.
- The issue is that the generic relation conditions do not get properly
- grouped in parentheses.
- """
- note_contact = Contact.objects.create()
- org_contact = Contact.objects.create()
- Note.objects.create(note="note", content_object=note_contact)
- org = Organization.objects.create(name="org name")
- org.contacts.add(org_contact)
-
- qs = Contact.objects.filter(
- Q(notes__note__icontains=r"other note")
- | Q(organizations__name__icontains=r"org name")
- )
- self.assertIn(org_contact, qs)
-
- qs = Contact.objects.filter(
- Q(organizations__name__icontains=r"org name")
- | Q(notes__note__icontains=r"other note")
- )
- self.assertIn(org_contact, qs)
- def test_join_reuse(self):
- qs = Person.objects.filter(addresses__street="foo").filter(
- addresses__street="bar"
- )
- self.assertEqual(str(qs.query).count("JOIN"), 2)
- def test_generic_relation_ordering(self):
- """
- Ordering over a generic relation does not include extraneous
- duplicate results, nor excludes rows not participating in the relation.
- """
- p1 = Place.objects.create(name="South Park")
- p2 = Place.objects.create(name="The City")
- c = Company.objects.create(name="Chubby's Intl.")
- Link.objects.create(content_object=p1)
- Link.objects.create(content_object=c)
- places = list(Place.objects.order_by("links__id"))
- def count_places(place):
- return len([p for p in places if p.id == place.id])
- self.assertEqual(len(places), 2)
- self.assertEqual(count_places(p1), 1)
- self.assertEqual(count_places(p2), 1)
- def test_target_model_len_zero(self):
- """
- Saving a model with a GenericForeignKey to a model instance whose
- __len__ method returns 0 (Team.__len__() here) shouldn't fail (#13085).
- """
- team1 = Team.objects.create(name="Backend devs")
- note = Note(note="Deserve a bonus", content_object=team1)
- note.save()
- def test_target_model_bool_false(self):
- """
- Saving a model with a GenericForeignKey to a model instance whose
- __bool__ method returns False (Guild.__bool__() here) shouldn't fail
- (#13085).
- """
- g1 = Guild.objects.create(name="First guild")
- note = Note(note="Note for guild", content_object=g1)
- note.save()
- @skipIfDBFeature("interprets_empty_strings_as_nulls")
- def test_gfk_to_model_with_empty_pk(self):
- """Test related to #13085"""
-
-
- b1 = Board.objects.create(name="")
- tag = Tag(label="VP", content_object=b1)
- tag.save()
- def test_ticket_20378(self):
-
-
- hs1 = HasLinkThing.objects.create()
- hs2 = HasLinkThing.objects.create()
- hs3 = HasLinkThing.objects.create()
- hs4 = HasLinkThing.objects.create()
- l1 = Link.objects.create(content_object=hs3)
- l2 = Link.objects.create(content_object=hs4)
- self.assertSequenceEqual(HasLinkThing.objects.filter(links=l1), [hs3])
- self.assertSequenceEqual(HasLinkThing.objects.filter(links=l2), [hs4])
- self.assertSequenceEqual(
- HasLinkThing.objects.exclude(links=l2), [hs1, hs2, hs3]
- )
- self.assertSequenceEqual(
- HasLinkThing.objects.exclude(links=l1), [hs1, hs2, hs4]
- )
- def test_ticket_20564(self):
- b1 = B.objects.create()
- b2 = B.objects.create()
- b3 = B.objects.create()
- c1 = C.objects.create(b=b1)
- c2 = C.objects.create(b=b2)
- c3 = C.objects.create(b=b3)
- A.objects.create(flag=None, content_object=b1)
- A.objects.create(flag=True, content_object=b2)
- self.assertSequenceEqual(C.objects.filter(b__a__flag=None), [c1, c3])
- self.assertSequenceEqual(C.objects.exclude(b__a__flag=None), [c2])
- def test_ticket_20564_nullable_fk(self):
- b1 = B.objects.create()
- b2 = B.objects.create()
- b3 = B.objects.create()
- d1 = D.objects.create(b=b1)
- d2 = D.objects.create(b=b2)
- d3 = D.objects.create(b=b3)
- d4 = D.objects.create()
- A.objects.create(flag=None, content_object=b1)
- A.objects.create(flag=True, content_object=b1)
- A.objects.create(flag=True, content_object=b2)
- self.assertSequenceEqual(D.objects.exclude(b__a__flag=None), [d2])
- self.assertSequenceEqual(D.objects.filter(b__a__flag=None), [d1, d3, d4])
- self.assertSequenceEqual(B.objects.filter(a__flag=None), [b1, b3])
- self.assertSequenceEqual(B.objects.exclude(a__flag=None), [b2])
- def test_extra_join_condition(self):
-
-
- self.assertIn(
- "content_type_id", str(B.objects.exclude(a__flag=None).query).lower()
- )
-
-
-
- self.assertNotIn(" join ", str(B.objects.exclude(a__flag=True).query).lower())
- self.assertIn(
- "content_type_id", str(B.objects.exclude(a__flag=True).query).lower()
- )
- def test_annotate(self):
- hs1 = HasLinkThing.objects.create()
- hs2 = HasLinkThing.objects.create()
- HasLinkThing.objects.create()
- b = Board.objects.create(name=str(hs1.pk))
- Link.objects.create(content_object=hs2)
- link = Link.objects.create(content_object=hs1)
- Link.objects.create(content_object=b)
- qs = HasLinkThing.objects.annotate(Sum("links")).filter(pk=hs1.pk)
-
-
-
- self.assertEqual(qs.count(), 1)
- self.assertEqual(qs[0].links__sum, link.id)
- link.delete()
-
-
-
- qs = qs.all()
- self.assertEqual(qs.count(), 1)
-
- self.assertIs(qs[0].links__sum, None)
-
- self.assertEqual(qs.filter(links__sum__isnull=True).count(), 1)
- self.assertEqual(qs.filter(links__sum__isnull=False).count(), 0)
- def test_filter_targets_related_pk(self):
-
-
- HasLinkThing.objects.create(pk=1)
- hs2 = HasLinkThing.objects.create(pk=2)
- link = Link.objects.create(content_object=hs2, pk=1)
- self.assertNotEqual(link.object_id, link.pk)
- self.assertSequenceEqual(HasLinkThing.objects.filter(links=link.pk), [hs2])
- def test_editable_generic_rel(self):
- GenericRelationForm = modelform_factory(HasLinkThing, fields="__all__")
- form = GenericRelationForm()
- self.assertIn("links", form.fields)
- form = GenericRelationForm({"links": None})
- self.assertTrue(form.is_valid())
- form.save()
- links = HasLinkThing._meta.get_field("links")
- self.assertEqual(links.save_form_data_calls, 1)
- def test_ticket_22998(self):
- related = Related.objects.create()
- content = Content.objects.create(related_obj=related)
- Node.objects.create(content=content)
-
-
- with self.assertRaises(ProtectedError):
- related.delete()
- def test_ticket_22982(self):
- place = Place.objects.create(name="My Place")
- self.assertIn("GenericRelatedObjectManager", str(place.links))
- def test_filter_on_related_proxy_model(self):
- place = Place.objects.create()
- Link.objects.create(content_object=place)
- self.assertEqual(Place.objects.get(link_proxy__object_id=place.id), place)
- def test_generic_reverse_relation_with_mti(self):
- """
- Filtering with a reverse generic relation, where the GenericRelation
- comes from multi-table inheritance.
- """
- place = Place.objects.create(name="Test Place")
- link = Link.objects.create(content_object=place)
- result = Link.objects.filter(places=place)
- self.assertCountEqual(result, [link])
- def test_generic_reverse_relation_with_abc(self):
- """
- The reverse generic relation accessor (targets) is created if the
- GenericRelation comes from an abstract base model (HasLinks).
- """
- thing = HasLinkThing.objects.create()
- link = Link.objects.create(content_object=thing)
- self.assertCountEqual(link.targets.all(), [thing])
- def test_generic_reverse_relation_exclude_filter(self):
- place1 = Place.objects.create(name="Test Place 1")
- place2 = Place.objects.create(name="Test Place 2")
- Link.objects.create(content_object=place1)
- link2 = Link.objects.create(content_object=place2)
- qs = Link.objects.filter(~Q(places__name="Test Place 1"))
- self.assertSequenceEqual(qs, [link2])
- qs = Link.objects.exclude(places__name="Test Place 1")
- self.assertSequenceEqual(qs, [link2])
- def test_check_cached_value_pk_different_type(self):
- """Primary key is not checked if the content type doesn't match."""
- board = Board.objects.create(name="some test")
- oddrel = OddRelation1.objects.create(name="clink")
- charlink = CharLink.objects.create(content_object=oddrel)
- charlink = CharLink.objects.get(pk=charlink.pk)
- self.assertEqual(charlink.content_object, oddrel)
- charlink.object_id = board.pk
- charlink.content_type_id = ContentType.objects.get_for_model(Board).id
- self.assertEqual(charlink.content_object, board)
|