文章目录
- 快速上手Django(八) -Django之 统一异常、Response处理
- 一、统一Response处理
- 二、统一异常处理
- 1. 需求背景
- 2. Django、drf统一异常处理
- 3. Django、drf异常处理基础
- 4. 纯django场景下
- 5. 【重要】使用drf场景下,实现思路
- 编写自定义异常处理方法
- 在settings/dev.py文件中添加自定义异常处理模块
- 三、工作常用总结
- 自定义 serializers.ValidationError 的错误返回
- 四、参考
快速上手Django(八) -Django之 统一异常、Response处理
一、统一Response处理
参考本人文章:Django 基础(3)-Django响应:rest_framework的Response、关于HttpResponse
思路:
不推荐使用HttpResponse,推荐自定义类 继承rest_framework 的 Response, 该Response继承了django原生的HttpResponse,来实现定制化需求。
直接自定义一个 MyJsonResponse 即可,我们在view中,直接return 这个类即可。
class MyJsonResponse(Response):
def __init__(self, code=None, msg: Union[str, dict] = 'success',
data=None, status=None, template_name=None, headers=None,
exception=False, content_type=None, **kwargs):
super().__init__(None, status=status)
if isinstance(data, Serializer):
msg = (
'You passed a Serializer instance as data, but '
'probably meant to pass serialized `.data` or '
'`.error`. representation.'
)
raise AssertionError(msg)
# 改变错误提示格式
if isinstance(msg, dict):
for k, v in msg.items():
for i in v:
msg = "%s:%s" % (k, i)
# 自定义返回格式
if code is None:
code = 2000
self.data = {'code': code, 'msg': msg, 'data': data}
self.data.update(kwargs)
self.template_name = template_name
self.exception = exception
self.content_type = content_type
if headers:
for name, value in headers.items():
self[name] = value
二、统一异常处理
Django 统一异常处理
参考URL: https://cloud.tencent.com/developer/article/1912568
1. 需求背景
在项目中统一异常处理,可以防止代码中有未捕获的异常出现。
我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要统一异常处理。
统一规范,方便前后端进行错误统一处理。同时可以更好的区分是因为网络异常,还是后端异常。
后端封装异常的好处是,如果后端异常HTTP RESP CODE是200,代表请求正确打到后端并取得了响应,如果不封装,那么如果出了异常,无法通过HTTP返回码正确区分。
当然也有人不喜欢这种后端异常都是 HTTP RESP CODE是200。其实这里还有一个非常重要的作用,就是一些异常我们要捕获出来,不要暴露给前端。 如果不喜欢的 所有后端异常都是 HTTP RESP CODE是200,这块全局处理,你也可以配置响应的 HTTP RESP CODE是 500,但是这块统一处理了,我们给前端返回的格式就统一了,这个点比较好!
这样,我们给前端返回的错误信息,就可以根据情况展示到界面上了,可以和前端沟通这种特定格式返回才可以展示到界面上~
2. Django、drf统一异常处理
Django与DRF结合的全局异常处理方案详解
参考URL: jb51.net/article/238484.htm
Django RestFramework 全局异常处理详解
参考URL: https://www.easck.com/cos/2022/0228/909945.shtml
3. Django、drf异常处理基础
Django 和 DRF(django rest framawork) 的结合在 python 后台中经常出现的组合。对于异常的全局处理,我们系统能有一个统一的解决方案,在开发环境能看到比较全的异常堆栈,而在生产环境能更好的给用户一个友好的提示。
如果没有 DRF,我们只需要在 Django 中加一个中间件就可以解决全局异常的处理问题,但是 DRF 会帮我们处理一些异常并自动返回到客户端,因此我们要协调两者的异常处理策略。
DRF 的异常都是继承自 APIException
这个类的,并且 DRF 跑出的异常会被 exception_handler
这个异常处理函数拦截(这个函数的位置在 /python3.7/site-packages/rest_framework/views.py中)
DRF 通过exception_handler
这个函数的文档签名我们知道,DRF 会处理所有继承自 APIException 的异常类,并且还会额外的处理 Django 内置的 Http404 和 PermissionDenied 异常,并将这些异常的处理结果返回到前端。
REST framework定义的异常:
- APIException 所有异常的父类
APIException中默认的是 500状态码
- ParseError 解析错误
- AuthenticationFailed 认证失败
- NotAuthenticated 尚未认证
- PermissionDenied 权限决绝
- NotFound 未找到
- MethodNotAllowed 请求方式不支持
- NotAcceptable 要获取的数据格式不支持
- Throttled 超过限流次数
- ValidationError 校验失败
可以看到有些继承APIException 的异常类,覆写了 https状态码,如下
class ValidationError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
也就是说,很多的没有在上面列出来的异常,就需要我们在自定义异常中自己处理了
如果不再这些处理范围之内,函数会返回 None,这时候会给 Django 抛出一个 500 的服务器错误异常。
Django 在 web 开发中往往会和 DRF 进行结合使用,DRF 有这自己的一套异常处理机制,我们可以对 DRF 中抛出的异常进行全局拦截,比如返回统一的 json 格式以便于前台进行处理。
但是我们同时在 Django 中使用后台 admin 管理数据的同时,不希望修改原有的异常返回。因此我们需要同时配置 Django 和 DRF 的全局异常来适应我们的需求。
4. 纯django场景下
整体思路:
- 在 Django 项目中可以自定义中间件类 继承 django.middleware.common 下的 MiddlewareMixin 中间件类, 覆写process_exception函数。
- setting.py 的MiddleWares中添加自定义的MiddleWare
process_exception(self, request, exception) 函数有两个参数,exception 是视图函数异常产生的 Exception 对象
process_exception 函数的执行顺序是按照 settings.py 中设置的中间件的顺序的倒序执行。( 如果注册的多个中间件类中包含process_exception函数的时候,调用的顺序跟注册的顺序是相反的)
process_exception 函数只在视图函数中出现异常的时候才执行,它返回的值可以是 None,也可以是一个 HttpResponse 对象
如果返回 None,则继续由下一个中间件的 process_exception 方法来处理异常
5. 【重要】使用drf场景下,实现思路
第一步,自定义自己的异常处理函数
第二步,对 DRF 拦截的异常进行处理
第三步,将其他异常抛给 Django 处理
但是,感觉第三步,不是很需要,drf中定义我们自己业务异常处理,已经满足足够多的业务场景。
因此,这里我选择第一、二步骤!同时,结合自定义的异常和状态码枚举类,来统一和规范异常返回信息。
编写自定义异常处理方法
在utils目录下创建exceptions.py文件,并编写自定义异常处理方法
from django.db import DatabaseError
from rest_framework.response import Response
from rest_framework.views import exception_handler
from rest_framework import status
from redis import RedisError
import logging
log = logging.getLogger("django")
def custom_exception_handler(exc, context):
"""
自定义异常处理
:param exc: 本次请求发生的异常信息
:param context: 本次请求发送异常的执行上下文[ 本次请求的request对象,异常发送的时间,行号等...]
:return: Response响应对象
"""
response = exception_handler(exc, context)
if response is None:
"""当response结果为None时,则当前程序运行的结果有2种可能:
1. 程序真的没有报错!
2. 程序报错了,但是drf框架不识别!
"""
view = context["view"]
if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
"""数据库异常"""
log.error('[%s] %s' % (view, exc))
return Response("系统内部存储错误!", status=status.HTTP_507_INSUFFICIENT_STORAGE)
return response
参数为 exc、context,exc 为错误异常对象实例
context[‘view’] :视图类的对象
context[‘request’]:当前请求的对象
我们可以利用这些属性来生成日志,例如:
'ip地址为:%s的用户,访问:%s 视图类,报错了,请求地址是:%s'%(request.META.get('REMOTE_ADDR'),str(view),request.path)
在settings/dev.py文件中添加自定义异常处理模块
DRF 支持单独配置异常处理函数,因此第一步现在 setting 中指定自定义的异常处理函数的位置:
REST_FRAMEWORK = {
# 异常处理
'EXCEPTION_HANDLER': 'myxxx.utils.exceptions.custom_exception_handler',
}
三、工作常用总结
自定义 serializers.ValidationError 的错误返回
重写drf的ValidationError, 改变抛出异常的状态码
参考URL:
在使用DRF进行反序列过程中,总是需要校验字段,然后返回错误结果。
ValidationError 中默认的状态码是 400
这个章节,推荐查看原文,代码很完整,完善!
四、参考
Django 和 DRF(Django Rest Framework) 下的全局异常处理
Django 统一异常处理
参考URL: https://cloud.tencent.com/developer/article/1912568