可插入视图 — Flask 文档

来自菜鸟教程
Flask/docs/2.0.x/views
跳转至:导航、​搜索

可插入视图

0.7 版中的新功能。


Flask 0.7 引入了受 Django 通用视图启发的可插入视图,这些视图基于类而不是函数。 主要目的是您可以替换部分实现,并通过这种方式具有可定制的可插入视图。

基本原理

假设您有一个从数据库加载对象列表并呈现到模板中的函数:

@app.route('/users/')
def show_users(page):
    users = User.query.all()
    return render_template('users.html', users=users)

这既简单又灵活,但如果您想以通用的方式提供此视图,以适应其他模型和模板,您可能需要更大的灵活性。 这就是可插入的基于类的视图出现的地方。 作为将其转换为基于类的视图的第一步,您将执行以下操作:

from flask.views import View

class ShowUsers(View):

    def dispatch_request(self):
        users = User.query.all()
        return render_template('users.html', objects=users)

app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users'))

如您所见,您需要做的是创建 flask.views.View 的子类并实现 dispatch_request()。 然后我们必须使用 as_view() 类方法将该类转换为实际的视图函数。 您传递给该函数的字符串是视图将具有的端点的名称。 但这本身并没有帮助,所以让我们稍微重构一下代码:

from flask.views import View

class ListView(View):

    def get_template_name(self):
        raise NotImplementedError()

    def render_template(self, context):
        return render_template(self.get_template_name(), **context)

    def dispatch_request(self):
        context = {'objects': self.get_objects()}
        return self.render_template(context)

class UserView(ListView):

    def get_template_name(self):
        return 'users.html'

    def get_objects(self):
        return User.query.all()

这对于这么小的例子当然没有多大帮助,但足以解释基本原理。 当您拥有基于类的视图时,问题就会出现 self 指向什么。 其工作方式是,每当分派请求时,都会创建该类的新实例,并使用 URL 规则中的参数调用 dispatch_request() 方法。 类本身使用传递给 as_view() 函数的参数进行实例化。 例如,您可以编写这样的类:

class RenderTemplateView(View):
    def __init__(self, template_name):
        self.template_name = template_name
    def dispatch_request(self):
        return render_template(self.template_name)

然后你可以像这样注册它:

app.add_url_rule('/about', view_func=RenderTemplateView.as_view(
    'about_page', template_name='about.html'))

方法提示

通过使用 route() 或更好的 add_url_rule(),可插入视图像常规函数一样附加到应用程序。 然而,这也意味着您必须在附加此视图时提供视图支持的 HTTP 方法的名称。 为了将该信息移动到类中,您可以提供具有以下信息的 methods 属性:

class MyView(View):
    methods = ['GET', 'POST']

    def dispatch_request(self):
        if request.method == 'POST':
            ...
        ...

app.add_url_rule('/myview', view_func=MyView.as_view('myview'))

基于方法的调度

对于 RESTful API,为每个 HTTP 方法执行不同的函数特别有用。 使用 flask.views.MethodView,您可以轻松做到这一点。 每个 HTTP 方法都映射到类的同名方法(只是小写):

from flask.views import MethodView

class UserAPI(MethodView):

    def get(self):
        users = User.query.all()
        ...

    def post(self):
        user = User.from_form_data(request.form)
        ...

app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))

这样您也不必提供 methods 属性。 它是根据类中定义的方法自动设置的。


装饰视图

由于视图类本身不是添加到路由系统的视图函数,因此装饰类本身没有多大意义。 相反,您必须手动装饰 as_view() 的返回值:

def user_required(f):
    """Checks whether user is logged in or raises error 401."""
    def decorator(*args, **kwargs):
        if not g.user:
            abort(401)
        return f(*args, **kwargs)
    return decorator

view = user_required(UserAPI.as_view('users'))
app.add_url_rule('/users/', view_func=view)

从 Flask 0.8 开始,还有一种替代方法,您可以在其中指定要在类声明中应用的装饰器列表:

class UserAPI(MethodView):
    decorators = [user_required]

由于从调用者的角度来看隐式自我,您不能在视图的各个方法上使用常规视图装饰器,但是请记住这一点。


API 的方法视图

Web API 通常与 HTTP 动词密切配合,因此基于 MethodView 实现此类 API 非常有意义。 也就是说,您会注意到 API 将需要不同的 URL 规则,这些规则在大多数情况下会转到相同的方法视图。 例如,考虑您在网络上公开一个用户对象:

网址 方法 说明
/users/ GET 提供所有用户的列表
/users/ POST 创建一个新用户
/users/<id> GET 显示单个用户
/users/<id> PUT 更新单个用户
/users/<id> DELETE 删除单个用户

那么你将如何使用 MethodView 来做到这一点? 诀窍是利用您可以为同一个视图提供多个规则的事实。

让我们暂时假设视图如下所示:

class UserAPI(MethodView):

    def get(self, user_id):
        if user_id is None:
            # return a list of users
            pass
        else:
            # expose a single user
            pass

    def post(self):
        # create a new user
        pass

    def delete(self, user_id):
        # delete a single user
        pass

    def put(self, user_id):
        # update a single user
        pass

那么我们如何将其与路由系统连接起来呢? 通过添加两个规则并明确提及每个规则的方法:

user_view = UserAPI.as_view('user_api')
app.add_url_rule('/users/', defaults={'user_id': None},
                 view_func=user_view, methods=['GET',])
app.add_url_rule('/users/', view_func=user_view, methods=['POST',])
app.add_url_rule('/users/<int:user_id>', view_func=user_view,
                 methods=['GET', 'PUT', 'DELETE'])

如果您有很多看起来相似的 API,您可以重构该注册代码:

def register_api(view, endpoint, url, pk='id', pk_type='int'):
    view_func = view.as_view(endpoint)
    app.add_url_rule(url, defaults={pk: None},
                     view_func=view_func, methods=['GET',])
    app.add_url_rule(url, view_func=view_func, methods=['POST',])
    app.add_url_rule(f'{url}<{pk_type}:{pk}>', view_func=view_func,
                     methods=['GET', 'PUT', 'DELETE'])

register_api(UserAPI, 'user_api', '/users/', pk='user_id')