Django与DRF结合的全局异常处理方案详解

花开有时
花开有时
发布时间:2026-10-15| 4人浏览过
原创

本文详解Django与DRF结合时的全局异常处理方案。通过自定义exception_handler拦截DRF异常并统一返回格式,同时利用Django中间件处理非DRF请求。方案特别保留了对/admin路径的原始处理行为,确保后台管理功能不受影响,并在生产环境提供友好的错误提示。

前言

Django 与 DRF(django rest framawork) 的组合在 Python 后端开发中十分常见。为了实现统一的异常处理,我们需要一套完整的解决方案:在开发环境中展示详细的异常堆栈信息,而在生产环境中则向用户返回友好的错误提示。本文旨在提供一种统一的全局异常处理方案,供读者参考使用。

实现的目标

若不使用 DRF,仅需在 Django 中添加一个中间件即可解决全局异常处理问题。然而,DRF 会自行处理部分异常并自动返回给客户端,因此我们需要协调 Django 与 DRF 之间的异常处理策略,避免冲突。

此外,我们希望继续使用 Django 的 admin 后台进行数据查看和修改,因此必须保留 admin 原有的内部异常处理行为,确保后台管理功能正常运作。

本文设定的目标如下:

  • 保留 Django 自带的 admin 异常处理行为
  • 拦截 DRF 产生的异常,并执行全局异常处理逻辑
  • 拦截除 DRF 异常之外的其他 Django 异常,并执行全局异常处理逻辑

DRF 全局异常拦截的解决思路

首先,DRF 的所有异常均继承自 APIException 类。当 DRF 抛出异常时,会被 exception_handler 这个异常处理函数拦截(该函数的具体位置位于 /python3.7/site-packages/rest_framework/views.py 中)。

我们进一步查看该函数的源码实现:

def exception_handler(exc, context):
    """
    Returns the response that should be used for any given exception.
    By default we handle the REST framework `APIException`, and also
    Django's built-in `Http404` and `PermissionDenied` exceptions.
    Any unhandled exceptions may return `None`, which will cause a 500 error
    to be raised.
    """
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()
    if isinstance(exc, exceptions.APIException):
        headers = {}
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait
        if isinstance(exc.detail, (list, dict)):
            data = exc.detail
        else:
            data = {'detail': exc.detail}
        set_rollback()
        return Response(data, status=exc.status_code, headers=headers)
    return None

通过该函数的文档签名可知,DRF 会处理所有继承自 APIException 的异常类,同时还会额外处理 Django 内置的 Http404 和 PermissionDenied 异常,并将处理后的结果返回给前端。

如果异常不在上述处理范围内,该函数将返回 None,此时 Django 会抛出一个 500 服务器错误异常。

DRF 支持单独配置异常处理函数,因此第一步是在 settings 文件中指定自定义异常处理函数的路径:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'server.exception.exception_globe.globe_exception_handler'
}

随后,我们定义自己的异常处理程序:

该处理程序的逻辑分为三步:第一步,调用 DRF 默认的异常处理函数;第二步,对 DRF 拦截的异常进行自定义处理;第三步,将其他未处理的异常抛给 Django 系统处理。

def globe_exception_handler(exc, context):
    """
        Below is the global exception handler of drf
        Http404 / PermissionDenied / APIException
    """
    # Call REST framework's default exception handler
    response = exception_handler(exc, context)
    request = context['request']
    # Exceptions form DRF and Django built-in `Http404` and `PermissionDenied`
    if response is not None:
        if isinstance(response.data, list):
            msg = '; '.join(response.data)
        elif isinstance(response.data, str):
            msg = response.data
        else:
            msg = 'Sorry, we make a mistake (* ̄︶ ̄)!'
        ex_data = {
            "msg": msg,
            "error_code": 1000,
            "request": request.path
        }
        return JsonResponse(data=ex_data, status=response.status_code)
    # Exceptions from others
    # 如果 response 为 None 则直接触发上面的 ExceptionGlobeMiddleware
    return response

Django 异常处理方案

从上一节的结果可知,DRF 无法处理的异常会被抛给 Django。由于 Django 支持通过定义中间件来进行全局异常处理,因此我们只需定义一个 Django 全局异常处理中间件,并将其配置到 settings 文件中的 MIDDLEWARE 数组即可。

try:
    from django.utils.deprecation import MiddlewareMixin  # Django 1.10.x
except ImportError:
    MiddlewareMixin = object  # Django 1.4.x - Django 1.9.x

class ExceptionGlobeMiddleware(MiddlewareMixin):
    """
        Below is the global exception handler of django
    """
    def process_exception(self, request, exception):
        # 直接抛出 django admin 的异常
        if str(request.path).startswith('/admin/'):
            return None
        # 捕获其他异常,直接返回 500
        ex_data = {
            "msg": "Sorry, we make a mistake (* ̄︶ ̄)!",
            "error_code": 1000,
            "request": request.path
        }
        return JsonResponse(data=ex_data, status=500)

值得注意的是,我们可以在中间件的处理函数中获取到 request 对象。通过该对象,我们可以获取用户请求的 URL,进而通过判断 URL 路径来确定哪些请求来自 Django 自带的 admin 后台。

参考代码如下:

# 直接抛出 django admin 的异常
if str(request.path).startswith('/admin/'):
    return None

总结

通过上述配置,我们完成了 Django 结合 DRF 的全局异常处理,并成功保留了 Django 自带 admin 的异常处理策略。

在实际生产环境中,我们可以通过环境变量来有选择地打印和记录日志。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注本站的更多内容!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。