正确做法是使用中间件捕获异常并返回json响应:定义中间件类,在process_exception中判断异常类型、查表映射状态码,用jsonresponse(status=xxx, safe=false)返回;需支持同步与异步视图,自定义异常继承exception而非apiexception。

直接用 django.core.handlers.exception 的 handle_uncaught_exception 不行,它不支持返回 JSON 响应;正确做法是覆盖 BaseHandler.get_response 或更稳妥地使用中间件捕获异常。
用中间件捕获所有视图异常并转为 JSON
这是最可控、可调试、且不影响 Django 默认错误页面(如 DEBUG=True 时)的方式。中间件在视图执行后、响应返回前介入,能统一处理 Exception 子类,包括你自定义的业务异常。
- 在
middlware.py中定义一个类,继承BaseMiddleware或直接实现__call__ - 在
process_exception(self, request, exception)方法中判断异常类型,构造JsonResponse - 必须显式返回
JsonResponse,否则 Django 会继续走默认异常处理流程 - 注意顺序:该中间件需放在
SecurityMiddleware之后、但早于CommonMiddleware(避免被其重写状态码)
自定义异常类要继承 Exception 而非 APIException
Django REST Framework 的 APIException 只在 DRF 视图里生效;纯 Django 视图(View、TemplateView、函数视图)抛出它不会被 DRF 异常处理器捕获。若混用 DRF 和普通视图,统一用原生 Exception 子类更可靠。
- 例如定义
class BadRequestError(Exception): pass - 在中间件中用
isinstance(exception, BadRequestError)匹配 - 避免依赖
status_code属性——原生异常没有这个属性,需自己在类中加或通过映射表转换 - 不要在异常类里硬编码 HTTP 状态码,应在中间件中根据异常类型查表映射(如
{BadRequestError: 400, PermissionDeniedError: 403})
JsonResponse 构造时必须设 status 且禁用 safe=False(当返回 dict 外结构)
JsonResponse 默认只接受 dict,且自动加 Content-Type: application/json;但如果你返回的是带额外字段的结构(比如 {"code": 1001, "message": "xxx", "data": null}),没问题;一旦你想返回 list(如批量错误详情),就必须传 safe=False,否则抛 TypeError: In order to allow non-dict objects to be serialized...。
- 推荐始终用 dict 封装,避免
safe=False带来的 XSS 风险(虽然 JSON list 本身不易被注入,但习惯上不放开) - 状态码必须显式传入
status=400,不能只靠HttpResponse.status_code = 400,否则部分客户端(如某些 axios 版本)可能读不到 - 示例:
return JsonResponse({"code": 400, "message": str(exception)}, status=400)
真正容易漏掉的是中间件对异步视图(async def)的支持——Django 4.1+ 的中间件默认不处理 async 视图,除非你实现 __acall__ 并 await self.get_response_async(request)。如果项目已用异步视图,这里不补全,异常就完全绕过你的中间件。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











