Django QuerySet 按 ID 自定义排序
给定一个包含多个ID或主键的列表,我想生成一个按列表中顺序排列的对象集合。
通常我会这样开始:
pk_list = [5, 9, 2, 14]
queryset = MyModel.objects.filter(pk__in=pk_list)
这样做当然会返回对象,但返回的顺序是根据模型的默认排序属性,而我希望按照pk_list
中主键的顺序来获取记录。
最终结果必须是一个单一的对象集合(QuerySet
),因为我想把这个按顺序排列的QuerySet
传递给Django的ModelMultipleChoiceField
表单字段。
3 个回答
1
我不知道有没有办法通过过滤条件来实现这个。如果你的数据库能按照IN
子句的顺序返回数据行,那么你可能可以把Django的extra
方法和你数据库提供的ROWNUM
结合起来,来达到这个目的。
比如说:
queryset = MyModel.objects.filter(pk__in=[pk_list]).extra(
select: {'rownum': 'row_num()'}).order_by('rownum')
这里的row_num()
假设是一个数据库函数,用来返回当前行的行号。Postgresql 8.4及以上版本支持row_num()
,但我不知道怎么按照IN
子句中的值的顺序来排列返回的行。
我觉得更好的办法是子类化ModelMultipleChoiceField
,在渲染时添加自定义的排序逻辑。
5
在Django 2.2中,我可以通过以下方法根据一个顺序列表来排列查询集:
pk_list = [4, 23, 10, 9]
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(pk_list)])
new_qs = qs.filter(pk__in=pk_list).order_by(preserved)
11
这个功能没有内置的方法可以实现。
如果你在使用MySQL数据库,可以利用这个数据库的FIELD()
函数来设置一个自定义的排序顺序,然后根据这个顺序进行排序。这样做是可行的:
pk_list = [5, 9, 2, 14]
ordering = 'FIELD(`id`, %s)' % ','.join(str(id) for id in pk_list)
queryset = MyModel.objects.filter(pk__in=[pk_list]).extra(
select={'ordering': ordering}, order_by=('ordering',))