获取切片后过滤 Django 查询集

Filtering a Django queryset once a slice has been taken

在我维护的一个 django 论坛中,我将禁止辱骂的用户 4 天。在论坛的"Home page"里,我展示的是大家的评论,但是不包括被封禁的人的评论。它是这样的:

    def get_queryset(self):
        if self.request.user_banned: #if user is hell-banned
            return Link.objects.order_by('-id')[:120]
        else: #if user is not hell-banned
            global condemned
            queryset = Link.objects.order_by('-id').exclude(submitter_id__in=condemned)[:120]
            return queryset

以上是ListView的一个get_queryset方法。请注意被禁止访问的用户如何无法判断他们的评论已被网站排除(禁止访问点)。 condemned 是一个包含地狱封禁用户主键的列表。

现在我想通过先切片然后排除被禁止的人来优化上面的内容。我正在尝试通过以下方式做到这一点:

def get_queryset(self):
    if self.request.user_banned: #if user is hell-banned
        return Link.objects.order_by('-id')[:120]
    else: #if user is not hell-banned
        global condemned
        queryset = Link.objects.order_by('-id')[:120]
        queryset = queryset.exclude(submitter_id__in=condemned)
        return queryset

不幸的是,这给了我错误:

Cannot filter a query once a slice has been taken.

我有什么选择?需要我能找到的最有效的解决方案,因为性能是关键。我在 Django < 1.8。提前致谢。

首先,Django 不允许您在切片之后进行过滤,因为在底层 SQL 中,您无法轻易地 limit 结果然后使用 where 进行过滤。

进行过滤然后切片可能无论如何都不是问题。请注意 querysets are lazy,因此 Django 只会从数据库中获取 120 个对象。

您需要进行一些基准测试以确定排除是否真的让您变慢了。您可以测试使用 exclude 和切片的查询是否明显比使用切片的查询慢。

如果发现排除速度慢,可以在Python

中过滤
comments = [c for c in comments if c.submitter_id not in condemned]. 

请注意,您最终得到的评论可能少于 120 条。

另一种选择是向 Submitter 模型添加 condemned 标志,然后将查询更改为 .exclude(submitter__condemned=True)。这可能比当前的 .exclude(submitter_id__in=condemned).

更快

您还应该检查您的数据库是否有 submitter_id 字段的索引。因为它是外键,所以它可能是。