Django ORM分组查询后DRF序列化报KeyError问题求助
错误原因
你触发KeyError的核心原因是:
- 调用
values()执行分组后返回的是字典类型的查询集,仅包含你传入values()的分组字段 + 后续annotate定义的聚合字段 - 你在用默认的
MetricSerializer(对应Metric模型)序列化时,序列化器会尝试读取所有定义的字段(比如channel),如果当前分组没有指定channel作为分组字段,返回的字典里就不存在这个键,直接触发KeyError
正确实现方案
方案1:使用专用的聚合序列化器(更推荐,逻辑清晰)
1. 定义聚合序列化器
from rest_framework import serializers class MetricAggregationSerializer(serializers.Serializer): # 所有可选分组字段,设置required=False避免未传时报错 date = serializers.DateField(required=False) channel = serializers.CharField(required=False, allow_null=True) country = serializers.CharField(required=False, allow_null=True) os = serializers.CharField(required=False, allow_null=True) # 固定返回的聚合字段 total_impressions = serializers.IntegerField() total_clicks = serializers.IntegerField()
2. 视图集逻辑实现
from django.db.models import Sum from rest_framework import viewsets from rest_framework.response import Response from .models import Metric class MetricViewSet(viewsets.ReadOnlyModelViewSet): queryset = Metric.objects.all() def list(self, request, *args, **kwargs): # 定义允许的分组字段,避免恶意传参 ALLOWED_GROUP_FIELDS = {"date", "channel", "country", "os"} # 从请求参数获取分组字段,示例请求:GET /metrics?group_by=channel&group_by=country group_fields = request.query_params.getlist("group_by", []) # 校验分组字段合法性 invalid_fields = set(group_fields) - ALLOWED_GROUP_FIELDS if invalid_fields: return Response( {"error": f"非法分组字段: {', '.join(invalid_fields)}"}, status=400 ) # 构造分组聚合查询,注意values必须放在annotate前面才会按指定字段分组 agg_queryset = Metric.objects.values(*group_fields).annotate( total_impressions=Sum("impressions"), total_clicks=Sum("clicks") ).order_by(*group_fields) # 可自行修改排序规则,如按总点击倒序:order_by("-total_clicks") # 序列化返回结果 serializer = MetricAggregationSerializer(agg_queryset, many=True) return Response(serializer.data)
方案2:动态修改原有ModelSerializer的字段
如果你需要复用原有MetricSerializer的字段定义,可以用动态字段逻辑:
from rest_framework import serializers from .models import Metric class DynamicMetricSerializer(serializers.ModelSerializer): class Meta: model = Metric fields = ["date", "channel", "country", "os", "impressions", "clicks"] def __init__(self, *args, **kwargs): # 接收传入的分组字段列表 group_fields = kwargs.pop("group_fields", []) super().__init__(*args, **kwargs) # 仅保留分组字段+聚合字段 allowed_fields = set(group_fields) | {"impressions", "clicks"} for field in list(self.fields.keys()): if field not in allowed_fields: del self.fields[field]
视图中初始化序列化器时传入当前分组字段即可:
serializer = DynamicMetricSerializer(agg_queryset, many=True, group_fields=group_fields)
注意事项
values()和annotate()的顺序不能颠倒,必须先写values()指定分组字段,再写annotate()执行聚合,否则ORM不会按预期分组- 如果分组字段存在空值,所有空值会被归为同一组,和SQL的GROUP BY逻辑一致
内容的提问来源于stack exchange,提问作者Jebaseelan Ravi
相关产品推荐
相关产品推荐

