Как объединить 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?

109   11  

11 ответов:

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

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

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

теперь можно сортировать результирующий список, например, по дате (как указано в комментарии Хасена j к другому ответу). Элемент

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

matches = pages | articles | posts

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

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

можно использовать 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 в вашем примере.

The 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 () возвращают экземпляры модели тип первого набора запросов, даже если аргументы являются наборами запросов остальные модели. Передача различных моделей работает до тех пор, как выбрать список одинаков во всех запросах (по крайней мере, типы, имена не имеют вопрос пока в том же порядке).

кроме того, только ограничение, смещение и порядок (т. е. нарезка и 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))

где: 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/#! topic/django-users / 6wUNuJa4jVw. See Алекс Гейнор

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

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

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

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

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

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

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})
    Ничего не найдено.

Добавить ответ:
Отменить.