Как объединить 2 или более запросов в представлении Django?

Я пытаюсь построить поиск для сайта Django, который я строю, и в поиске я ищу в 3 разных моделях. И чтобы получить разбиение на страницы в списке результатов поиска, я хотел бы использовать общее представление object_list для отображения результатов. Но для этого мне нужно объединить 3 запроса в один.

как я могу это сделать? Я пробовал это:

result_list = []            
page_list = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
article_list = Article.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term) | 
    Q(tags__icontains=cleaned_search_term))
post_list = Post.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term) | 
    Q(tags__icontains=cleaned_search_term))

for x in page_list:
    result_list.append(x)
for x in article_list:
    result_list.append(x)
for x in post_list:
    result_list.append(x)

return object_list(
    request, 
    queryset=result_list, 
    template_object_name='result',
    paginate_by=10, 
    extra_context={
        'search_term': search_term},
    template_name="search/result_list.html")

но это не работает, я получаю ошибку, когда пытаюсь использовать этот список в общем представлении. В списке отсутствует атрибут клонирования.

кто-нибудь знает, как я могу объединить три списка, page_list, article_list и post_list?

11 ответов


объединение запросов в список является самым простым подходом. Если база данных будет поражена для всех запросов в любом случае (например, потому что результат должен быть отсортирован), это не добавит дополнительных затрат.

from itertools import chain
result_list = list(chain(page_list, article_list, post_list))

используя itertools.chain быстрее, чем цикл каждого списка и добавление элементов один за другим, так как itertools реализован в C. Он также потребляет меньше памяти, чем преобразование каждого набора запросов в список перед объединением.

теперь можно сортировать итоговый список, например, по дате (как указано в комментарии hasen j к другому ответу). The


попробуйте это:

matches = pages | articles | posts

сохраняет все функции запросов, что приятно, если вы хотите order_by или подобное.

Oops, обратите внимание, что это не работает на запросах из двух разных моделей...


можно использовать QuerySetChain классом ниже. При использовании его с пагинатором Django, он должен попасть только в базу данных с COUNT(*) запросы для всех запросов и SELECT() запросы только для тех запросов, записи которых отображаются на текущей странице.

обратите внимание, что нужно указать template_name= при использовании QuerySetChain с общими представлениями, даже если все связанные запросы используют одну и ту же модель.

from itertools import islice, chain

class QuerySetChain(object):
    """
    Chains multiple subquerysets (possibly of different models) and behaves as
    one queryset.  Supports minimal methods needed for use with
    django.core.paginator.
    """

    def __init__(self, *subquerysets):
        self.querysets = subquerysets

    def count(self):
        """
        Performs a .count() for all subquerysets and returns the number of
        records as an integer.
        """
        return sum(qs.count() for qs in self.querysets)

    def _clone(self):
        "Returns a clone of this queryset chain"
        return self.__class__(*self.querysets)

    def _all(self):
        "Iterates records in all subquerysets"
        return chain(*self.querysets)

    def __getitem__(self, ndx):
        """
        Retrieves an item or slice from the chained set of results from all
        subquerysets.
        """
        if type(ndx) is slice:
            return list(islice(self._all(), ndx.start, ndx.stop, ndx.step or 1))
        else:
            return islice(self._all(), ndx, ndx+1).next()

в вашем примере использование быть:

pages = Page.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(body__icontains=cleaned_search_term))
articles = Article.objects.filter(Q(title__icontains=cleaned_search_term) |
                                  Q(body__icontains=cleaned_search_term) |
                                  Q(tags__icontains=cleaned_search_term))
posts = Post.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(body__icontains=cleaned_search_term) | 
                            Q(tags__icontains=cleaned_search_term))
matches = QuerySetChain(pages, articles, posts)

затем использовать matches С пагинатором, как вы использовали result_list в вашем примере.

на itertools модуль был представлен в Python 2.3, поэтому он должен быть доступен во всех версиях Python, на которых работает Django.


связанный, для смешивания запросов из той же модели или для аналогичных полей из нескольких моделей, начиная с Джанго 1.11 a qs.union() метод также в наличии:

union()

union(*other_qs, all=False)

новое в Django 1.11. Использует оператор UNION SQL для объединения результатов двух или более запросов. Например:

>>> qs1.union(qs2, qs3)

оператор UNION по умолчанию выбирает только отдельные значения. Чтобы разрешить дублирование значений, используйте all=True аргумент.

union (), intersection () и difference() возвращают экземпляры модели тип первого QuerySet, даже если аргументы являются QuerySets остальные модели. Передача различных моделей работает до тех пор, пока SELECT список одинаков во всех запросах (по крайней мере, типы, имена не вопрос пока в том же порядке).

кроме того, только ограничение, смещение и порядок (т. е. нарезка и order_by()) в результате объект QuerySet. Далее,базы данных ограничения на то, какие операции разрешены в сочетании запросы. например, большинство баз данных не допускают ограничения или смещения в объединенные запросы.

https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union


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

чтобы только вытащить объекты, которые вам действительно нужны из базы данных, вы должны использовать разбиение на страницы в QuerySet, а не в списке. Если вы это сделаете, Django фактически срезает QuerySet перед выполнением запроса, поэтому SQL-запрос будет использовать Смещение и ограничение только для получения записей, которые вы фактически отобразите. Но вы не можете этого сделать, если вы не можете каким-то образом втиснуть свой поиск в один запрос.

учитывая, что все три ваши модели имеют поля заголовка и тела, почему бы не использовать модель наследования? Просто все три модели наследуются от общего предка, который имеет название и тело, и выполняют поиск как один запрос на модели предка.


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

from itertools import chain
result = list(chain(*docs))

where: docs-это список запросов


DATE_FIELD_MAPPING = {
    Model1: 'date',
    Model2: 'pubdate',
}

def my_key_func(obj):
    return getattr(obj, DATE_FIELD_MAPPING[type(obj)])

And then sorted(chain(Model1.objects.all(), Model2.objects.all()), key=my_key_func)

цитата из https://groups.google.com/forum#!тема/django-пользователи / 6wUNuJa4jVw. См.Алекс Гейнор


похоже, что t_rybik создал комплексное решение наhttp://www.djangosnippets.org/snippets/1933/


для поиска лучше использовать специальные решения, такие как Сена - это очень гибкий.


вот идея... просто вытащите одну полную страницу результатов из каждого из трех, а затем выбросьте 20 наименее полезных... это устраняет большие запросы, и таким образом вы жертвуете только небольшой производительностью вместо lot


требования: Django==2.0.2, django-querysetsequence==0.8

в случае, если вы хотите совместить querysets и все равно выйдет с QuerySet, вы, возможно, захотите, чтобы проверить Django-queryset-последовательность.

но одна заметка об этом. Это займет всего два querysets как аргументом. Но с python reduce вы всегда можете применить его к нескольким querysets.

from functools import reduce
from queryset_sequence import QuerySetSequence

combined_queryset = reduce(QuerySetSequence, list_of_queryset)

и это все. Ниже приведена ситуация, с которой я столкнулся и как я использовал list comprehension, reduce и django-queryset-sequence

from functools import reduce
from django.shortcuts import render    
from queryset_sequence import QuerySetSequence

class People(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    mentor = models.ForeignKey('self', null=True, on_delete=models.SET_NULL, related_name='my_mentees')

class Book(models.Model):
    name = models.CharField(max_length=20)
    owner = models.ForeignKey(Student, on_delete=models.CASCADE)

# as a mentor, I want to see all the books owned by all my mentees in one view.
def mentee_books(request):
    template = "my_mentee_books.html"
    mentor = People.objects.get(user=request.user)
    my_mentees = mentor.my_mentees.all() # returns QuerySet of all my mentees
    mentee_books = reduce(QuerySetSequence, [each.book_set.all() for each in my_mentees])

    return render(request, template, {'mentee_books' : mentee_books})