← All Articles A Product of Kinsa Creative

Manually order a Django queryset to match a predefined list

Say you have a need to manually position objects from a queryset. For example say you're ordering a list where you want an object with a certain attribute to be first, an object with a different attribute to be second, and so on.

One way to address this is to get each one and then chain them:

import itertools

# Use .filter() instead of .get() to avoid a TypeError that object is not iterable
obj_1 = MyModel.objects.filter(id=4)
obj_2 = MyModel.objects.filter(id=2)
obj_3 = MyModel.objects.filter(id=5)
queryset = list(itertools.chain(obj_1, obj_2, obj_3))

But what about when you also need to maintain calling those objects as a single queryset rather than than a list. For example the queryset for a Django ModelForm form field.

from django.db.models import Case, When

# this is the pre-defined order of MyModel objects we want to return
ordered_list_of_ids = [4, 2, 5, 3, 1]
# create a Case/When/then statement that says "when ID is 4 then position is 0, when ID is 2 then position is 1, and so on
preserved_ordering = Case(*[When(id=id, then=position) for position, id in enumerate(ordered_list_of_ids)])
queryset = MyModel.objects.filter(id__in=ordered_list_of_ids).order_by(preserved_ordering)

Feedback?

Email us at enquiries@kinsa.cc.