可插拔视图¶
Changelog
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]
请牢记:因为从调用者的角度来看,类的self 被隐藏了,所以不能在类的方法上单独 使用装饰器。
用于API的方法视图¶
网络 API 经常直接对应 HTTP 变量,因此很有必要实现基于 MethodView 的 API 。即多数时候,API 需要把不同的 URL 规则应用到同一个方法视图。例如,假设你需要这样使用一个 user 对象:
URL |
方法 |
说明 |
|
|
提供所有用户的列表 |
|
|
创建新用户 |
|
|
显示单个用户 |
|
|
更新单个用户 |
|
|
删除单个用户 |
那么如何使用 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')