select_related с обратными внешними ключами

У меня есть две модели в Django. Первый имеет иерархию того, какие рабочие функции (должности) подчиняются другим должностям, а второй - это люди и какие рабочие функции они выполняют.

class PositionHierarchy(model.Model):
    pcn = models.CharField(max_length=50)
    title = models.CharField(max_length=100)
    level = models.CharField(max_length=25)
    report_to = models.ForeignKey('PositionHierachy', null=True)


class Person(model.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    ...
    position = models.ForeignKey(PositionHierarchy)

Когда у меня есть запись человека, и я хочу найти менеджера человека, я должен сделать

manager = person.position.report_to.person_set.all()[0]
# Can't use .first() because we haven't upgraded to 1.6 yet

Если я получаю людей с QuerySet, я могу присоединиться (и избежать повторного обращения к базе данных) с position и report_to, используя Person.objects.select_related('position', 'position__reports_to').filter(...), но есть ли способ избежать повторного обращения к базе данных для получения person_set? Я попытался добавить 'position__reports_to__person_set' или просто position__reports_to__person к select_related, но это, похоже, не изменило запрос. Это то, для чего предназначено prefetch_related?

Я хотел бы создать собственный менеджер, чтобы при выполнении запроса на получение записей Person я также получал их PositionHeirarchy и запись Person их менеджера без дополнительных обращений к базе данных. Это то, что у меня есть до сих пор:

class PersonWithManagerManager(models.Manager):
    def get_query_set(self):
        qs = super(PersonWithManagerManager, self).get_query_set()
        return qs.select_related(
            'position',
            'position__reports_to',
        ).prefetch_related(
        )

person Paul Tomblin    schedule 16.04.2014    source источник
comment
возможно опечатка, но должно быть get_queryset(), а не get_query_set.   -  person Paolo    schedule 09.02.2018
comment
@Paolo, это было get_query_set в Django 1.5.   -  person Paul Tomblin    schedule 09.02.2018


Ответы (1)


Да, именно для этого предназначен prefetch_related(). Это потребует дополнительного запроса, но идея состоит в том, что он получит всю связанную информацию сразу, а не один раз за Person.

В твоем случае:

qs.select_related('position__report_to')
  .prefetch_related('position__report_to__person_set')

должно потребоваться два запроса, независимо от количества Persons в исходном наборе запросов.

Сравните этот пример с документацией:

>>> Restaurant.objects.select_related('best_pizza')
                      .prefetch_related('best_pizza__toppings')
person Kevin Christopher Henry    schedule 16.04.2014