I am trying to build the search for a Django site I am building, and in the search I am searching in 3 different models. And to get pagination on the search result list I would like to use a generic object_list view to display the results. But to do that i have to merge 3 querysets into one.
How can i do that? I've tried this:
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")
But this doesn't work I get an error when I try to use that list in the generic view. The list is missing the clone attribute.
Anybody know how I can merge the three lists, page_list
, article_list
and post_list
?
In case you want to chain a lot of querysets, try this:
where: docs is a list of querysets
The big downside of your current approach is its inefficiency with large search result sets, as you have to pull down the entire result set from the database each time, even though you only intend to display one page of results.
In order to only pull down the objects you actually need from the database, you have to use pagination on a QuerySet, not a list. If you do this, Django actually slices the QuerySet before the query is executed, so the SQL query will use OFFSET and LIMIT to only get the records you will actually display. But you can't do this unless you can cram your search into a single query somehow.
Given that all three of your models have title and body fields, why not use model inheritance? Just have all three models inherit from a common ancestor that has title and body, and perform the search as a single query on the ancestor model.
Related, for mixing querysets from the same model, or for similar fields from a few models, Starting with Django 1.11 a
qs.union()
method is also available:https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union
For searching it's better to use dedicated solutions like Haystack - it's very flexible.
Requirements:
Django==2.0.2
,django-querysetsequence==0.8
In case you want to combine
querysets
and still come out with aQuerySet
, you might want to check out django-queryset-sequence.But one note about it. It only takes two
querysets
as it's argument. But with pythonreduce
you can always apply it to multiplequeryset
s.And that's it. Below is a situation I ran into and how I employed
list comprehension
,reduce
anddjango-queryset-sequence
Try this:
Retains all the functions of the querysets which is nice if you want to order_by or similar.
Oops, please note that this doesn't work on querysets from two different models...