Django DRF如何按外键字段分组聚合并正确序列化数据?
按话题聚合用户点赞数并正确序列化的解决方案
问题背景
现有Django模型支持用户为话题点赞,需通过DRF API返回话题ID、话题名称、总点赞数的聚合数据,但按用户/团队过滤后分组聚合时,多次尝试出现字段缺失或映射错误:
- 按
topic分组后,topic_id不显示,topic_name输出ID字符串 - 按
topic__id、topic__name分组后,序列化器无法识别字段,仅返回upvotes - 调整
values后,topic_name重复且仍显示ID,未调用__str__方法
现有模型与序列化器
模型代码(修正Django语法错误)
# models.py from django.db import models from django.contrib.auth.models import User class Topic(models.Model): name = models.CharField(max_length=255) # Django无StringField,应为CharField并指定长度 def __str__(self): return str(self.name) class UserUpvotes(models.Model): """记录用户对话题的点赞数""" user = models.ForeignKey(User, on_delete=models.CASCADE) topic = models.ForeignKey(Topic, on_delete=models.CASCADE) upvotes = models.PositiveIntegerField(default=0)
原序列化器代码
# serializers.py from rest_framework import serializers from .models import UserUpvotes class TopicUpvotesSerializer(serializers.ModelSerializer): topic_name = serializers.StringRelatedField(source="topic") class Meta: model = UserUpvotes fields = ["topic_id", "topic_name", "upvotes"]
解决方案
1. 调整数据库聚合查询逻辑
聚合查询需先指定分组字段(topic_id和topic__name),再通过Sum聚合点赞数,同时保留过滤逻辑:
# views.py from django.db.models import Sum from rest_framework.response import Response from rest_framework.views import APIView from .models import UserUpvotes from .serializers import AggregatedTopicUpvotesSerializer class AggregatedTopicUpvotesView(APIView): def get(self, request): # 示例:过滤当前用户的点赞记录,可替换为团队过滤逻辑 filtered_upvotes = UserUpvotes.objects.filter(user=request.user) # 按话题ID和名称分组,计算每个话题的总点赞数 aggregated_data = filtered_upvotes.values( "topic_id", "topic__name" ).annotate( upvotes=Sum("upvotes") ).order_by("-upvotes") # 可选:按点赞数降序排列 serializer = AggregatedTopicUpvotesSerializer(aggregated_data, many=True) return Response(serializer.data)
2. 改用普通Serializer适配聚合结果
由于聚合返回的是字典(而非UserUpvotes模型实例),不能使用ModelSerializer,需自定义Serializer映射字段:
# serializers.py from rest_framework import serializers class AggregatedTopicUpvotesSerializer(serializers.Serializer): topic_id = serializers.IntegerField() topic_name = serializers.CharField(source="topic__name") upvotes = serializers.IntegerField()
关键说明
- 原
ModelSerializer依赖模型字段,而聚合结果中的topic__name并非UserUpvotes的直接字段,因此无法识别 - 分组时必须显式指定
topic_id和topic__name,确保聚合后能获取到所需的话题信息 - 使用
Sum聚合函数计算总点赞数,替代原模型中单个用户的点赞数记录
最终输出示例
[ { "topic_id": 3, "topic_name": "Korean Studies", "upvotes": 14 }, { "topic_id": 12, "topic_name": "Inflation", "upvotes": 3 } ]
内容的提问来源于stack exchange,提问作者Original BBQ Sauce
相关产品推荐
相关产品推荐

