我试图建立一个我正在建立的Django网站的搜索,在这个搜索中,我用三种不同的模式进行搜索。为了在搜索结果列表上进行分页,我想使用一个通用的object_list视图来显示结果。但要做到这一点,我必须将三个查询集合并为一个。
我该怎么做?我已经试过了:
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'))
您可以使用下面的QuerySetChain类。当它与Django的分页器一起使用时,它应该只对所有查询集进行COUNT(*)查询,而只对记录显示在当前页面上的查询集进行SELECT()查询。
注意,如果使用带有泛型视图的QuerySetChain,则需要指定template_name=,即使链接的查询集都使用相同的模型。
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)
然后像您在示例中使用result_list一样,将匹配项与分页器一起使用。
itertools模块是在Python2.3中引入的,因此它应该可以在Django运行的所有Python版本中使用。
这也可以通过两种方式实现。
第一种方法
对queryset|使用union运算符可以合并两个queryset。如果两个查询集属于同一模型/单个模型,则可以使用联合运算符组合查询集。
对于一个实例
pagelist1 = Page.objects.filter(
Q(title__icontains=cleaned_search_term) |
Q(body__icontains=cleaned_search_term))
pagelist2 = Page.objects.filter(
Q(title__icontains=cleaned_search_term) |
Q(body__icontains=cleaned_search_term))
combined_list = pagelist1 | pagelist2 # this would take union of two querysets
第二种方法
实现两个查询集之间的组合操作的另一种方法是使用itertools链函数。
from itertools import chain
combined_results = list(chain(pagelist1, pagelist2))