DRF中如何将ListAPIView的排名字段传递给Serializer避免重复计算
排行榜视图优化与排名传递方案
问题背景
使用DRF构建排行榜API时,现有实现存在两个核心问题:
- 序列化器的
place字段仅返回占位符"1",希望直接复用视图中已完成的排序结果,避免重复计算排名 total_karma的计算方式低效,每个用户需循环遍历投票记录累加,引发大量数据库查询
一、传递排名到序列化器
视图中已完成排序和Top10截取,直接给每个User对象附加rank属性,序列化器读取该属性即可:
修改视图get_queryset
class LeaderboardAPIView(ListAPIView): serializer_class = UserSerializerLeaderboard permission_classes = (IsAuthenticated,) def get_queryset(self): queryset = User.objects.all() # 按总积分倒序排序并截取前10 sorted_users = sorted(queryset, key=lambda x: x.total_karma, reverse=True)[:10] # 为每个用户添加排名属性 for idx, user in enumerate(sorted_users, start=1): user.rank = idx return sorted_users
修改序列化器get_place
class UserSerializerLeaderboard(serializers.ModelSerializer): score = serializers.SerializerMethodField(read_only=True) place = serializers.SerializerMethodField(read_only=True) def get_score(self, obj): return obj.total_karma def get_place(self, obj): return obj.rank # 直接读取视图附加的rank属性 class Meta: model = User fields = ("score", "place")
二、核心性能优化:重构total_karma计算
原代码循环遍历投票记录累加的方式会引发N+1查询,改用数据库聚合查询一次性计算:
修改User模型方法
from django.db.models import Sum @property def total_karma(self): return self.total_post_score() + self.total_comment_score() def total_post_score(self): # 用Sum聚合函数一次查询得到总投票数 return PostVote.objects.filter(post__user=self).aggregate(total=Sum('vote'))['total'] or 0 def total_comment_score(self): # 同理用Sum聚合计算评论投票总数 return CommentVote.objects.filter(comment__user=self).aggregate(total=Sum('vote'))['total'] or 0
三、进一步优化:数据库层面完成排序
上述方案仍需拉取所有用户后排序,用户量较大时依然低效。可直接在数据库查询时计算总积分并排序,仅返回Top10:
最终优化版视图
from django.db.models import Sum, F class LeaderboardAPIView(ListAPIView): serializer_class = UserSerializerLeaderboard permission_classes = (IsAuthenticated,) def get_queryset(self): # 数据库层面聚合计算总积分、排序、取Top10 queryset = User.objects.annotate( post_score=Sum('post__postvote__vote'), comment_score=Sum('comment__commentvote__vote') ).annotate( total_karma=F('post_score') + F('comment_score') ).filter( total_karma__gt=0 # 可选:过滤总积分为0的用户 ).order_by('-total_karma')[:10] # 为查询结果添加排名 for idx, user in enumerate(queryset, start=1): user.rank = idx return queryset
对应修改序列化器
因已通过annotate添加total_karma字段,序列化器可直接读取,无需调用模型的@property:
class UserSerializerLeaderboard(serializers.ModelSerializer): score = serializers.IntegerField(source='total_karma', read_only=True) place = serializers.SerializerMethodField(read_only=True) def get_place(self, obj): return obj.rank class Meta: model = User fields = ("score", "place")
内容的提问来源于stack exchange,提问作者rd360
相关产品推荐
相关产品推荐

