如何针对自定义值列表订购 Django 查询?

How can I order a Django query against a custom list of values?

我正在使用 models.py 中定义的值填充下拉列表,例如:

    rank_choices = (
        ('King', 'King')
        ('Prince', 'Prince')
        ('Duke', 'Duke')
        ('Baron', 'Baron')
        ('Lackey', 'Lackey')

当我显示在特定时间具有排名的人员列表时,我希望他们按降序排列。

当前查询(仅字母顺序):

attendees = Rank.objects.filter(feast__id=feast__id).exclude(rank='Lackey').order_by('rank')

如何将其更改为按其在列表中的位置排序?

我正在考虑这个:

    rank_choices = (
        (0, 'King')
        (1, 'Prince')
        (2, 'Duke')
        (3, 'Baron')
        (4, 'Lackey')

但即使我可以从数值中获取详细值,排序中的任何更改都会 return 不正确的数据值 pre-change。有没有更好的方法?

选择的解决方案

受wim回答的启发,我最终进行了数据迁移,将排名更改为数值并按如下方式排序。

ranks = sorted(ranks, key=lambda x: int(x.rank))

我通过将 rank_choices 从我的模型导入我的视图并在排序后用相应的标题替换数值来恢复详细的值。

您似乎 rank 存储在数据库的 CharField 中。在不更改架构的情况下执行自定义 order_by 并不简单。因此,对于简单的情况,您可以考虑在python代码中进行排序:

rank_lookup = {rank: n for n,(rank,rank) in enumerate(rank_choices[::-1])}
ranks = Rank.objects.filter(...).values_list('rank', flat=1)
ranks = sorted(ranks, key=rank_lookup.get)

调用 sorted 后,查询集将被评估,您将得到一个 python 列表。

如果这不令人满意,在 Django ORM 中可以(但不是很漂亮)通过使用 Case/When 构造在查询集中获得您想要的结果:

>>> for rank, rank in rank_choices:
...     Rank.objects.create(rank=rank)
...     
>>> Rank.objects.order_by('rank')  # alphabetical
[<Rank: Baron>, <Rank: Duke>, <Rank: King>, <Rank: Lackey>, <Rank: Prince>]
>>> rank_lookup = {rank: n for n,(rank,rank) in enumerate(rank_choices)}
>>> cases = [When(rank=rank, then=Value(rank_lookup[rank])) for rank in rank_lookup]
>>> cases
[<When: WHEN <Q: (AND: ('rank', 'King'))> THEN Value(0)>,
 <When: WHEN <Q: (AND: ('rank', 'Lackey'))> THEN Value(4)>,
 <When: WHEN <Q: (AND: ('rank', 'Baron'))> THEN Value(3)>,
 <When: WHEN <Q: (AND: ('rank', 'Prince'))> THEN Value(1)>,
 <When: WHEN <Q: (AND: ('rank', 'Duke'))> THEN Value(2)>]
>>>
>>> Rank.objects.annotate(my_order=Case(*cases, output_field=IntegerField())).order_by('my_order')
[<Rank: King>, <Rank: Prince>, <Rank: Duke>, <Rank: Baron>, <Rank: Lackey>]
>>> Rank.objects.annotate(my_order=Case(*cases, output_field=IntegerField())).order_by('-my_order')
[<Rank: Lackey>, <Rank: Baron>, <Rank: Duke>, <Rank: Prince>, <Rank: King>]

感谢 for inspiring this idea. This will work in Django 1.8+ because of the new features in conditional expressions 中的用户 "mu is too short"。

对于不幸被困在旧版本 Django 上的用户,可以通过构建原始 sql 片段以使用 Queryset.extra.

传递相同的想法。