Django DRF为排序列表项添加排名索引的实现方法
DRF列表接口追加排名索引实现方案
首先明确一点:你给出的第二种期望返回格式不是合法的JSON结构,标准JSON规范中数组类型仅支持按顺序存储值,无法用数字作为键挂载对象,实际开发中优先选择第一种带index字段的数组结构,兼容性最好,前后端处理都更简单。
最简便实现方式(无需修改序列化器)
你只需要在视图层完成排序后,遍历排好序的结果列表,用enumerate生成从1开始的排名,追加到每个条目里即可,修改后的视图代码如下:
from operator import itemgetter from rest_framework import viewsets from rest_framework.response import Response from .models import User from .serializers import RankUserAllPurchaseSerializer class RankBestPurchaserViewset(viewsets.ViewSet): def list(self, request): queryset = User.objects.all() serializer = RankUserAllPurchaseSerializer(queryset, many=True) # 按消费金额倒序排序 sorted_list = sorted(serializer.data, key=itemgetter('amount'), reverse=True) # 遍历追加排名索引,start=1表示排名从1开始计数 for rank, item in enumerate(sorted_list, start=1): item['index'] = rank return Response(sorted_list)
改完之后接口返回的结构和你期望的第一种格式完全一致。
额外性能优化建议
你当前的序列化器写法存在严重的N+1查询问题:每序列化1个用户,就会单独执行2次数据库查询统计消费总额和消费次数,用户量超过100之后接口响应速度会明显下降。
可以通过ORM注解的方式,在数据库层面一次性完成所有用户的统计计算,把总查询次数从2N+1降到1次,优化后的代码如下:
- 修改视图层的查询集,提前用
annotate统计数据:
from django.db.models import Sum, Count, Q class RankBestPurchaserViewset(viewsets.ViewSet): def list(self, request): # 用注解一次性算出所有用户的消费总额、消费次数 queryset = User.objects.annotate( total_amount=Sum('sale__saleproduct__price', filter=Q(sale__saleproduct__isnull=False)), total_qty=Count('sale__saleproduct') ) serializer = RankUserAllPurchaseSerializer(queryset, many=True) sorted_list = sorted(serializer.data, key=itemgetter('amount'), reverse=True) for rank, item in enumerate(sorted_list, start=1): item['index'] = rank return Response(sorted_list)
- 修改序列化器,直接取注解生成的字段值,不再重复查库:
class RankUserAllPurchaseSerializer(serializers.BaseSerializer): def to_representation(self, instance): return { 'id': instance.id, 'username': instance.username, 'surname': instance.surname, 'amount': float(instance.total_amount or 0), 'qty_amount': instance.total_qty, }
关于第二种返回格式的说明
非常不建议使用该格式,原因有两个:
- 不符合标准JSON规范,数组类型不支持自定义数字键,强行实现只能把顶层从数组改成普通对象,所有数字键序列化后会自动转为字符串,前端还需要额外做类型转换
- 不符合REST接口的通用设计规范,后续维护、接口文档生成都会遇到额外问题
如果因为特殊业务场景非要用这种结构,可参考如下实现(再次提醒:非必要不要用):
def list(self, request): # 省略前面的查询、排序逻辑 res = {} for rank, item in enumerate(sorted_list, start=1): res[rank] = item return Response(res)
内容的提问来源于stack exchange,提问作者Gum
相关产品推荐
相关产品推荐

