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

DRF自定义错误消息异常:错误响应不更新问题排查与解决

问题描述

我编写了以下Django Rest Framework序列化器:

class CompanyProfileCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = CompanyProfile
        exclude = ["id", "company"]


class CompanyCreateSerializer(serializers.ModelSerializer):
    company_profile = CompanyProfileCreateSerializer(required=True)
    password = serializers.CharField(write_only=True)

    class Meta:
        model = Company
        fields = ["id", "email", "password", "company_profile"]
        extra_kwargs = {
            "password": {"write_only": True, "style": {"input_type": "password"}}
        }
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # 为所有字段动态添加自定义错误消息
        for field_name, field in self.fields.items():
            field.error_messages.update({
                "required": f"{field_name.replace('_', ' ').capitalize()} is required.",
                "null": f"{field_name.replace('_', ' ').capitalize()} cannot be null.",
                "invalid": f"Invalid value for {field_name.replace('_', ' ').capitalize()}."
            })

    def create(self, validated_data):
        company_profile_data = validated_data.pop("company_profile")
        company = Company.objects.create(**validated_data, **company_profile_data)
        return company

参考Stack Overflow的回答,在序列化器的__init__方法中为所有字段动态添加了自定义错误消息,但遇到异常问题:

  • 当发送包含已存在邮箱的请求时:
    {
        "email": "companytestregister1@gmail.com",
        "password": "password123",
        "company_profile": {
            "name": "Company Test Register1"
        }
    }
    
    返回正确错误响应:
    {
        "field": "email",
        "detail": "user with this email already exists."
    }
    
  • 但修改请求为不含email参数的内容时:
    {
        "password": "password123",
        "company_profile": {
            "name": "Company Test Register1"
        }
    }
    
    仍返回上述相同的错误响应,直到重启服务器。实际应返回的错误响应为:
    {
        "field": "email",
        "detail": "Email is required"
    }
    
  • 发送正确请求参数时,无需重启服务器即可正常创建公司。

以下是视图类与自定义异常处理器的代码:

视图类代码

@extend_schema(tags=["company"])
class CompanyView(
    GenericViewSet,
    CreateModelMixin,
    RetrieveModelMixin,
    UpdateModelMixin,
):
    """View to create/retrieve/update a company."""

    queryset = Company.objects.all()
    lookup_field = "id"
    parser_classes = [JSONParser, MultiPartParser, FormParser]

    def get_serializer_class(self):
        serializer_action_classes = {
            "create": CompanyCreateSerializer,
            "retrieve": CompanyRetrieveSerializer,
            "update": CompanyUpdateSerializer,
            "partial_update": CompanyUpdateSerializer,
        }
        if self.action in serializer_action_classes:
            return serializer_action_classes[self.action]
        else:
            raise ValidationError({"detail": "Method Not Allowed."})

    def get_permissions(self):
        permission_action_classes = {
            "create": [AllowAny()],
            "retrieve": [IsAuthenticated(), IsOwner()],
            "update": [IsAuthenticated(), IsOwner()],
            "partial_update": [IsAuthenticated(), IsOwner()],
        }
        if self.action in permission_action_classes:
            return permission_action_classes[self.action]
        else:
            return [NotAllowed()]

    def get_serializer_context(self):
        context = super().get_serializer_context()
        if self.action in ["update", "partial_update"]:
            context["instance"] = self.get_object()
        return context

    @extend_schema(description="Create a new company")
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    @extend_schema(description="Retrieve a single company by User ID")
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @extend_schema(description="Update a single company by User ID")
    def update(self, request, *args, **kwargs):
        return super().update(request, *args, **kwargs)

    @extend_schema(description="Partially Update a single company by User ID")
    def partial_update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        instance = serializer.update(
            instance=Company.objects.get(pk=kwargs["id"]),
            validated_data=serializer.validated_data,
        )
        output_serializer = CompanyRetrieveSerializer(
            instance, context={"request": request}
        )
        return Response(output_serializer.data)

自定义异常处理器代码

from rest_framework.views import exception_handler
from rest_framework.response import Response

def get_last_value(dictionary: dict, values = []) -> list[list[str]]:
    """
    Get the last values from a nested dictionary.
    """
    for key, item in dictionary.items():
        if isinstance(item, dict):
            get_last_value(item, values)
        else:
            values.append(item)
    return values


def custom_exception_handler(exc, context):
    """
    Custom exception handler.
    Format of exception:
    {
        "field": "Field name",
        "detail": "Error message"
    }
    """
    response = exception_handler(exc, context)
    if response is not None:
        if isinstance(response.data, dict):
            field = next(iter(response.data))
            error_message = get_last_value(response.data)[0][0]
            error_response = Response({
                "field": field,
                "detail": error_message
            }, status=response.status_code)
            return error_response
        else:
            error_message = str(response.data)
            error_response = Response({
                "field": "non_field_errors",
                "detail": error_message
            }, status=response.status_code)
            return error_response
    return response

请问该问题的原因是什么?如何解决?


问题原因

问题出在自定义异常处理器的get_last_value函数使用了可变默认参数values = []。Python中,可变默认参数会在函数定义时被初始化一次,之后每次调用函数都会复用同一个列表对象。

当第一次请求触发邮箱重复的错误时,values列表会被填充该错误信息;后续请求(比如缺少email的请求)调用get_last_value时,不会重新初始化空列表,而是直接在之前的列表上追加新的错误信息。但你的代码只取get_last_value(response.data)[0][0],也就是列表的第一个元素,所以始终返回第一次的错误信息,直到服务器重启(重启后函数重新定义,默认参数才会被重新初始化)。

解决方案

修改get_last_value函数,不要使用可变默认参数,而是在函数内部初始化空列表:

def get_last_value(dictionary: dict) -> list[list[str]]:
    """
    Get the last values from a nested dictionary.
    """
    values = []
    for key, item in dictionary.items():
        if isinstance(item, dict):
            values.extend(get_last_value(item))
        else:
            values.append(item)
    return values

同时,更新自定义异常处理器中调用get_last_value的代码:

error_message = get_last_value(response.data)[0]

这样每次调用get_last_value都会创建新的空列表,不会复用之前的错误数据,就能正确返回当前请求对应的错误信息了。

内容的提问来源于stack exchange,提问作者Ashutosh Chapagain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 08:52:03