Skip to content

Commit

Permalink
Fixed #30493 -- Fixed GenericRelation's prefetch queryset
Browse files Browse the repository at this point in the history
  • Loading branch information
can committed May 29, 2019
1 parent 5ec4497 commit 37980d9
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 7 deletions.
26 changes: 19 additions & 7 deletions django/contrib/contenttypes/fields.py
@@ -1,4 +1,6 @@
import operator
from collections import defaultdict
from functools import reduce

from django.contrib.contenttypes.models import ContentType
from django.core import checks
Expand Down Expand Up @@ -523,6 +525,7 @@ def __init__(self, instance=None):
self.object_id_field_name = rel.field.object_id_field_name
self.prefetch_cache_name = rel.field.attname
self.pk_val = instance.pk
self.for_concrete_model = rel.field.for_concrete_model

self.core_filters = {
'%s__pk' % self.content_type_field_name: content_type.id,
Expand Down Expand Up @@ -558,25 +561,34 @@ def get_queryset(self):
queryset = super().get_queryset()
return self._apply_rel_filters(queryset)

def get_content_type(self, obj):
return ContentType.objects.db_manager(obj._state.db).get_for_model(
obj, for_concrete_model=self.for_concrete_model)

def get_prefetch_queryset(self, instances, queryset=None):
if queryset is None:
queryset = super().get_queryset()

queryset._add_hints(instance=instances[0])
queryset = queryset.using(queryset._db or self._db)

query = {
'%s__pk' % self.content_type_field_name: self.content_type.id,
'%s__in' % self.object_id_field_name: {obj.pk for obj in instances}
}
or_queries = [models.Q(**{
'%s__pk' % self.content_type_field_name: self.get_content_type(instance).pk,
'%s' % self.object_id_field_name: instance.pk
}) for instance in instances]
queryset = queryset.filter(reduce(operator.or_, or_queries))

# We (possibly) need to convert object IDs to the type of the
# instances' PK in order to match up instances:
object_id_converter = instances[0]._meta.pk.to_python

return (
queryset.filter(**query),
lambda relobj: object_id_converter(getattr(relobj, self.object_id_field_name)),
lambda obj: obj.pk,
queryset,
lambda relobj: (
object_id_converter(getattr(relobj, self.object_id_field_name)),
relobj.content_type_id
),
lambda obj: (obj.pk, self.get_content_type(obj).pk),
False,
self.prefetch_cache_name,
False,
Expand Down
11 changes: 11 additions & 0 deletions tests/generic_relations/tests.py
Expand Up @@ -546,6 +546,17 @@ def test_add_then_remove_after_prefetch(self):
platypus.tags.remove(weird_tag)
self.assertSequenceEqual(platypus.tags.all(), [furry_tag])

def test_prefetch_related_generic_relation(self):
bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
bear = Animal.objects.create(common_name="bear")
tag1 = TaggedItem.objects.create(content_object=bacon, tag="bacon")
tag2 = TaggedItem.objects.create(content_object=bear, tag="bear")
prefetched_queryset = TaggedItem.objects.prefetch_related("content_object", "content_object__tags")
with self.assertNumQueries(4):
prefetched_items = list(prefetched_queryset)
self.assertEqual(list(prefetched_items[0].content_object.tags.all()), [tag1])
self.assertEqual(list(prefetched_items[1].content_object.tags.all()), [tag2])


class ProxyRelatedModelTest(TestCase):
def test_default_behavior(self):
Expand Down

0 comments on commit 37980d9

Please sign in to comment.