【4.0】Flask框架之CBV

发布时间 2023-08-26 21:42:21作者: Chimengmeng

【一】基本使用

from flask import Flask, render_template

# template_folder 默认就是当前文件夹下的 templates 可以不写
app = Flask(__name__, template_folder='templates')

# FBV : 基于函数的视图
@app.route('/index', methods=['GET'])
def index():
    return 'ok'

# CBV : 基于类的视图
# CBV : 需要继承基类 MethodView , MethodView 继承了 View
from flask.views import View, MethodView

# CBV 视图函数
class Home(MethodView):
    def get(self):
        return render_template('home.html')

    def post(self):
        return "POST"

# 注册路由
app.add_url_rule('/home', endpoint='home', view_func=Home.as_view('home'))

if __name__ == '__main__':
    app.run()

【二】CBV 函数加装饰器

# -*-coding: Utf-8 -*-
# @File : 01CBV .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/8/24
from flask import Flask, render_template

# template_folder 默认就是当前文件夹下的 templates 可以不写
app = Flask(__name__, template_folder='templates')


# 登录装饰器
def auth(func):
    def inner(*args, **kwargs):
        res = func(*args, **kwargs)
        return res

    return inner


# FBV : 基于函数的视图
@app.route('/index', methods=['GET'], endpoint='index')
@auth
def index():
    return 'ok'


# CBV : 基于类的视图
# CBV : 需要继承基类 MethodView , MethodView 继承了 View
from flask.views import View, MethodView


# CBV 视图函数
class Home(MethodView):
    # MethodView 有一个类属性 decorators 可以存放装饰器
    # 如果有多个可以直接 逗号隔开
    decorators = [auth]
    # 可以控制整个视图函数的请求方式
    methods = ['GET']

    def get(self):
        return render_template('home.html')

    def post(self):
        return "POST"


# 注册路由
# as_view('home') 中的 home 其实就是 endpoint
app.add_url_rule('/home', endpoint='home', view_func=Home.as_view('home'))

if __name__ == '__main__':
    app.run()
  • 为什么decorators = [auth] 能加装饰器

  • app.add_url_rule('/home', view_func=view内存地址)

  • 用装饰器一直在装饰 view内存地址 ,所以,以后执行,就是有装饰器的view,装饰器代码会走

@auth
def view(**kwargs):
    return self.dispatch_request(**kwargs)
  • 等价于 view = auth(view)
  • view_func=Home.as_view('home') home 是 endpoint,就是路由别名
    • app.add_url_rule('/home', view_func=Home.as_view('home'))
    • add_url_rule---》endpoint没传---》会执行endpoint = _endpoint_from_view_func(view_func)
    • 取函数名作为 endpoint 的值
    • view_func是 加了一堆装饰器的view函数---》它的名字应该是装饰器的名字--》但是
      • view.__name__ = name # name 就是home
    • 所以endpoint就是你传的home
    • 如果传了endpoint,就是传的那个,那as_view('home')就没作用了,但是也必须要传

【三】CBV 源码分析

  • 入口 as.view()
def as_view(
        cls, name: str, *class_args: t.Any, **class_kwargs: t.Any
) -> ft.RouteCallable:
    if cls.init_every_request:

        def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
            self = view.view_class(  # type: ignore[attr-defined]
                *class_args, **class_kwargs
            )
            # 支持异步操作
            return current_app.ensure_sync(self.dispatch_request)(**kwargs)

    else:
        self = cls(*class_args, **class_kwargs)

        def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
            return current_app.ensure_sync(self.dispatch_request)(**kwargs)
	
    # 装饰器列表
    if cls.decorators:
        view.__name__ = name
        view.__module__ = cls.__module__
        # 遍历每一个装饰器
        for decorator in cls.decorators:
            # 装饰器语法糖干的事: 
            #把被装饰的函数当参数传入到装饰器,返回结果赋值给被装饰的函数,一个个用装饰器包装view
            view = decorator(view)

    view.view_class = cls  # type: ignore
    view.__name__ = name
    view.__doc__ = cls.__doc__
    view.__module__ = cls.__module__
    view.methods = cls.methods  # type: ignore
    view.provide_automatic_options = cls.provide_automatic_options  # type: ignore
    # 返回 view 函数
    return view
  • view
if cls.init_every_request:

    def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
        self = view.view_class(  # type: ignore[attr-defined]
            *class_args, **class_kwargs
        )
        # 支持异步操作
        return current_app.ensure_sync(self.dispatch_request)(**kwargs)

else:
    self = cls(*class_args, **class_kwargs)


    def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
        return current_app.ensure_sync(self.dispatch_request)(**kwargs)
  • 调用了 dispatch_request 方法
    • 我们发现这是一个抛异常的函数
def dispatch_request(self) -> ft.ResponseReturnValue:
    """The actual view function behavior. Subclasses must override
    this and return a valid response. Any variables from the URL
    rule are passed as keyword arguments.
    """
    raise NotImplementedError()
  • 其实我们应该去找 父类 的 dispatch_request 方法
    • MethodViewdispatch_request 方法
def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue:
    # request.method.lower() 获取到 method 的小写 假设是get请求
    # meth 就是 CBV 中以get命名的方法
    meth = getattr(self, request.method.lower(), None)

    # If the request method is HEAD and we don't have a handler for it
    # retry with GET.
    # 
    if meth is None and request.method == "HEAD":
        meth = getattr(self, "get", None)

    assert meth is not None, f"Unimplemented method {request.method!r}"
    # 执行方法
    return current_app.ensure_sync(meth)(**kwargs)