假设我有这个模型,我想按逻辑运算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 和表达式包装器
还有另一种有点黑客的方法,可以通过结合使用Q
和ExpressionWrapper
来实现这一点。
在 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')