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

时间:2018-03-14 08:56:43

标签: python django django-queryset

让我们想象一下我有这个模型,我想通过逻辑操作n1 != n2对它们进行排序:

class Thing(Model):
    n1 = IntegerField()
    n2 = IntegerField()
    ...

    def is_different(self):
        return self.n1 != self.n2

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

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 queryset的解决方案:

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()。我们不再改进或修复此方法的错误。

然后,最佳方法是什么?

谢谢!

2 个答案:

答案 0 :(得分:2)

您可以使用Func() expressions处理它。

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

答案 1 :(得分:2)

条件表达式

它的一个选择是使用conditional expressions。它们提供了简单的方法来检查条件并根据它们提供一个值。在你的情况下,它看起来像:

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

Q和ExpressionWrapper

通过结合使用QExpressionWrapper,可以实现另一种方法。

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

ExpressionWrapper允许你包装任何表达式并定义它的最终输出类型。这样我们就可以使用QQ和括号简单地包装&表达式(或多个|表达式,并手动定义它输出的类型。< / p>

请注意,这是未记录的,所以此行为可能会在将来发生变化,但我已使用django版本1.8,1.11和2.0进行了检查,并且工作正常

示例:

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