我正在尝试构建我正在构建的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
?
答案 0 :(得分:973)
将查询集连接到列表中是最简单的方法。如果无论如何都会为所有查询集命中数据库(例如,因为结果需要进行排序),这不会增加进一步的成本。
from itertools import chain
result_list = list(chain(page_list, article_list, post_list))
使用itertools.chain
比循环每个列表并逐个添加元素更快,因为{C}实现了itertools
。它还比在连接之前将每个查询集转换为列表消耗更少的内存。
现在可以对结果列表进行排序,例如按日期(按照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'))
答案 1 :(得分:414)
试试这个:
matches = pages | articles | posts
保留查询集的所有功能,如果你想要order_by或类似的话,那就很好。
糟糕,请注意,这不适用于来自两个不同模型的查询集......
答案 2 :(得分:91)
相关,对于混合来自同一模型的查询集,或来自少数模型的类似字段,从 Django 1.11 开始,qs.union()
method也可用:
<强>
union()
强>union(*other_qs, all=False)
Django 1.11中的新功能。使用SQL的UNION运算符组合两个或多个QuerySet的结果。例如:
>>> qs1.union(qs2, qs3)
UNION运算符默认情况下仅选择不同的值。要允许重复值,请使用all = True 参数。
union(),intersection()和difference()返回模型实例 即使参数是QuerySets,第一个QuerySet的类型 其他型号。只要SELECT,传递不同的模型就可以工作 list在所有QuerySet中都是相同的(至少是类型,名称不是 只要相同顺序的类型)。
此外,只有LIMIT,OFFSET和ORDER BY(即切片和 在生成的QuerySet上允许order_by())。此外,数据库 限制合并中允许的操作 查询。例如,大多数数据库不允许使用LIMIT或OFFSET 合并的查询。
https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union
答案 3 :(得分:73)
您可以使用下面的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
的分页符一样。
{2.3}中引入了itertools
模块,因此它应该可以在Django运行的所有Python版本中使用。
答案 4 :(得分:26)
当前方法的一个重大缺点是它具有较大的搜索结果集效率低,因为每次必须从数据库中下拉整个结果集,即使您只打算显示一页结果。
为了仅从数据库中下拉实际需要的对象,您必须在QuerySet上使用分页,而不是列表。如果这样做,Django实际上会在执行查询之前对QuerySet进行切片,因此SQL查询将使用OFFSET和LIMIT来仅获取您将实际显示的记录。但是你不能这样做,除非你能以某种方式将你的搜索塞进一个查询。
鉴于您的所有三个模型都有标题和正文字段,为什么不使用model inheritance?只需让所有三个模型都从具有标题和正文的共同祖先继承,并在祖先模型上作为单个查询执行搜索。
答案 5 :(得分:20)
如果您想链接大量查询集,请尝试以下方法:
from itertools import chain
result = list(chain(*docs))
其中:docs是查询集列表
答案 6 :(得分:15)
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
答案 7 :(得分:5)
这是一个想法......只需从三个中的每一个中拉下一整页结果,然后抛出20个最不实用的结果......这样就消除了大型查询集,这样你只会牺牲一点性能而不是很多
答案 8 :(得分:5)
<强>要求:强>
Django==2.0.2
,django-querysetsequence==0.8
如果您想要合并querysets
并仍然使用QuerySet
,则可能需要查看django-queryset-sequence。
但有一点关于它。它只需要两个querysets
作为参数。但是使用python reduce
,您始终可以将其应用于多个queryset
。
from functools import reduce
from queryset_sequence import QuerySetSequence
combined_queryset = reduce(QuerySetSequence, list_of_queryset)
就是这样。以下是我遇到的情况以及我如何使用list comprehension
,reduce
和django-queryset-sequence
from functools import reduce
from django.shortcuts import render
from queryset_sequence import QuerySetSequence
class People(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
mentor = models.ForeignKey('self', null=True, on_delete=models.SET_NULL, related_name='my_mentees')
class Book(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(Student, on_delete=models.CASCADE)
# as a mentor, I want to see all the books owned by all my mentees in one view.
def mentee_books(request):
template = "my_mentee_books.html"
mentor = People.objects.get(user=request.user)
my_mentees = mentor.my_mentees.all() # returns QuerySet of all my mentees
mentee_books = reduce(QuerySetSequence, [each.book_set.all() for each in my_mentees])
return render(request, template, {'mentee_books' : mentee_books})
答案 9 :(得分:4)
这可以通过两种方法来实现。
第一种方法
对查询集|
使用联合运算符以对两个查询集进行联合。如果两个查询集都属于同一模型/单个模型,则可以使用联合运算符组合查询集。
用于实例
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))
答案 10 :(得分:3)
这将完成工作而无需使用任何其他库
result_list = list(page_list) + list(article_list) + list(post_list)
答案 11 :(得分:2)
最好的选择是使用 Django 内置方法:
# Union method
result_list = page_list.union(article_list, post_list)
这将返回这些查询集中所有对象的联合。
如果您只想获取三个查询集中的对象,您会喜欢查询集的内置方法 intersection
。
# intersection method
result_list = page_list.intersection(article_list, post_list)
答案 12 :(得分:1)
此递归函数将一组查询集串联为一个查询集。
def merge_query(ar):
if len(ar) ==0:
return [ar]
while len(ar)>1:
tmp=ar[0] | ar[1]
ar[0]=tmp
ar.pop(1)
return ar
答案 13 :(得分:1)