How to combine 2 or more querysets in a Django view?
我正在尝试建立对我正在建设的Django网站的搜索,在搜索中,我在3个不同的模型中进行搜索。为了在搜索结果列表上进行分页,我想使用一个通用的对象列表视图来显示结果。但要做到这一点,我必须将3个查询集合并为一个。
我该怎么做?我试过了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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") |
但这不起作用,当我试图在通用视图中使用该列表时,会得到一个错误。列表缺少克隆属性。
有人知道我如何合并这三个列表吗,
将查询集连接到列表中是最简单的方法。如果对所有查询集都命中数据库(例如,因为结果需要排序),这不会增加更多的成本。
1 2 | from itertools import chain result_list = list(chain(page_list, article_list, post_list)) |
由于
现在可以对结果列表进行排序,例如按日期(按照hasen j对另一个答案的评论中的要求)。
1 2 3 | result_list = sorted( chain(page_list, article_list, post_list), key=lambda instance: instance.date_created) |
如果您使用的是python 2.4或更高版本,那么可以使用
1 2 3 4 | from operator import attrgetter result_list = sorted( chain(page_list, article_list, post_list), key=attrgetter('date_created')) |
试试这个:
1 | matches = pages | articles | posts |
保留查询集的所有功能,如果您希望按或类似方式排序,这很好。
哎呀,请注意,这对来自两个不同模型的查询集不起作用…
相关,对于混合来自同一模型的查询集,或对于来自几个模型的类似字段,也可以从django 1.11开始使用
union()
1 union(*other_qs, all=False)New in Django 1.11. Uses SQL’s UNION operator to combine the results of two or more QuerySets. For example:
1 >>> qs1.union(qs2, qs3)The UNION operator selects only distinct values by default. To allow duplicate values, use the all=True
argument.union(), intersection(), and difference() return model instances of
the type of the first QuerySet even if the arguments are QuerySets of
other models. Passing different models works as long as the SELECT
list is the same in all QuerySets (at least the types, the names don’t
matter as long as the types in the same order).In addition, only LIMIT, OFFSET, and ORDER BY (i.e. slicing and
order_by()) are allowed on the resulting QuerySet. Further, databases
place restrictions on what operations are allowed in the combined
queries. For example, most databases don’t allow LIMIT or OFFSET in
the combined queries.
https://docs.djangoproject.com/en/1.11/ref/models/queryset/django.db.models.query.queryset.union
您可以使用下面的
注意,如果将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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() |
在您的示例中,用法是:
1 2 3 4 5 6 7 8 9 | 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) |
然后将
在python 2.3中引入了
当前方法的最大缺点是搜索结果集太大而效率低下,因为您每次都必须从数据库中下拉整个结果集,即使您只打算显示一页结果。
为了只从数据库中下拉实际需要的对象,必须对查询集而不是列表使用分页。如果这样做,Django实际上会在执行查询之前分割查询集,因此SQL查询将使用偏移量和限制来获取实际显示的记录。但是,除非您能以某种方式将搜索塞进一个查询中,否则无法执行此操作。
既然您的三个模型都有标题和正文字段,为什么不使用模型继承呢?只需让所有三个模型都继承自一个具有标题和主体的共同祖先,并将搜索作为对祖先模型的单个查询来执行。
如果要链接大量查询集,请尝试以下操作:
1 2 | from itertools import chain result = list(chain(*docs)) |
其中:docs是查询集列表
1 2 3 4 5 6 7 8 9 | 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/!主题/django用户/6wunuja4jvw。见Alex Gaynor
要求:江户十一〔13〕、江户十一〔14〕。
如果您想要合并
但有一点值得注意。它只需要两个
1 2 3 4 | from functools import reduce from queryset_sequence import QuerySetSequence combined_queryset = reduce(QuerySetSequence, list_of_queryset) |
就是这样。下面是我遇到的一种情况,以及我是如何雇用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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}) |
有个主意……只需将三个结果中的每一个都下拉一整页,然后扔掉20个最不有用的结果…这样就消除了大量的查询集,这样只会牺牲一点性能,而不会牺牲很多性能。
这可以通过两种方式实现。
第一种方法
使用union operator for queryset
举例来说
1 2 3 4 5 6 7 | 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链函数。
1 2 | from itertools import chain combined_results = list(chain(pagelist1, pagelist2)) |
此递归函数将查询集数组连接到一个查询集中。
1 2 3 4 5 6 7 8 | 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 |