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

如何统一处理Django与DRF异常并返回自定义响应格式?

统一Django与DRF异常响应格式的优雅方案

问题背景

  • Django 原生错误信息默认存储在details键中
  • Django Rest Framework (DRF) 错误信息默认存储在detail键中

这导致同时抛出两类异常的应用响应格式不一致,目标是将所有错误响应统一为如下格式:

{
  "message": "...",
  "code": "..."
}

已尝试方案的局限性

  1. DRF自定义异常处理器:仅能处理DRF抛出的异常,无法覆盖Django原生异常(如数据库约束错误、模型验证错误)
  2. 模型层自定义错误信息:尝试将字典作为error_messages的值(如下),但Django不支持这种格式
email = models.EmailField(error_messages={"required": {"message": "", "code": 123}})
  1. 序列化器验证器自定义消息:在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 21:05:23