Как объединить 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. Он также потребляет меньше памяти, чем преобразование каждого набора запросов в список перед конкатенацией.теперь можно сортировать результирующий список, например, по дате (как указано в комментарии Хасена 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как аргументом. Но с pythonreduceвы всегда можете применить его к несколькимquerysets.from functools import reduce from queryset_sequence import QuerySetSequence combined_queryset = reduce(QuerySetSequence, list_of_queryset)и это все. Ниже приведена ситуация, с которой я столкнулся и как я использовал
list comprehension,reduceиdjango-queryset-sequencefrom 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})
Comments