[Fixed]-Django: prefetch related objects of a GenericForeignKey

10👍

You can manually implement something like prefetch_selected and use Django’s select_related method, that will make join in database query.

apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes = Box.objects.all()
content_objects = {}
# apples
content_objects[apple_ctype.id] = Apple.objects.select_related(
    'farm').in_bulk(
        [b.object_id for b in boxes if b.content_type == apple_ctype]
    )
# chocolates
content_objects[chocolate_ctype.id] = Chocolate.objects.select_related(
    'factory').in_bulk(
        [b.object_id for b in boxes if b.content_type == chocolate_ctype]
    )

This should make only 3 queries (get_for_model queries are omitted). The in_bulk method returns a dict in the format {id: model}. So to get your content_object you need a code like:

content_obj = content_objects[box.content_type_id][box.object_id]

However I’m not sure if this code will be quicker than your O(5) solution as it requires additional iteration over boxes queryset and it also generates a query with a WHERE id IN (...) statement.

But if you sort boxes only by fields from Box model you can fill the content_objects dict after pagination. But you need to pass content_objects to __unicode__ somehow.

How would you refactor this DB schema to make such queries easier?

We have a similar structure. We store content_object in Box, but instead of object_id and content_object we use ForeignKey(Box) in Apple and Chocolate. In Box we have a get_object method to return the Apple or Chocolate model. In this case we can use select_related, but in most of our use-cases we filter Boxes by content_type. So we have the same problems like your 5th option. But we started our project on Django 1.2 when there was no prefetch_selected.

If you rename farm/factory to some common name, like creator, will prefetch_related work?

About your option 6

I can’t say anything against filling _content_object_cache.
If you don’t like to deal with internals you can fill a custom property and then use

apple = getattr(self, 'my_custop_prop', None)
if apple is None:
    apple = self.content_object
👤Igor

Leave a comment