如何在Django视图中组合2个或更多的查询集?

我试图build立一个Django网站的search我正在build设,并在search我在3个不同的模型search。 为了在search结果列表上得到分页,我想使用通用的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") 

但是这不起作用当我尝试在通用视图中使用该列表时,出现错误。 该列表缺less克隆属性。

任何人都知道我可以如何合并三个列表, page_listarticle_listpost_list

将查询集连接到列表是最简单的方法。 如果数据库无论如何都会被所有的查询集所击中(例如,因为结果需要sorting),这不会增加成本。

 from itertools import chain result_list = list(chain(page_list, article_list, post_list)) 

使用itertools.chain比循环每个列表和逐个追加元素要快,因为itertools是在C中实现的。它比在将每个查询集转换成列表之前消耗更less的内存。

现在可以按date对结果列表进行sorting(如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 

保留查询集的所有function,如果你想order_by或者类似的话,这个function是很好的。

糟糕,请注意,这不适用于来自两个不同模型的查询集…

您可以使用下面的QuerySetChain类。 当在Django的paginator中使用它时,它只应该使用COUNT(*)查询来查询所有查询集,而SELECT()查询只能查询那些logging在当前页面上显示的查询集。

请注意,即使链接的查询集都使用相同的模型,也需要指定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版本中使用。

相关的,混合来自同一模型的查询集或来自几个模型的类似字段,从Django 1.11开始, qs.union()方法也是可用的:

union()

 union(*other_qs, all=False) 

Django 1.11新增function 使用SQL的UNION运算符组合两个或更多QuerySets的结果。 例如:

 >>> qs1.union(qs2, qs3) 

UNION运算符默认情况下只select不同的值。 要允许重复值,请使用all = True参数。

即使参数是其他模型的QuerySets,union(),intersection()和difference()也会返回第一个QuerySettypes的模型实例。 传递不同模型的工作只要SELECT列表在所有的QuerySets中都是一样的(至less是types,只要types相同,名称无关紧要)。

另外,在结果QuerySet上只允许LIMIT,OFFSET和ORDER BY(即slicing和order_by())。 此外, 数据库对组合查询中允许的操作进行限制。 例如,大多数数据库不允许组合查询中的LIMIT或OFFSET。

https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union

当前方法的一个很大的缺点是对于大型search结果集效率低下,因为每次都必须从数据库中提取整个结果集,即使您只打算显示一页结果。

为了只从数据库中提取实际需要的对象,必须在QuerySet上使用分页,而不是在列表上使用。 如果你这样做,Django实际上在查询执行之前对QuerySet进行分片,所以SQL查询将使用OFFSET和LIMIT来获取你实际要显示的logging。 但是你不能这样做,除非你可以以某种方式将你的search插入到一个查询中。

鉴于你所有的三个模型都有标题和正文字段,为什么不使用模型inheritance ? 只要所有三个模型都从具有标题和正文的共同祖先inheritance,并在祖先模型上作为单个查询执行search。

如果你想链很多的查询集,试试这个:

 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 。 见Alex Gaynor

为了search,最好使用像Haystack这样的专用解决scheme – 这是非常灵活的。

这里有一个想法…只是从三个结果中每一个拉下一整页的结果,然后扔掉20个最不有用的结果…这消除了大的查询集,这样,你只牺牲一点点的performance,而不是很多