【问题标题】:How can I order a Django query against a custom list of values?如何根据自定义值列表订购 Django 查询?
【发布时间】:2016-04-18 20:22:21
【问题描述】:

我正在用我的 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')

但即使我可以从数值中获取详细值,排序中的任何更改都会返回不正确的数据预更改值。有没有更好的办法?

选择的解决方案

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

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

我通过将模型中的 rank_choices 导入我的视图并在排序后将数值替换为相应的标题来获取详细值。

【问题讨论】:

    标签: python django postgresql django-queryset


    【解决方案1】:

    看起来您已将 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>]
    

    感谢comments 中的用户“mu is too short”启发了这个想法。由于conditional expressions 中的新功能,这将在 Django 1.8+ 中工作。

    对于不幸被旧版本 Django 卡住的用户,同样的想法可以通过使用 Queryset.extra 构造一个原始 sql 片段来传递。

    【讨论】:

    • 谢谢。我要试试这个。我需要显示 Rank 对象及其 fk 对象的一些其他属性,但也许我可以省略 flat=1,为 values_list 提供多个列并使用它。
    • 是的,只需修改 sorted 调用中传递给key 的参数
    • 你不能用 Django 指定 ORDER BY 表达式吗? order by case rank when 'King' then 0 when 'Prince' then 1 ... end 例如。 SQL 允许您按任何有效的表达式进行排序,而不仅仅是列。
    • @muistooshort 我真的不知道。如果你能得到它的工作,发布一个答案:)
    • 我知道 SQL 但对 Django 知之甚少。 OP 代码中的order_by('rank') 表明order_by 对字符串参数很满意,因此order_by('case rank when ...') 可能会起作用。或者,您可以 JOIN 到具有排名名称和数字并按 JOINed 编号排序的两列表,这在 Django 中可能会更干净,并且需要更少的字符串争吵和转义。
    猜你喜欢
    • 2017-04-18
    • 1970-01-01
    • 2012-01-29
    • 1970-01-01
    • 2018-01-05
    • 2017-08-13
    • 1970-01-01
    • 2011-05-15
    • 2011-08-06
    相关资源
    最近更新 更多