Prefetch_related для нескольких уровней

Если мои модели выглядят так:

class Publisher(models.Model):
    pass

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

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

и я хотел бы получить запрос для Publisher я do Publisher.object.all(). Если вы хотите убедиться в предварительной выборке, я могу сделать:

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

Мои вопросы:

  • Есть ли способ сделать эту предварительную выборку с помощью select_related или я должен использовать prefetch_related?
  • Есть ли способ предварительной выборки page_set? Это не работает:

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

Ответ 1

  • Нет, вы не можете использовать select_related для обратного отношения. select_related выполняет объединение SQL, поэтому одной записи в главном запросе требуется указать только одно в связанной таблице (поля ForeignKey или OneToOne). prefetch_related фактически выполняет полностью отдельный второй запрос, кэширует результаты, затем "присоединяет" его к набору запросов в python. Поэтому это необходимо для полей ManyToMany или reverse ForeignKey.

  • Вы пробовали два символа подчеркивания, чтобы сделать предварительные выборки на нескольких уровнях? Вот так: Publisher.objects.all().prefetch_related('book_set', 'book_set__page_set')

Ответ 2

Так как Django 1.7, экземпляры класса django.db.models.Prefetch могут использоваться как аргумент .prefetch_related. Prefetch конструктор объекта имеет аргумент queryset, который позволяет задавать вложенные множественные предварительные выборки следующим образом:

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'
        )
    )

Он сохраняется в атрибутах с суффиксом _list, потому что я использую ListQuerySet для обработки результатов предварительной выборки (фильтр/порядок).