如何根据模型字段值以特定顺序对 Django QuerySet 进行排序?

How to order Django QuerySet in specific order based off model field values?

我有以下(简化)模型:

class MyModel(models.Model):
    TYPE_SOMETHING = 0
    TYPE_SOMETHING_ELSE = 1
    TYPE_ANOTHER = 2

    type = models.SmallIntegerField()
    # other model fields

我想执行如下查询:

MyModel.objects.all()

但是,QuerySet 必须按 type 属性排序,但按特定顺序排列:

1. TYPE_SOMETHING_ELSE
2. TYPE_SOMETHING
3. TYPE_ANOTHER

这不是升序或降序排序。

我该怎么做?

我想你可以这样做:

from django.utils.datastructures import SortedDict

MyModel.objects.extra(select=SortedDict([('types',
'case when type = %d then 2 when type = %d then 1 else 0 end' % 
(MyModel.TYPE_SOMETHING_ELSE,MyModel.TYPE_SOMETHING))])
).order_by('types')

这后面的sql查询是

SELECT (case when type = 1 then 2 when type = 0 then 1 else 0  end) AS "type", "testapp_mymodel"."id", "testapp_mymodel"."types" FROM "testapp_mymodel" ORDER BY "types" ASC

而 SortedDict 只是用来保存 select 选项的顺序

如果我正确理解你的问题,你想存储一组整数,但要按 non-standard 顺序从数据库中检索它们。我不确定您的常量变量如何适合您的模式,但我的建议是您应该通过将变量名称单独存储在一个列表中来将它们放在一边,您可以通过它们的索引访问该列表。例如:

choices_list = [TYPE_SOMETHING, TYPE_SOMETHING_ELSE, TYPE_ANOTHER]

TYPE_CHOICES = (
    (0, 1),
    (1, 0),
    (2, 3)
)

class MyModel(models.Model):

    type = models.SmallIntegerField(choices=TYPE_CHOICES)

    def get_type_display(self):
        return choices_list[self.type.id]

如果您想更进一步,您可以自动将 SmallIntegerField 覆盖为 return 这些值。

我们可以借助 django SortedDictqueryset 进行排序 有关 SortedDict 的更多信息,请参阅 this 文档

from django.utils.datastructures import SortedDict

MyModel.objects.extra( select=SortedDict([('type',
  'case when type = %d then 2 when type = %d then 1 else 0  end' %
  (MyModel.TYPE_SOMETHING_ELSE, MyModel.TYPE_SOMETHING, MyModel.TYPE_ANOTHER))]) )
  .order_by('type')

因为 Django 的 SortedDict 已弃用,我使用了与 Annop 和 Savad KP 类似的答案:

import collections
MyModel.objects.extra(
    select=collections.SortedDict([(
        'types',
        'CASE WHEN type = %d THEN 0 ',
        'WHEN type = %d THEN 1',
        'WHEN type = %s THEN 2',
        'END' % (
            MyModel.TYPE_ANOTHER,
            MyModel.TYPE_SOMETHING,
            MyModel.TYPE_SOMETHING_ELSE
        ))
    ])
).order_by('types')

最后很简单:)

我发布了一个 tiny third-party,它通过为您生成 CASE WHEN ... SQL 语句来帮助解决这个问题。

有了这个库,你可以做:

from django_reorder.reorder import reorder

MyModel.objects.order_by(reorder(type=[TYPE_SOMETHING_ELSE, TYPE_SOMETHING, TYPE_ANOTHER]))

您的查询集将按照您想要的方式排序。

您可以将 order_by() 方法与 Case() 表达式一起使用:

from django.db.models import Case, When, IntegerField    

queryset = MyModel.objects.order_by(Case(
    When(type=MyModel.TYPE_ANOTHER, then=0),
    When(type=MyModel.TYPE_SOMETHING, then=1),
    When(type=MyModel.TYPE_SOMETHING_ELSE, then=2),
    output_field=IntegerField()))

这比使用 extra() 和原始 SQL 更好,Django 文档建议不要这样做。