Django - 通过布尔运算对模型进行排序的最佳方法



假设我有这个模型,我想按逻辑运算n1 != n2对它们进行排序:

class Thing(Model):
n1 = IntegerField()
n2 = IntegerField()
...
def is_different(self):
return self.n1 != self.n2

如果我按内置函数对它们进行排序sorted我发现它不返回查询集,而是返回一个列表:

things = Thing.objects.all()    
sorted_things = sorted(things, key=lambda x: x.is_different())

现在,如果我使用annotate

sorted_things = things.annotate(diff=(F('n1') != F('n2'))).order_by('diff')

它会引发以下错误:AttributeError: 'bool' object has no attribute 'resolve_expression'

我使用extra查询集找到了解决方案:

sorted_things = things.extra(select={'diff': 'n1!=n2'}).order_by('diff')

但遵循 Django 文档 (https://docs.djangoproject.com/en/2.0/ref/models/querysets/#extra):

使用此方法作为最后的手段

这是一个旧的 API,我们的目标是在将来的某个时候弃用它。仅当无法使用其他查询集方法表示查询时,才使用它。如果您确实需要使用它,请使用 QuerySet.extra 关键字在您的用例中提交工单(请先查看现有工单列表),以便我们可以增强 QuerySet API 以允许删除 extra()。我们不再改进或修复此方法的错误。

那么,最好的方法是什么?

谢谢!

条件表达式

它的一个选项是使用条件表达式。它们提供了检查条件并根据条件提供值之一的简单方法。在您的情况下,它将看起来像:

sorted_things = things.annotate(diff=Case(When(n1=F('n2'), then=True), default=False, output_field=BooleanField())).order_by('diff')

Q 和表达式包装器

还有另一种有点黑客的方法,可以通过结合使用QExpressionWrapper来实现这一点。

在 django 中,Q旨在用于filter()exclude()Case等内部,但它只是创造了显然可以在任何地方使用的条件。它只有一个缺点:它没有定义输出的类型(它总是布尔值,django 可以假设在每种情况下Q打算使用时。

但是有ExpressionWrapper允许您包装任何表达式并定义其最终输出类型。这样我们就可以简单地包装Q表达式(或使用&|和括号粘合在一起的多个Q表达式),并手动定义它输出的类型。

请注意,这是未记录的,因此此行为将来可能会更改,但是我已经使用django版本1.8,1.11和2.0进行了检查,并且工作正常

例:

sorted_things = things.annotate(diff=ExpressionWrapper(Q(n1=F('n2')), output_field=BooleanField())).order_by('diff')

您可以使用 Func() 表达式来解决它。

from django.db.models import Func, F
class NotEqual(Func):
arg_joiner = '<>'
arity = 2
function = ''
things = Thing.objects.annotate(diff=NotEqual(F('n1'), F('n2'))).order_by('diff')

最新更新