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
相关产品推荐
相关产品推荐

