DRF局部更新时,Serializer未返回模型clean()的ValidationError却返回KeyError
问题复现步骤
序列化验证通过:
视图中使用partial=True初始化Serializer,is_valid()返回True(序列化器无自定义验证器):class BatchIdView(APIView): ... def patch(self, request, batch_id): ... serializer = BatchSerializer(batch, data=data, partial=True) if not serializer.is_valid(): return Response(data=humps.camelize(serializer.errors), status=status.HTTP_400_BAD_REQUEST)执行更新操作:
尝试调用serializer.update(),并捕获异常:try: serializer.update(batch, serializer.validated_data) except ValidationError as e: return Response(data=humps.camelize(e.messages), status=status.HTTP_400_BAD_REQUEST) except Exception as e: return Response(data={"error": str(e)}, status=status.HTTP_501_NOT_IMPLEMENTED)模型save()触发验证异常:
模型的save()方法调用full_clean(),触发clean()中的ValidationError:class Batch(models.Model): ... def save(self, *args, **kwargs): self.full_clean() return super().save(*args, **kwargs) def clean(self): if condition: raise ValidationError({"field": "error text goes here"})已确认测试场景中上述
raise ValidationError被触发。触发KeyError而非返回验证错误:
未返回预期的ValidationError响应,反而触发了Serializer的to_representation()方法,该方法访问data["product_type"]时抛出KeyError:def to_representation(self, instance): data = super().to_representation(instance) response_data = copy(data) ... response_data["product_type"] = {"current": data["product_type"]}
结论:仅当传入数据触发模型验证错误时出现此问题,正常数据时to_representation()可正常工作。
原因分析
异常类不匹配:
模型clean()抛出的是django.core.exceptions.ValidationError,而视图中捕获的是rest_framework.exceptions.ValidationError,二者属于不同模块的类,因此原生ValidationError不会被第一个except块捕获,会落入except Exception块。若视图在try-except之后还有返回serializer.data的逻辑,就会继续执行到序列化响应步骤,触发to_representation()。partial更新下的序列化数据问题:
当模型验证失败、数据未保存时,Serializer使用partial=True初始化,super().to_representation(instance)可能仅序列化了传入的字段(取决于Serializer的fields配置),导致data中缺少product_type字段,进而触发KeyError。
解决方法
方法1:统一捕获ValidationError类
在视图中导入并捕获Django原生的ValidationError(DRF的ValidationError继承自它,因此可同时捕获两类异常):
from django.core.exceptions import ValidationError # 视图中的try-except块修改为: try: serializer.update(batch, serializer.validated_data) except ValidationError as e: # 转换为统一格式的错误信息 errors = e.message_dict if hasattr(e, 'message_dict') else {"detail": str(e)} return Response(data=humps.camelize(errors), status=status.HTTP_400_BAD_REQUEST) except Exception as e: return Response(data={"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
方法2:将模型验证逻辑迁移到Serializer
把模型clean()中的验证逻辑移到Serializer的validate()方法中,让验证在is_valid()阶段触发,直接通过serializer.errors返回错误:
class BatchSerializer(serializers.ModelSerializer): ... def validate(self, attrs): # 复制模型clean()中的验证逻辑 if condition: raise serializers.ValidationError({"field": "error text goes here"}) return attrs
这样is_valid()会直接返回False,视图中第一个判断分支就会返回验证错误,无需等到save()阶段。
方法3:修复to_representation()的KeyError问题
在to_representation()中添加字段存在性检查,避免KeyError:
def to_representation(self, instance): data = super().to_representation(instance) response_data = copy(data) ... # 检查字段是否存在,不存在则设置默认值 response_data["product_type"] = {"current": data.get("product_type")} return response_data
内容的提问来源于stack exchange,提问作者Lev Slinsen

