简体   繁体   English

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

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

I'm populating a dropdown with values defined in my models.py, something like: 我正在用我的models.py中定义的值填充下拉列表,如下所示:

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

When I'm displaying a list of people with a rank at a particular time, I want them to appear in descending rank. 当我在特定时间显示具有排名的人员列表时,我希望他们以降序出现。

Current query (merely alphabetic): 当前查询(仅字母):

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

How could I change this to order rank by its position in the list? 我怎样才能改变它在列表中的排名顺序?

I'm considering this: 我正在考虑:

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

But even if I can get the verbose values back from the numeric values, any changes in the ordering would return incorrect values for data pre-change. 但是,即使我可以从数字值中得到详细的值,顺序中的任何更改也会为数据预更改返回错误的值。 Is there a better way? 有没有更好的办法?

Chosen solution 选择的解决方案

Inspired by wim's answer, I ended up doing a data migration to change the ranks to numeric values and sorting as follows. 受wim答案的启发,我最终进行了数据迁移,以将等级更改为数值并按以下方式进行排序。

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

I'm getting the verbose values back by importing the rank_choices from my models into my views and replacing the numeric values with the corresponding titles after the sorting. 通过将模型中的rank_choices导入到视图中,并在排序后将数字值替换为相应的标题,可以得到详细的值。

It looks like you have rank stored in a CharField in database. 看来您已将rank存储在数据库的CharField中。 It's not simple to do a custom order_by without changing the schema. 在不更改架构的情况下进行自定义order_by并不容易。 Therefore, for simple cases, you may consider to make the ordering in python code: 因此,对于简单的情况,您可以考虑使用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)

After the call to sorted , the queryset will be evaluated and you will have instead a python list. 调用后sorted ,将查询集将被评估,你将有代替Python列表。

If this is not satisfactory, it is possible (but not pretty) in the Django ORM to get the result you want in a queryset by using a Case / When construct: 如果这不令人满意,则可以使用Case / When构造在Django ORM中(但不是很漂亮)在查询集中获得所需的结果:

>>> 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>]

Thanks to user "mu is too short" in the comments for inspiring this idea. 感谢用户“ mu is too short”在评论中启发了这个想法。 This will work in Django 1.8+ because of the new features in conditional expressions . 由于条件表达式中的新功能,这将在Django 1.8+中有效。

For users unfortunate enough to be stuck on older versions of Django, the same idea is possible by constructing a raw sql fragment to pass in using Queryset.extra . 对于不幸不幸被旧版Django卡住的用户,通过构造一个原始sql片段以使用Queryset.extra进行传递,可以实现相同的想法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM