我正在尝试构建我正在构建的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

有帮助吗?

解决方案

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

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开始 a qs.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

您可以使用下面的 QuerySetChain 类。当它与Django的paginator一起使用时,它应该仅针对所有查询集的 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)

然后将匹配与paginator一起使用,就像你在示例中使用 result_list 一样。

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

这是一个想法......只需从三个中的每一个中拉下一整页结果,然后抛弃20个最不实用的结果......这样就消除了大型查询集,这样你只会牺牲一点性能而不是很多

<强>要求: 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})

这可以通过两种方式实现。

第一种方法

对queryset | 使用union运算符来获取两个queryset的并集。如果两个查询集都属于同一个模型/单个模型,则可以使用union运算符组合查询集。

对于实例

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))

此递归函数将查询集数组连接到一个查询集中。

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
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top