如何统一处理Django与DRF异常并返回自定义响应格式?
统一Django与DRF异常响应格式的优雅方案
问题背景
- Django 原生错误信息默认存储在
details键中 - Django Rest Framework (DRF) 错误信息默认存储在
detail键中
这导致同时抛出两类异常的应用响应格式不一致,目标是将所有错误响应统一为如下格式:
{ "message": "...", "code": "..." }
已尝试方案的局限性
- DRF自定义异常处理器:仅能处理DRF抛出的异常,无法覆盖Django原生异常(如数据库约束错误、模型验证错误)
- 模型层自定义错误信息:尝试将字典作为
error_messages的值(如下),但Django不支持这种格式
email = models.EmailField(error_messages={"required": {"message": "", "code": 123}})
- 序列化器验证器自定义消息:在
UniqueValidator中传入字典格式消息,最终会被嵌套在{"details": {"message": {...}}}结构中,无法达到顶层统一格式的要求
email = serializers.EmailField( validators=[ UniqueValidator( queryset=models.User.objects.all(), message={ "message": "a user with this email already exists", "code": status.EMAIL_EXISTS, }, ) ], )
优雅解决方案:结合DRF异常处理器与Django中间件
我们可以通过DRF自定义异常处理器处理DRF相关异常,同时通过Django全局异常中间件捕获并转换Django原生异常,两者统一输出目标格式。
1. 自定义DRF异常处理器
在项目中创建exceptions.py,编写自定义处理器,将DRF的detail转换为统一格式:
from rest_framework.views import exception_handler from rest_framework.response import Response from rest_framework import status def custom_drf_exception_handler(exc, context): # 先调用DRF默认处理器获取基础响应 response = exception_handler(exc, context) if response is not None: # 初始化统一格式响应 custom_response = { "message": response.data.get("detail", "请求错误"), "code": response.status_code } # 处理序列化器字段级错误(如UniqueValidator的嵌套错误) if isinstance(response.data, dict) and "detail" not in response.data: # 提取第一个字段的错误信息作为message first_error = next(iter(response.data.values())) if isinstance(first_error, list): first_error = first_error[0] # 兼容字典格式的自定义消息 if isinstance(first_error, dict): custom_response["message"] = first_error.get("message", str(first_error)) custom_response["code"] = first_error.get("code", status.HTTP_400_BAD_REQUEST) else: custom_response["message"] = str(first_error) return Response(custom_response, status=response.status_code) return response
然后在settings.py中配置DRF使用该处理器:
REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'your_project_name.exceptions.custom_drf_exception_handler', }
2. 自定义Django全局异常中间件
创建middleware.py,编写中间件捕获Django原生异常并转换格式:
from django.http import JsonResponse from django.core.exceptions import ValidationError, ObjectDoesNotExist from django.db import IntegrityError class DjangoExceptionMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): response = self.get_response(request) return response def process_exception(self, request, exception): # 仅处理API请求(可根据项目路由规则调整判断逻辑) if request.path.startswith('/api/'): message = str(exception) code = 500 # 针对不同异常类型定制消息和状态码 if isinstance(exception, ValidationError): # 提取Django ValidationError的details信息 if exception.details: first_error = next(iter(exception.details.values())) message = first_error[0] if isinstance(first_error, list) else str(first_error) code = 400 elif isinstance(exception, ObjectDoesNotExist): message = "请求的资源不存在" code = 404 elif isinstance(exception, IntegrityError): message = "数据库约束冲突,可能存在重复数据或关联资源不存在" code = 400 return JsonResponse( {"message": message, "code": code}, status=code ) # 非API请求按Django默认逻辑处理 return None
在settings.py的MIDDLEWARE中添加该中间件(建议放在CommonMiddleware之后):
MIDDLEWARE = [ # ... 其他中间件 'your_project_name.middleware.DjangoExceptionMiddleware', ]
3. 补充:统一错误码定义(可选)
可以在项目中定义统一的业务错误码常量,替换原生状态码,让错误语义更清晰:
# constants.py class ErrorCode: BAD_REQUEST = 400 NOT_FOUND = 404 DUPLICATE_EMAIL = 1001 INVALID_EMAIL_FORMAT = 1002
之后在处理器和中间件中直接引用这些常量即可。
方案优势
- 无需在视图中大量编写
try-except,保持代码整洁 - 分别针对DRF和Django异常做针对性处理,覆盖所有异常场景
- 完全实现了目标响应格式的统一
内容的提问来源于stack exchange,提问作者BillTheKid
相关产品推荐
相关产品推荐

