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

Django DRF创建Grade时触发KeyError: 'grade'问题求助

问题:实现Create方法时触发KeyError: 'grade'

问题描述

尝试实现基础的create方法创建Grade实例,但触发了KeyError,提示grade=validated_data['grade']中找不到'grade'键。

序列化器代码

class GradeCreateSerializer(serializers.ModelSerializer):
    grade = serializers.ChoiceField(choices=GRADES, source="get_grade_display")

    class Meta:
        model = Grade
        fields = ["grade"]
        # ordering = ['-created_date']


def create(self, validated_data):
    user = None
    request = self.context.get("request")
    if request and hasattr(request, "user"):
        user = request.user

    try:
        perms = Perm.objects.get(user=user)
    except:
        perms = None

    if user.role in ['OWNER', 'PARTNER']:
        school = user.owns.first()
    elif perms is not None:
        if user.role == 'STAFF' and perms.can_crt_grade_class:
            school = user.works

    instance = Grade.objects.create(
        grade=validated_data['grade'],
        school=school,
    )
    instance.save()
    return instance

错误回溯

Traceback (most recent call last):
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\django\views\generic\base.py", line 103, in view
    return self.dispatch(request, *args, **kwargs)
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\rest_framework\views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
    raise exc
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\rest_framework\views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\rest_framework\generics.py", line 190, in post
    return self.create(request, *args, **kwargs)
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\rest_framework\mixins.py", line 19, in create
    self.perform_create(serializer)
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\rest_framework\mixins.py", line 24, in perform_create
    serializer.save()
  File "F:\Projects\SchoolProject\main\Abugida\lib\site-packages\rest_framework\serializers.py", line 212, in save
    self.instance = self.create(validated_data)
  File "F:\Projects\SchoolProject\main\Classes\serializers.py", line 45, in create
    grade=validated_data['grade'],
KeyError: 'grade'

错误原因

你给grade字段指定了source="get_grade_display",但get_grade_display是Django为Choice类型字段自动生成的显示方法,用于返回选项的友好名称,而非模型实际存储的字段值。

当序列化器处理验证时,会将前端传入的grade值映射到source指定的get_grade_display,因此validated_data里的键会变成get_grade_display,而非grade。但你在create方法里试图获取validated_data['grade'],自然会触发KeyError。

另外,get_grade_display是只读方法,不能用来反向设置模型的grade字段值,这个source的用法完全错误。

修复方案

方案1:前端传入的是Grade字段的实际存储值(如选项代码/数字)

直接去掉source参数,让序列化器直接映射到模型的grade字段:

class GradeCreateSerializer(serializers.ModelSerializer):
    # 去掉source,模型本身有grade字段且为Choice类型,无需额外指定
    grade = serializers.ChoiceField(choices=GRADES)

    class Meta:
        model = Grade
        fields = ["grade"]

    def create(self, validated_data):
        user = self.context.get("request").user if self.context.get("request") else None
        if not user:
            raise PermissionDenied("未登录用户无法创建班级")
        
        perms = None
        try:
            perms = Perm.objects.get(user=user)
        except Perm.DoesNotExist:
            # 仅捕获Perm不存在的异常,避免吞掉其他错误
            pass
        
        school = None
        if user.role in ['OWNER', 'PARTNER']:
            school = user.owns.first()
        elif perms and user.role == 'STAFF' and perms.can_crt_grade_class:
            school = user.works
        
        if not school:
            raise PermissionDenied("当前用户无权限创建班级")
        
        # 此时validated_data['grade']已存在,直接使用即可
        return Grade.objects.create(
            grade=validated_data['grade'],
            school=school,
        )

方案2:前端传入的是Grade选项的显示文本

如果前端传的是友好名称(比如"一年级"而非"1"),需要手动将显示文本转换为模型存储的选项值:

class GradeCreateSerializer(serializers.ModelSerializer):
    # 接收显示文本,不指定source
    grade_display = serializers.ChoiceField(choices=[(label, label) for _, label in GRADES])

    class Meta:
        model = Grade
        fields = ["grade_display"]

    def create(self, validated_data):
        user = self.context.get("request").user if self.context.get("request") else None
        if not user:
            raise PermissionDenied("未登录用户无法创建班级")
        
        perms = None
        try:
            perms = Perm.objects.get(user=user)
        except Perm.DoesNotExist:
            pass
        
        school = None
        if user.role in ['OWNER', 'PARTNER']:
            school = user.owns.first()
        elif perms and user.role == 'STAFF' and perms.can_crt_grade_class:
            school = user.works
        
        if not school:
            raise PermissionDenied("当前用户无权限创建班级")
        
        # 将显示文本转换为对应的存储值
        grade_value = next((key for key, label in GRADES if label == validated_data['grade_display']), None)
        if not grade_value:
            raise ValidationError("无效的班级名称")
        
        return Grade.objects.create(
            grade=grade_value,
            school=school,
        )

额外优化建议

  1. 原代码中except:捕获所有异常,这会隐藏很多潜在错误,建议改为捕获特定异常(如Perm.DoesNotExist)。
  2. 添加对school为None的判断,避免创建无关联学校的Grade实例(如果模型中school是必填字段)。
  3. 无需调用instance.save(),Grade.objects.create()已经会将实例保存到数据库。

内容的提问来源于stack exchange,提问作者rediet yosef

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 19:40:28