可插拨视图

Changelog

New in version 0.7.

Flask 0.7 版本引入了可插拨视图。可插拨视图基于使用类来代替函数,其灵感来自于 Django 的通用视图。可插拨视图的主要用途是用可定制的、可插拨的视图来替代部分 实现。

基本原理

假设有一个函数用于从数据库中载入一个对象列表并在模板中渲染:

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

上例简单而灵活。但是如果要把这个视图变成一个可以用于其他模型和模板的通用视图, 那么这个视图还是不够灵活。因此,我们就需要引入可插拨的、基于类的视图。第一步, 可以把它转换为一个基础视图:

  1. from flask.views import View
  2. class ShowUsers(View):
  3. def dispatch_request(self):
  4. users = User.query.all()
  5. return render_template('users.html', objects=users)
  6. app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users'))

就如你所看到的,必须做的是创建一个 flask.views.View 的子类,并且执行 dispatch_request() 。然后必须通过使用 as_view() 方法把类转换为实际视图函数。传递给函数的 字符串是最终视图的名称。但是这本身没有什么帮助,所以让我们来小小地重构一下:

  1. from flask.views import View
  2. class ListView(View):
  3. def get_template_name(self):
  4. raise NotImplementedError()
  5. def render_template(self, context):
  6. return render_template(self.get_template_name(), **context)
  7. def dispatch_request(self):
  8. context = {'objects': self.get_objects()}
  9. return self.render_template(context)
  10. class UserView(ListView):
  11. def get_template_name(self):
  12. return 'users.html'
  13. def get_objects(self):
  14. return User.query.all()

这样做对于示例中的小应用没有什么用途,但是可以足够清楚的解释基本原理。当你 有一个基础视图类时,问题就来了:类的 self 指向什么?解决之道是:每当请 求发出时就创建一个类的新实例,并且根据来自 URL 规则的参数调用 dispatch_request() 方法。类本身根据参数实例化后传 递给 as_view() 函数。例如可以这样写一个类:

  1. class RenderTemplateView(View):
  2. def __init__(self, template_name):
  3. self.template_name = template_name
  4. def dispatch_request(self):
  5. return render_template(self.template_name)

然后可以这样注册:

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

方法提示

可插拨视图可以像普通函数一样加入应用。加入的方式有两种,一种是使用 route() ,另一种是使用更好的 add_url_rule() 。在加入的视图中应该提供所使用的 HTTP 方法的 名称。提供名称的方法是使用 methods 属性:

  1. class MyView(View):
  2. methods = ['GET', 'POST']
  3. def dispatch_request(self):
  4. if request.method == 'POST':
  5. ...
  6. ...
  7. app.add_url_rule('/myview', view_func=MyView.as_view('myview'))

基于方法调度

对于 REST 式的 API 来说,为每种 HTTP 方法提供相对应的不同函数显得尤为有用。使用 flask.views.MethodView 可以轻易做到这点。在这个类中,每个 HTTP 方法 都映射到一个同名的类方法(名称为小写字母):

  1. from flask.views import MethodView
  2. class UserAPI(MethodView):
  3. def get(self):
  4. users = User.query.all()
  5. ...
  6. def post(self):
  7. user = User.from_form_data(request.form)
  8. ...
  9. app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))

使用这种方式,不必提供 methods 属性,它会自动使用相应 的类方法。

装饰视图

视图函数会被添加到路由系统中,而视图类则不会。因此视图类不需要装饰,只能以手工 使用 as_view() 来装饰返回值:

  1. def user_required(f):
  2. """Checks whether user is logged in or raises error 401."""
  3. def decorator(*args, **kwargs):
  4. if not g.user:
  5. abort(401)
  6. return f(*args, **kwargs)
  7. return decorator
  8. view = user_required(UserAPI.as_view('users'))
  9. app.add_url_rule('/users/', view_func=view)

自 Flask 0.8 版本开始,新加了一种选择:在视图类中定义装饰的列表:

  1. class UserAPI(MethodView):
  2. decorators = [user_required]

请牢记:因为从调用者的角度来看,类的 self 被隐藏了,所以不能在类的方法上单独 使用装饰器。

用于 API 的方法视图

网络 API 经常直接对应 HTTP 变量,因此很有必要实现基于 MethodView 的 API 。即多数时候, API 需要把不同的 URL 规则应用到同一个方法视图。例如,假设你需要这样使用一个 user 对象:

URL

方法

说明

/users/

GET

给出一个包含所有用户的列表

/users/

POST

创建一个新用户

/users/<id>

GET

显示一个用户

/users/<id>

PUT

更新一个用户

/users/<id>

DELETE

删除一个用户

那么如何使用 MethodView 来实现呢?方法是使用多个规则对应 到同一个视图。

假设视图是这样的:

  1. class UserAPI(MethodView):
  2. def get(self, user_id):
  3. if user_id is None:
  4. # 返回一个包含所有用户的列表
  5. pass
  6. else:
  7. # 显示一个用户
  8. pass
  9. def post(self):
  10. # 创建一个新用户
  11. pass
  12. def delete(self, user_id):
  13. # 删除一个用户
  14. pass
  15. def put(self, user_id):
  16. # update a single user
  17. pass

那么如何把这个视图挂接到路由系统呢?方法是增加两个规则并为每个规则显式声明 方法:

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

如果你有许多类似的 API ,那么可以代码如下:

  1. def register_api(view, endpoint, url, pk='id', pk_type='int'):
  2. view_func = view.as_view(endpoint)
  3. app.add_url_rule(url, defaults={pk: None},
  4. view_func=view_func, methods=['GET',])
  5. app.add_url_rule(url, view_func=view_func, methods=['POST',])
  6. app.add_url_rule(f'{url}<{pk_type}:{pk}>', view_func=view_func,
  7. methods=['GET', 'PUT', 'DELETE'])
  8. register_api(UserAPI, 'user_api', '/users/', pk='user_id')