Django ORM: Выбор связанного набора - PullRequest
17 голосов
/ 12 мая 2009

Скажите, у меня есть 2 модели:

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

class Choice(models.Model):
    poll = models.ForeignKey(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? Или, по крайней мере, фрагмент, делающий то же самое в лучшем виде.

Как вы обычно решаете эту проблему?

Ответы [ 4 ]

18 голосов
/ 07 июля 2012

Время прошло, и эта функциональность теперь доступна в 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
15 голосов
/ 12 мая 2009

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

polls = Poll.objects.filter(category='foo')
choices = Choice.objects.filter(poll__in=polls)
12 голосов
/ 12 мая 2009

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

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

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

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

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

1 голос
/ 12 мая 2009

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

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

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


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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...