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, )
额外优化建议
- 原代码中
except:捕获所有异常,这会隐藏很多潜在错误,建议改为捕获特定异常(如Perm.DoesNotExist)。 - 添加对
school为None的判断,避免创建无关联学校的Grade实例(如果模型中school是必填字段)。 - 无需调用
instance.save(),Grade.objects.create()已经会将实例保存到数据库。
内容的提问来源于stack exchange,提问作者rediet yosef
相关产品推荐
相关产品推荐

