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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 01:21:03