我正在尝试构建我正在构建的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")
但这不起作用当我尝试在通用视图中使用该列表时,我收到错误.该列表缺少clone属性.
有人知道如何合并三个列表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或类似的话,这是很好的.
糟糕,请注意,这不适用于来自两个不同模型的查询集...
相关,对于混合来自相同模型的查询集,或来自几个模型的类似字段,从Django 1.11开始,还提供了一种qs.union()
方法:
union()
union(*other_qs, all=False)Django 1.11中的新功能.使用SQL的UNION运算符组合两个或多个QuerySet的结果.例如:
>>> qs1.union(qs2, qs3)UNION运算符默认情况下仅选择不同的值.要允许重复值,请使用all = True参数.
union(),intersection()和difference()返回第一个QuerySet类型的模型实例,即使参数是其他模型的QuerySets.只要所有QuerySet中的SELECT列表相同,传递不同的模型就会起作用(至少类型,只要类型相同,名称无关紧要).
此外,在结果QuerySet上只允许LIMIT,OFFSET和ORDER BY(即切片和order_by()).此外,数据库限制组合查询中允许的操作.例如,大多数数据库在组合查询中不允许LIMIT或OFFSET.
https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.union
您可以使用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 .
该itertools
模块是在Python 2.3中引入的,因此它应该可以在Django运行的所有Python版本中使用.
当前方法的一大缺点是它具有大量搜索结果集的低效率,因为每次必须从数据库中下拉整个结果集,即使您只打算显示一页结果.
为了仅从数据库中下拉实际需要的对象,您必须在QuerySet上使用分页,而不是列表.如果这样做,Django实际上会在执行查询之前对QuerySet进行切片,因此SQL查询将使用OFFSET和LIMIT来仅获取您将实际显示的记录.但你不能这样做,除非你能以某种方式将你的搜索塞进一个查询中.
鉴于您的所有三个模型都有标题和正文字段,为什么不使用模型继承?让所有三个模型继承自具有标题和正文的共同祖先,并在祖先模型上作为单个查询执行搜索.
如果您想链接大量查询集,请尝试以下操作:
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
要求:
Django==2.0.2
,django-querysetsequence==0.8
如果你想要结合querysets
并仍然出来QuerySet
,你可能想看看django-queryset-sequence.
但有一点关于它.它只需要两个querysets
作为参数.但是使用python,reduce
你总是可以将它应用于多个queryset
s.
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})
这可以通过两种方式来实现。
第一种方法
对查询集使用联合运算符可|
对两个查询集进行联合。如果两个查询集都属于同一模型/单个模型,则可以使用联合运算符组合查询集。
对于一个实例
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))
这是一个想法...只需从三个中的每一个中拉下一整页结果,然后抛出20个最不实用的结果......这样就消除了大型查询集,这样你只会牺牲一点性能而不是很多