代码之家  ›  专栏  ›  技术社区  ›  DavidRguez

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

  •  3
  • DavidRguez  · 技术社区  · 6 年前

    假设我有这个模型,我想按逻辑运算对它们进行排序 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 查询集:

    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方法表达查询时才使用它。如果您确实需要使用它,请使用QuerySet提交票据。extra关键字与您的用例(请先检查现有票据列表),以便我们可以增强QuerySet API以允许删除extra()。我们不再改进或修复此方法的错误。

    那么,做这件事的最佳方式是什么?

    谢谢

    2 回复  |  直到 4 年前
        1
  •  3
  •   GwynBleidD    6 年前

    条件表达式

    一种选择是使用 conditional expressions 。它们提供了检查条件的简单方法,并根据条件提供一个值。在您的情况下,它将如下所示:

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

    Q和ExpressionWrapper

    还有另一种有点老套的方法,通过结合使用 Q ExpressionWrapper

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

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

    请注意,这是没有文档记录的,因此这种行为在将来可能会改变,但我已经使用django版本1.8、1.11和2.0进行了检查,效果很好

    示例:

    sorted_things = things.annotate(diff=ExpressionWrapper(Q(n1=F('n2')), output_field=BooleanField())).order_by('diff')
    
        2
  •  2
  •   Martin    6 年前

    您可以使用 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')