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

DRF局部更新时,Serializer未返回模型clean()的ValidationError却返回KeyError

DRF中模型clean()抛出的ValidationError未被捕获,反而触发KeyError的问题

问题复现步骤

  1. 序列化验证通过:
    视图中使用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)
    
  2. 执行更新操作:
    尝试调用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)
    
  3. 模型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被触发。

  4. 触发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()可正常工作。

原因分析

  1. 异常类不匹配:
    模型clean()抛出的是django.core.exceptions.ValidationError,而视图中捕获的是rest_framework.exceptions.ValidationError,二者属于不同模块的类,因此原生ValidationError不会被第一个except块捕获,会落入except Exception块。若视图在try-except之后还有返回serializer.data的逻辑,就会继续执行到序列化响应步骤,触发to_representation()。

  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 16:24:53