前言
公司实习时遇到一个问题:项目中所使用不是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.py1 
 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.py1 
 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,还不知道怎么解决
 
        