前言
公司实习时遇到一个问题:项目中所使用不是Restful API,每个视图函数都要先生成字典,于是项目中便充斥着如下的代码:
1 | result={ |
维护起来比较麻烦,对前端也很不友好,为了解决该问题,自定义了一个Django中间件对API格式和异常进行统一处理,以此达到如下效果:
- 在view中只需返回
data,raise自定义的异常 - 实现自定义异常状态码(
status)和自定义异常信息(message) - view中返回的
Object若为Model,则会返回调用其__str__方法的结果 - 对于
view中出现的其他异常,Response会返回Unknown exception - 异常信息(
message)实现了i18n
Django中对异常的处理
Django中对request的处理
- 首先执行
process_request函数,然后在执行视图函数之前执行process_view函数,再执行视图函数,最后执行process_response函数 process_request只返回None,所有中间件的process_request执行完之后,就匹配路由,找到对应的视图函数,在执行视图函数之前先执行中间件的process_view函数- 如果
process_view返回 None,就继续执行后续的中间件的process_view方法,执行完所有的process_view函数之后执行视图函数 - 如果其中有个 process_view 返回了 HttpResponse,就不执行后续的 process_view 函数,会跳到第一个 process_response 函数,并继续往下执行

中间件(类)中5种方法
中间件中可以定义5个方法:
- process_request(request)
- process_view(request, view_func, view_args, view_kwargs)
- process_exception(request, exception)
- process_template_response(request, response)
- process_response(request, response)
process_request
- 中间件在收到
request请求之后执行 - 按照
settings.py中MIDDLEWARE_CLASSES的顺序,顺序执行 - 如果该函数返回
None,继续执行后面的中间件的process_request方法 - 如果该函数返回
HttpResponse,则不再继续执行后面的中间件的process_request方法
process_view
- 执行完所有中间件的
process_request方法 - 在
urls.py中找到对应视图函数 - 拿到视图函数的名称、参数,在执行视图函数之前执行
- 如果返回
None,则继续执行后面的中间件的process_view函数,然后执行下昂应的视图函数 - 如果返回
HttpResponse,则不执行后续的process_view函数,也不执行视图函数,然后执行所有的response中间件
process_exception
- 执行视图函数的过程中如果引发异常,则按照
settings.py中MIDDLEWARE_CLASSES的顺序,倒序执行process_exception方法 - 如果返回
None,继续执行下一个中间件的process_exception方法 - 如果返回
HttpReponse对象,则该中间件上方其他中间件的process_exception方法不会被调用 - 一旦其中某个中间件有返回值,则调用
template_response和response中间件
,否则启动默认的异常处理
最后半句个人理解:如果如果所有中间件的process_exception方法都执完后还没有返回值,则启动默认的异常处理
process_template_response
- 在视图函数执行结束之后执行
response是Django视图或者某一中间件的返回值(TemplateResponse对象或等价)- 只有
response实现了render方法才会执行 - 一旦所有的中间件的
template_response被执行完,则调用render方法 - 按照中间件的顺序,倒序执行
process_response
- 在视图函数执行结束之后执行
- 必须有返回值,且返回类型必须是
HttpResponse对象 - 按照中间件的顺序,倒序执行
代码实现
文件
- middleware.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24from django.utils.deprecation import MiddlewareMixin
from django.models import models
from django.core.serializers.json import DjangoJSONEncoder
from django.http import JsonResponse
class MyMiddleware(MiddlewareMixin):
def process_exception(self, request, exception):
if not isinstance(exception, BaseException):
if settings.DEBUG:
return JsonResponse({'result': '', 'msg': str(exception), 'status': 1000})
else:
return JsonResponse(UnknownException().as_dict())
else:
return JsonResponse(exception.as_dict())
def process_response(self, request, response):
procese_type = (list, tuple, dict, str, int)
if isinstance(response, models.Model):
response = str(response)
if isinstance(response, procese_type):
ret = {'result': response, 'msg': 'success', 'status': 200}
return JsonResponse(ret, encoder=DjangoJSONEncoder)
else:
return response - exceptions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from abc import ABCMeta
from .message import ErrorMsg
class InterFaceAsDictInterFace:
def as_dict(self):
ret = {'result': '', 'msg': getattr(self, '__msg__', ''), 'status': getattr(self, '__status__', '')}
return ret
class BaseException(Exception, InterFaceAsDictInterFace):
__metaclass__ = ABCMeta
def __init__(self, msg=None):
super(BaseException, self).__init__()
if msg is not None:
self.__msg__ = msg
class UnknownException(InterFaceAsDictInterFace):
__status__ = 1000
__msg__ = ErrorMsg.UNKNOWN_EXCEPTION
class MyException(BaseException):
__status__=1001
__msg__=ErrorMsg.MY_EXCEPTION - message.py
1 | from django.utils.translation import gettext as _ |
修改settings文件
修改setting中的MIDDLEWARE_CLASSES变量
1 | MIDDLEWARE = [ |
Todo
对前端Post请求进行参数校验
目前想出来了两种策略(假设post_json为序列化后的字典):
- 视图函数中使用
get从字典中获取参数,判断required的参数是否为空,raise自定义的异常,如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# exception.py
...
class ValidationError(BaseException):
__msg__ = ErrorMsg.INVALID_ARGUMENT
__status__ = 1001
...
# message.py
class ErrorMsg:
UNKNOWN_EXCEPTION= _('Unknown exception.')
INVALID_ARGUMENT = _('Invalid arguments.')
REQUIRED_ARGUMENT = _('A {0} argument is required.')
# view.py
def test(request):
...
user_name = post_json.get('username','')# required
pass_word = post_json.get('password','')# required
user_type = post_json.get('user_type','')# not required
if not username or not password:
raise ValidationError(ErrorMsg.REQUIRED_ARGUMENT.format('username/passswordd'))
... - 视图函数中对参数不做校验,只需在中间件添加一句,即可对视图函数中
raise的KeyError进行统一处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27# exception.py
...
class ValidationError(BaseException):
__msg__ = ErrorMsg.INVALID_ARGUMENT
__status__ = 1001
...
# middleware.py
...
def process_exception(self, request, exception):
if isinstance(exception, KeyError):
exception = ValidationError(ErrorMsg.REQUIRED_ARGUMENT.format(exception))
...
# message.py
class ErrorMsg:
UNKNOWN_EXCEPTION= _('Unknown exception.')
INVALID_ARGUMENT = _('Invalid arguments.')
REQUIRED_ARGUMENT = _('A {0} argument is required.')
# view.py
def test(request):
...
user_name = post_json['user_name'] # required
user_type = post_json.get('user_type','')# not required
...
视图函数返回
目前视图函数必须有返回值,不能为None,还不知道怎么解决