You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 00:39:17