如何在Django视图中组合2个或更多的查询集?
我正在尝试构建搜索我正在构建的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
?
将查询集连接到列表是最简单的方法。 如果数据库无论如何都会针对所有查询集(例如,因为结果需要排序),这不会增加额外的成本。
from itertools import chain
result_list = list(chain(page_list, article_list, post_list))
使用itertools.chain
比循环每个列表和逐个追加元素要快,因为itertools
是用C语言实现的。它比在连接之前将每个查询集转换为列表消耗的内存少。
现在可以按照日期对结果列表进行排序(如hasen j的评论中对其他答案的要求)。 sorted()
函数方便地接受一个生成器并返回一个列表:
result_list = sorted(
chain(page_list, article_list, post_list),
key=lambda instance: instance.date_created)
如果您使用Python 2.4或更高版本,则可以使用attrgetter
而不是lambda。 我记得阅读速度更快,但是我没有看到一百万条产品列表上明显的速度差异。
from operator import attrgetter
result_list = sorted(
chain(page_list, article_list, post_list),
key=attrgetter('date_created'))
尝试这个:
matches = pages | articles | posts
保留查询集的所有功能,如果您想order_by或类似的话,这些功能都很好。
糟糕,请注意,这不适用于来自两种不同模型的查询集...
您可以使用下面的QuerySetChain
类。 当在Django的paginator中使用它时,它只应该用所有查询集的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
的paginator matches
。
itertools
模块是在Python 2.3中引入的,所以它应该在Django运行的所有Python版本中都可用。