使用Python装饰器简化Django请求处理代码
在用Django做开发时,遇到这样的问题,对于客户端发送的请求,在逻辑处理之前,要对这个请求做一些验证:
- 验证用户是否登录,否则返回错误码
- 验证用户是否具有访问权限,否则返回错误码,记录日志
- try-except包裹捕获异常,出现异常时记录日志
- 如果是写操作,还需要开启事务
总的来说,代码如下:
def my_view(request):
# 判断用户是否已经通过认证,seesionid在cookie中
if request.user.is_authenticated and request.method == 'POST':
staff = request.user
if staff.type == staff_types[0]:
data = request.POST.copy()
try:
with transaction.atomic():
dosomething()
except Exception as e:
traceback.print_exc()
debug_logger.exception(repr(e))
return JsonResponse({'result': 'FAIL', 'error_code': '104'})
else:
warning_logger.warning('用户:%s 部门:%s 访问:%s 权限不足', staff.username, staff.type, staff_types[0])
return JsonResponse({'result': 'FAIL', 'error_code': '105'})
else:
return JsonResponse({'result': 'FAIL', 'error_code': '101'})
一个项目中要处理的请求特别多,如果每一个请求函数都这样写的话,代码冗余过多。
解决办法主要想到了两个:一是利用python的函数引用,将请求验证封装一下,将逻辑处理函数传递到请求验证函数中;二是利用python的装饰器,decorator,通过注解的方式包裹请求验证到逻辑处理中。
方法一
首先封装一下请求验证:
def cli_filter(request, fun):
if request.user.is_authenticated and request.method == 'POST':
staff = request.user
if staff.type == staff_types[0]:
data = request.POST.copy()
try:
return fun(data, staff)
except Exception as e:
traceback.print_exc()
debug_logger.exception(repr(e))
return JsonResponse({'result': 'FAIL', 'error_code': '104'})
else:
warning_logger.warning('用户:%s 部门:%s 访问:%s 权限不足', staff.username, staff.type, staff_types[0])
return JsonResponse({'result': 'FAIL', 'error_code': '105'})
else:
return JsonResponse({'result': 'FAIL', 'error_code': '101'})
def cli_transaction_filter(request, fun):
def anonymous(data, staff):
with transaction.atomic():
return fun(data, staff)
return cli_filter(request, anonymous)
然后修改view函数:
def view(request):
def fun(data, staff):
dosomething()
return JsonResponse({})
# return cli_transaction_filter(request, fun)
return cli_filter(request, fun)
在view函数内部定义一个子函数,用来处理逻辑,并返回处理结果。将子函数传递给封装好的请求验证函数。
最终返回结果类似于:
return cli_filter(request, fun)(return fun(data, staff))
方法二
python装饰器教程:装饰器-廖雪峰的官方网站
首先定义装饰器:
def cli_request(t=True):
def decorator(func):
def wrapper(request):
if request.user.is_authenticated and request.method == 'POST':
staff = request.user
if staff.type == staff_types[0]:
try:
if t:
# 开启事务
with transaction.atomic():
return func(request)
else:
return func(request)
except Exception as e:
traceback.print_exc()
debug_logger.exception(repr(e))
return JsonResponse({'result': 'FAIL', 'error_code': '104'})
else:
warning_logger.warning('用户:%s 部门:%s 访问:%s 权限不足', staff.username, staff.type, staff_types[0])
return JsonResponse({'result': 'FAIL', 'error_code': '105'})
else:
return JsonResponse({'result': 'FAIL', 'error_code': '101'})
return wrapper
return decorator
参数t用来判断是否开启事务
由于这是有参数的装饰器,所以要包裹三层
然后修改view函数:
@cli_request()
def select_clients(request):
data = request.POST.copy()
staff = request.user
dosomething()
return JsonResponse({})
注意这里的注解@cli_request()要带上括号,否则会被当作无参数的装饰器,无参数的装饰器只会返回两层,即最终的返回结果是函数wrapper的引用。
如果不需要开启事务,那么注解参数传入False,即@cli_request(False)
还有就是我的view函数里会用到data = request.POST.copy()和staff = request.user,在第一种方法里通过函数参数传递,不用在view函数里再写。但是方法二不行,所以每个view函数都需要根据自身是否用到这两个变量,写上这两句代码。