Reputation: 2281
Consider this sample model:
MODEL_CHOICES = ( (u"SPAM", u"Spam"), (u"XOUP", u"Eggsoup"), )
(snip)
type = models.CharField(max_length=4, choices=MODEL_CHOICES)
(The actual implementation is domain-specific and in non-English, so this sample has to do.)
When building my query, I'd like to sort the results by that type field, and present the results to the user. Naturally, I'd like to sort by the display name of that field.
Something along the lines of:
documents = MyModel.objects.filter(...).order_by("type")
However, [query-set].order_by([field])
only allows sorting by field name, which will result in SPAM < XOUP (to the machine) even though Eggsoup < Spam (to the human reader).
Consider this set of instances sorted by type:
Name | Type
obj1 | SPAM
obj2 | SPAM
obj3 | SPAM
obj4 | XOUP
obj5 | XOUP
But this is the order the user will see, i.e. the user will see the display name, not the internal field value of the type column:
Name | Type
obj1 | Spam
obj2 | Spam
obj3 | Spam
obj4 | Eggsoup
obj5 | Eggsoup
Which in the eyes of the human user is not sorted correctly.
Is there a feature in Django that allows sorting by display name? Or a way to accomplish this "manually"? (Renaming the choices so that the display name has the same order as the actual values is not an option.)
Upvotes: 2
Views: 2900
Reputation: 46
It's possible with a queryset, and sometimes required. For example:
from django.db.models import Case, Q, Value, When
def get_queryset(self, request):
queryset = super().get_queryset(request)
choices = sorted(MODEL_CHOICES, key=lambda x: x[1])
args = [
When(Q(type=val), then=Value(i))
for i, (val, _) in enumerate(choices)
]
return queryset.order_by(
Case(*args, default=Value(999))
)
Upvotes: 0
Reputation: 1635
If the result sets are small enough, you could do an in-memory sort of results in code. I can't think of any decent way to sort the results in database level. It certainly would be possible if you used stored procedure which is aware of display names, however you'd have to write raw sql. As for manual sorting- you can use something like this:
obj_list = list(Model.objects.all())
import operator
obj_list.sort(key=operator.methodcaller('get_foo_display'))
Here are some nice examples on how to sort lists in Python: http://wiki.python.org/moin/HowTo/Sorting
Upvotes: 4
Reputation: 25154
Assuming the display name comes from the model as well then you can sort by that field to order the choices like you want.
MODEL_CHOICES = MyModel.objects.all().values_list('value_field', 'display_field').order_by('display_field')
...
type = models.CharField(max_length=4, choices=MODEL_CHOICES)
If the choices are a constant like in your question then you can simply change the order in the constant.
MODEL_CHOICES = ((u"XOUP", u"Eggsoup"), (u"SPAM", u"Spam"), )
In the end the choices is just a list of tuples so you can sort with normal Python sort. The Python wiki has a nice example of sorting a list of tuples on the second element.
Upvotes: 0