Is django prefetch_related supposed to work with GenericRelation
If you want to retrieve Book
instances and prefetch the related tags use Book.objects.prefetch_related('tags')
. No need to use the reverse relation here.
You can also have a look at the related tests in the Django source code.
Also the Django documentation states that prefetch_related()
is supposed to work with GenericForeignKey
and GenericRelation
:
prefetch_related
, on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related, in addition to the foreign key and one-to-one relationships that are supported by select_related. It also supports prefetching ofGenericRelation
andGenericForeignKey
.
UPDATE: To prefetch the content_object
for a TaggedItem
you can use TaggedItem.objects.all().prefetch_related('content_object')
, if you want to limit the result to only tagged Book
objects you could additionally filter for the ContentType
(not sure if prefetch_related
works with the related_query_name
). If you also want to get the Author
together with the book you need to use select_related()
not prefetch_related()
as this is a ForeignKey
relationship, you can combine this in a custom prefetch_related()
query:
from django.contrib.contenttypes.models import ContentTypefrom django.db.models import Prefetchbook_ct = ContentType.objects.get_for_model(Book)TaggedItem.objects.filter(content_type=book_ct).prefetch_related( Prefetch( 'content_object', queryset=Book.objects.all().select_related('author') ))
prefetch_related_objects
to the rescue.
Starting from Django 1.10 (Note: it still presents in the previous versions, but was not part of the public API.), we can use prefetch_related_objects to divide and conquer our problem.
prefetch_related
is an operation, where Django fetches related data after the queryset has been evaluated (doing a second query after the main one has been evaluated). And in order to work, it expects the items in the queryset to be homogeneous (the same type). The main reason the reverse generic generation does not work right now is that we have objects from different content types, and the code is not yet smart enough to separate the flow for different content types.
Now using prefetch_related_objects
we do fetches only on a subset of our queryset where all the items will be homogeneous. Here is an example:
from django.db import modelsfrom django.db.models.query import prefetch_related_objectsfrom django.core.paginator import Paginatorfrom django.contrib.contenttypes.models import ContentTypefrom tags.models import TaggedItem, Book, Movietagged_items = TaggedItem.objects.all()paginator = Paginator(tagged_items, 25)page = paginator.get_page(1)# prefetch books with their author# do this only for items where# tagged_item.content_object is a Bookbook_ct = ContentType.objects.get_for_model(Book)tags_with_books = [item for item in page.object_list if item.content_type_id == book_ct.id]prefetch_related_objects(tags_with_books, "content_object__author")# prefetch movies with their director# do this only for items where# tagged_item.content_object is a Moviemovie_ct = ContentType.objects.get_for_model(Movie)tags_with_movies = [item for item in page.object_list if item.content_type_id == movie_ct.id]prefetch_related_objects(tags_with_movies, "content_object__director")# This will make 5 queries in total# 1 for page items# 1 for books# 1 for book authors# 1 for movies# 1 for movie directors# Iterating over items wont make other queriesfor item in page.object_list: # do something with item.content_object # and item.content_object.author/director print( item, item.content_object, getattr(item.content_object, 'author', None), getattr(item.content_object, 'director', None) )