Please enable Javascript to view the contents

Django 装饰器

 ·  ☕ 4 分钟

在前后端分离开发过程中,提供给前端的 API 接口,有的使用 GET 请求,有的使用 POST 请求。为了避免,后端在 views.py 的 request 中取值报错,需要在每个 view 函数中判断请求头的方法。于是,提取了一个公共的函数放在 utils.py 中,以便 view 函数引用。使用时依然繁琐,最后,在 Django 文档中找到了require_http_methods 装饰器轻松解决。本文主要介绍装饰器的基本概念,Django 装饰器实现的方法。

1. 基本概念

  • 装饰器模式

装饰器模式允许,动态地扩展额外的功能,而不需要改变原来的结构。下图,表示的就是,装饰器动态扩展功能的特性。

  • Python装饰器

Python装饰器是装饰器模式的Python实现,实际上,就是函数包装器。Python解释器加载函数时,执行包装器。包装器可以修改函数接收的参数和返回值。

2. 对请求方法过滤

如果不使用装饰器,需要实现函数is_post_method_return_true_or_err_resp ,从request中获取请求的方法,进行判断。当判定为否时,返回一个 HttpResponse 对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# -*- coding: utf-8 -*-
import json
from django.http import HttpResponse

def is_post_method_return_true_or_err_resp(request):
    if request.method == "POST":
        return True, None
    else:
        return False, HttpResponse(
            json.dumps({
                "result": False,
                "data": [],
                "message": u"请使用POST方法",
                "code": -1
            }), content_type='application/json')

在使用时,在 view 函数中,调用 utils 中判断的函数即可。

1
2
3
4
5
def my_view(request):
    checked, error_resp = is_post_method_return_true_or_err_resp(request)
    if checked:
        return error_resp
    pass

3. Django装饰器

上面的实现方式,每次调用函数,都需要判断返回值,return 一个 HttpResponse。能不能将这部分也提取出来呢?当然可以,Django 提供了相应的装饰器。

1
2
3
4
5
from django.views.decorators.http import require_GET

@require_POST
def my_view(request):
    pass

上面的 @require_POST,是 Python 中使用装饰器的语法糖。只需要一行代码,就给 view 函数,装配上仅限POST方法的功能。

如果使用GET方法访问,Django 会返回给浏览器:

GET YOUR_URL 405 (METHOD NOT ALLOWED)

4. 写一个装饰器

为了更进一步了解装饰器的原理和实现方法,这里使用Python实现一个 my_require_http_methods 装饰器,传入允许的请求方法列表。如果,请求的方法被允许,继续执行,否则返回错误提示。

4.1 一个简单的装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import json

from django.http import HttpResponse

def is_post_method(func):
    def my_function(request, *args, **kwargs):
        if request.method == "POST":
            # do somthing
            return func(request, *args, **kwargs)
        else:
            # do something
            return HttpResponse(
                json.dumps({
                    "result": False,
                    "data": [],
                    "message": u"请使用POST方法",
                    "code": -1
                }), content_type='application/json')

    return my_function

在 view 函数前面增加 @is_post_method,即可。

1
2
3
@is_post_method
def my_view(request):
    pass

上面的调用过程为:

1
is_post_method(my_view)()

被装饰器修饰的 my_view 函数作为参数,调用 is_post_method 函数。首先执行的是,my_function 函数,然后是 my_vew 函数。

4.2 带参数的装饰器

如果装饰器新增的功能,需要传入参数怎么办呢?装饰器可以理解为一个闭包,将函数当做参数,然后返回一个绑定变量的函数。利用闭包,在外层,再定义一个高阶的函数,用于参数的传递。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def required_method(method_list):
    def _required_method_decorator(func):
        def my_function(request, *args, **kwargs):
            if request.method in method_list:
                return func(request, *args, **kwargs)
            else:
                return HttpResponse(
                    json.dumps({
                        "result": False,
                        "data": [],
                        "message": u"请使用%s方法" % "、".join(method_list),
                        "code": -1
                    }), content_type='application/json')

        return my_function
    return _required_method_decorator

在 view 函数前面增加 @required_method,带上允许的方法列表参数,即可。

1
2
3
@required_method(['POST'])
def my_view(request):
    pass

上面的调用过程为:

1
required_method(['POST'])(my_view)()

装饰器函数中,method_list=['POST']func=my_view

4.3 元信息恢复

1
2
3
@required_method(['GET'])
def home(request):
    print home.__name__ # 输出 my_function

打印函数名,会发现被装饰器修饰过的函数,name属性被修改。不仅仅是name属性,元信息比如名字、文档字符串、注解和参数签名都丢失了。

为了解决这个问题,Django 的 utils.functional 包提供 wraps 装饰器来恢复元信息。最终的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import json
from django.utils.functional import wraps
from django.http import HttpResponse


def required_method(method_list):
    def _required_method_decorator(func):
        @wraps(func)
        def my_function(request, *args, **kwargs):
            if request.method in method_list:
                return func(request, *args, **kwargs)
            else:
                return HttpResponse(
                    json.dumps({
                        "result": False,
                        "data": [],
                        "message": u"请使用%s方法" % "、".join(method_list),
                        "code": -1
                    }), content_type='application/json')

        return my_function
    return _required_method_decorator

5. 参考


微信公众号
作者
微信公众号