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
?
You can use the
QuerySetChain
class below. When using it with Django's paginator, it should only hit the database withCOUNT(*)
queries for all querysets andSELECT()
queries only for those querysets whose records are displayed on the current page.Note that you need to specify
template_name=
if using aQuerySetChain
with generic views, even if the chained querysets all use the same model.In your example, the usage would be:
Then use
matches
with the paginator like you usedresult_list
in your example.The
itertools
module was introduced in Python 2.3, so it should be available in all Python versions Django runs on.Looks like t_rybik has created a comprehensive solution at http://www.djangosnippets.org/snippets/1933/
here's an idea... just pull down one full page of results from each of the three and then throw out the 20 least useful ones... this eliminates the large querysets and that way you only sacrifice a little performance instead of a lot
Concatenating the querysets into a list is the simplest approach. If the database will be hit for all querysets anyway (e.g. because the result needs to be sorted), this won't add further cost.
Using
itertools.chain
is faster than looping each list and appending elements one by one, sinceitertools
is implemented in C. It also consumes less memory than converting each queryset into a list before concatenating.Now it's possible to sort the resulting list e.g. by date (as requested in hasen j's comment to another answer). The
sorted()
function conveniently accepts a generator and returns a list:If you're using Python 2.4 or later, you can use
attrgetter
instead of a lambda. I remember reading about it being faster, but I didn't see a noticeable speed difference for a million item list.Quoted from https://groups.google.com/forum/#!topic/django-users/6wUNuJa4jVw. See Alex Gaynor