prefetch_related for multiple Levels

Alex Rothberg picture Alex Rothberg · Nov 25, 2014 · Viewed 19.5k times · Source

If my Models look like:

class Publisher(models.Model):
    pass

class Book(models.Model):
    publisher = models.ForeignKey(Publisher)

class Page(models.Model):
    book = models.ForeignKey(Book)

and I would like to get the queryset for Publisher I do Publisher.object.all(). If then want to make sure to prefetch I can do:

Publisher.objects.all().prefetch_related('book_set')`

My questions are:

  1. Is there a way to do this prefetching using select_related or must I use prefetch_related?
  2. Is there a way to prefetch the page_set? This does not work:

Publisher.objects.all().prefetch_related('book_set', 'book_set_page_set')

Answer

Dmitriy Sintsov picture Dmitriy Sintsov · Jun 30, 2017

Since Django 1.7, instances of django.db.models.Prefetch class can be used as an argument of .prefetch_related. Prefetch object constructor has a queryset argument that allows to specify nested multiple levels prefetches like that:

Project.objects.filter(
        is_main_section=True
    ).select_related(
        'project_group'
    ).prefetch_related(
        Prefetch(
            'project_group__project_set',
            queryset=Project.objects.prefetch_related(
                Prefetch(
                    'projectmember_set',
                    to_attr='projectmember_list'
                )
            ),
            to_attr='project_list'
        )
    )

It is stored into attributes with _list suffix because I use ListQuerySet to process prefetch results (filter / order).