使用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函数都需要根据自身是否用到这两个变量,写上这两句代码。