在Flask中我们可能会经常提到这么两个概念,一个是视图,另一个就是蓝图。
视图是一个应用对请求进行响应的函数。 Flask 通过模型把进来的请求 URL 匹配到 对应的处理视图。视图返回数据, Flask 把数据变成出去的响应。 Flask 也可以反 过来,根据视图的名称和参数生成 URL 。
那么我们首先来了解一下视图的相关知识。
首先我们来看下最简单的视图函数:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return "Hello Flask!"
if __name__ == '__main__':
app.run()
其中hello_world函数也就是我们的视图函数了。
Flask在0.7版本后引入了可插拔视图,其实最简单的理解就是该视图使用了类来代替函数。
想要使用类视图函数,那么首先我们得自己定义一个类,继承自Flask.view.View:
from flask.views import View
class Admin(View):
def dispatch_request(self):
pass
如果你是用的是pycharm,那么编译器会提示你Class Admin must implement all abstract methods
。也就是说我们必须实现上面所示的dispatch_request
方法。
我们可以查看下View的源码:
class View(object):
methods = None
provide_automatic_options = None
decorators = ()
def dispatch_request(self):
raise NotImplementedError()
@classmethod
def as_view(cls, name, *class_args, **class_kwargs):
def view(*args, **kwargs):
self = view.view_class(*class_args, **class_kwargs)
return self.dispatch_request(*args, **kwargs)
if cls.decorators:
view.__name__ = name
view.__module__ = cls.__module__
for decorator in cls.decorators:
view = decorator(view)
view.view_class = cls
view.__name__ = name
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.methods = cls.methods
view.provide_automatic_options = cls.provide_automatic_options
return view
前面我们通过app.route的装饰器进行路由书写的时候,其中我们可以指定请求方法以及装饰器,在View类中同样定义了methods和decorators属性来提供对应得功能。
同时提供了as_view方法把类转换为实际视图函数。传递给函数的 字符串是最终视图的名称。
知道了上面的东西,我们简单来个示例代码:
# encoding:utf-8
from flask import request
from flask.views import View
def decorator_func(func):
print("decorator1")
print("decorator1:%s" % func.__name__)
def wrapper(*args, **kwargs):
print("decorator1 called!")
return func(*args, **kwargs)
return wrapper
class Admin(View):
methods = ['GET', 'POST']
decorators = [decorator_func]
def dispatch_request(self):
if request.method == 'GET':
return 'admin get method request'
if request.method == 'POST':
return 'admin post method request'
很简单的一个例子,包含了我们上面提到的几个要点,那么现在就剩下最后一步了,那就是使用as_view方法进行视图注册。
app.add_url_rule('/admin/', endpoint='admin', view_func=Admin.as_view('admin'))
运行下程序:
ok,这就是一个标准视图的例子。那么如果仅仅是这样写的话使用app.route的方式似乎比这种类方式简单的多,那么为什么会提供这种方式了,简单的说一点那就是类的继承。通过类的继承我们可以将许多公共的功能抽取作为父视图然后根据功能需求实现对应的子视图就好了。
最常见的场景就是我们在实现restapi的时候,通常会将json作为返回数据的格式。在flask中我们可以通过jsonify函数来将数据转化为json格式。如果每个方法我们都得自己写,那无疑是很麻烦的,此时我们就可以将这个功能抽取出来实现一个父类。
class JsonResponse(View):
methods = ['GET', 'POST']
def response_data(self):
raise NotImplementedError
def dispatch_request(self):
return jsonify(self.response_data())
class Admin(JsonResponse):
def response_data(self):
return {
"username": "flask admin"}
如果子视图类没有实现response_data方法,那么访问时会提示如下错误:
对于 REST 式的 API 来说,为每种 HTTP 方法提供相对应的不同函数显得尤为有用。使用 flask.views.MethodView 可以轻易做到这点。在这个类中,每个 HTTP 方法 都映射到一个同名函数(函数名称为小写字母):
常见的http方法:
那么如何用MethodView 来实现呢?
class Admin(MethodView):
def get(self, user_id):
if user_id is None:
return "query all admin"
else:
return "query one admin"
def post(self):
return "post one admin"
def put(self, user_id):
return "put one admin"
def delete(self, user_id):
return "delete one admin"
这是一个最简单的结构,其中函数名称对应着小写的http请求方法。此时接收到一个请求后会根据http的请求方法执行对应的方法。
这一点从源代码中可以很清楚的了解其实现。
class MethodView(with_metaclass(MethodViewType, View)):
"""A class-based view that dispatches request methods to the corresponding
class methods. For example, if you implement a ``get`` method, it will be
used to handle ``GET`` requests. ::
class CounterAPI(MethodView):
def get(self):
return session.get('counter', 0)
def post(self):
session['counter'] = session.get('counter', 0) + 1
return 'OK'
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
"""
def dispatch_request(self, *args, **kwargs):
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, "Unimplemented method %r" % request.method
return meth(*args, **kwargs)
注册该类视图和标准视图一样:
app.add_url_rule('/admin/', endpoint='admin', view_func=Admin.as_view('admin'))
此时我们可以执行不带参数的方法,不如post:
其他几个方法我们都传递了参数,此时如果直接访问:
出现这个问题的原因是Flask未能正确的解析传入的参数。我们可以通过debug查看下当前的request请求:
大家注意按照上面的注册方式此时带有参数user_id方法的url_rule仍然是’/admin/’,回想下前面路由一节的知识,很容易就发现错误点。
@app.route('/post/' )
那么该如何解决这个问题了?那就是显示的为方法声明对应的路由规则:
admin_view = Admin.as_view('admin')
app.add_url_rule('/admin/', defaults={
'user_id': None}, view_func=admin_view, methods=['GET', ])
app.add_url_rule('/admin/', view_func=admin_view, methods=['POST', ])
app.add_url_rule('/admin/' , view_func=admin_view, methods=['GET', 'PUT', 'DELETE'])
注意了,这里注册的时候view_func不要使用view_func=Admin.as_view(‘admin’)的形式,这样的话根据源码的逻辑会抛出异常的:
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError(
"View function mapping is overwriting an "
"existing endpoint function: %s" % endpoint
)