Django ORM: выбор соответствующего набора

Скажем, у меня две модели:

class Poll(models.Model):
    category = models.CharField(u"Category", max_length = 64)
    [...]

class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    [...]

Учитывая объект Poll, я могу запросить его выбор с помощью:

poll.choice_set.all()

Но есть ли функция утилиты для запроса всех вариантов из набора опросов?

На самом деле, я ищу что-то вроде следующего (что не поддерживается, и я не ищу, как это может быть):

polls = Poll.objects.filter(category = 'foo').select_related('choice_set')
for poll in polls:
    print poll.choice_set.all() # this shouldn't perform a SQL query at each iteration

Я сделал (уродливую) функцию, чтобы помочь мне в этом:

def qbind(objects, target_name, model, field_name):
    objects = list(objects)
    objects_dict = dict([(object.id, object) for object in objects])
    for foreign in model.objects.filter(**{field_name + '__in': objects_dict.keys()}):
        id = getattr(foreign, field_name + '_id')
        if id in objects_dict:
            object = objects_dict[id]
            if hasattr(object, target_name):
                getattr(object, target_name).append(foreign)
            else:
                setattr(object, target_name, [foreign])
    return objects

который используется следующим образом:

polls = Poll.objects.filter(category = 'foo')
polls = qbind(polls, 'choices', Choice, 'poll')
# Now, each object in polls have a 'choices' member with the list of choices.
# This was achieved with 2 SQL queries only.

Есть ли что-то более легкое, уже предоставленное Django? Или, по крайней мере, фрагмент делает то же самое в лучшем виде.

Как вы обычно справляетесь с этой проблемой?

Ответ 1

Обновление. Начиная с Django 1.4, эта функция встроена: см. prefetch_related.

Первый ответ: не тратьте время на то, чтобы написать что-то вроде qbind, пока вы уже не написали рабочее приложение, не профилировали его и не продемонстрировали, что N запросов - это проблема производительности для вашей базы данных и сценариев загрузки.

Но, возможно, вы это сделали. Итак, второй ответ: qbind() делает то, что вам нужно будет делать, но было бы более идиоматично, если бы он был упакован в пользовательский подкласс QuerySet с сопровождающим подклассом Manager, который возвращает экземпляры пользовательского QuerySet. В идеале вы могли бы сделать их универсальными и многоразовыми для любых обратных отношений. Тогда вы можете сделать что-то вроде:

Poll.objects.filter(category='foo').fetch_reverse_relations('choices_set')

Пример метода Manager/QuerySet см. в этот фрагмент, который решает подобную проблему, но для случая общих внешних ключей, а не обратные отношения. Было бы не слишком сложно объединить кишки вашей функции qbind() со структурой, показанной там, чтобы сделать действительно приятное решение вашей проблемы.

Ответ 2

Время прошло, и эта функциональность теперь доступна в Django 1.4 с введением функции prefetch_related() QuerySet. Эта функция эффективно выполняет то, что выполняется предлагаемой функцией qbind. то есть. Выполняются два запроса, и соединение происходит на земле Python, но теперь это обрабатывается ORM.

Теперь исходный запрос будет выглядеть следующим образом:

polls = Poll.objects.filter(category = 'foo').prefetch_related('choice_set')

Как показано в следующем примере кода, polls QuerySet можно использовать для получения всех Choice объектов на Poll без каких-либо дополнительных обращений к базе данных:

for poll in polls:
    for choice in poll.choice_set:
        print choice

Ответ 3

Я думаю, что вы говорите: "Я хочу, чтобы все варианты для набора опросов". Если да, попробуйте следующее:

polls = Poll.objects.filter(category='foo')
choices = Choice.objects.filter(poll__in=polls)

Ответ 4

Я думаю, что то, что вы пытаетесь сделать, это термин "нетерпеливая загрузка" дочерних данных - это означает, что вы загружаете дочерний список (choice_set) для каждого опроса, но все в первом запросе к БД, Впоследствии вам нужно будет собрать кучу запросов.

Если это правильно, то вы ищете "select_related" - см. https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related

Я заметил, что вы пытались "select_related" , но это не сработало. Можете ли вы попробовать сделать "select_related" , а затем фильтр. Это может исправить это.


ОБНОВЛЕНИЕ: Это не работает, см. комментарии ниже.