Как объединить 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
вы всегда можете применить его к нескольким 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})