寫個驗證用戶登錄的裝飾器:在調用函數前,先檢查session里有沒有用戶
from functools import wraps from flask import session, abort def login_required(func): @wraps(func) def decorated_function(*args, **kwargs): if not 'user' in session: abort(401) return func(*args, **kwargs) return decorated_function app.secret_key = '12345678'
需將此裝飾器加在每個需要驗證登錄的請求方法上即可
@app.route('/admin') @login_required def admin(): return '<h1>Admin Dashboard</h1>'
URL集中映射
Flask也支持像Django一樣,把URL路由規則統一管理,而不是寫在視圖函數上
我們先來寫個視圖函數,將它放在一個”views.py”文件中:
def foo(): return '<h1>Hello Foo!</h1>'
然后在Flask主程序上調用”app.add_url_rule”方法:
app.add_url_rule('/foo', view_func=views.foo)
這樣,路由”/foo”就綁定在”views.foo()”函數上了,效果等同於在”views.foo()”函數上加上”@app.route(‘/foo’)”裝飾器。通過”app.add_url_rule”方法,我們就可以將路由同視圖分開,將路由統一管理,實現完全的MVC。
裝飾器本質上是一個閉包函數,所以我們當然可以把它當函數使用:
app.add_url_rule('/foo', view_func=login_required(views.foo))
可插拔視圖Pluggable View
from flask.views import View class HelloView(View): def dispatch_request(self, name=None): return render_template('hello-view.html', name=name) view = HelloView.as_view('helloview') app.add_url_rule('/helloview', view_func=view) app.add_url_rule('/helloview/<name>', view_func=view)
我們創建了一個”flask.views.View”的子類,並覆蓋了其”dispatch_request()”函數,渲染視圖的主要代碼必須寫在這個函數里。然后我們通過”as_view()”方法把類轉換為實際的視圖函數,”as_view()”必須傳入一個唯一的視圖名。此后,這個視圖就可以由”app.add_url_rule”方法綁定到路由上了。上例的效果,同本篇第一節中”/hello”路徑的效果,完全一樣。
這個例子比較簡單,只是為了介紹怎么用視圖類,體現不出它的靈活性,我們再看個例子:
class RenderTemplateView(View): def __init__(self, template): self.template = template def dispatch_request(self): return render_template(self.template) app.add_url_rule('/hello', view_func=RenderTemplateView.as_view('hello', template='hello-view.html')) app.add_url_rule('/login', view_func=RenderTemplateView.as_view('login', template='login-view.html'))
視圖裝飾器支持
class HelloView(View): decorators = [login_required] def dispatch_request(self, name=None): return render_template('hello-view.html', name=name)
我們只需將裝飾器函數加入到視圖類變量”decorators”中即可。它是一個列表,所以能夠支持多個裝飾器,並按列表中的順序執行。
請求方法的支持
當我們的視圖要同時支持GET和POST請求時,視圖類可以這么定義:
class MyMethodView(View): methods = ['GET', 'POST'] def dispatch_request(self): if request.method == 'GET': return '<h1>Hello World!</h1>This is GET method.' elif request.method == 'POST': return '<h1>Hello World!</h1>This is POST method.' app.add_url_rule('/mmview', view_func=MyMethodView.as_view('mmview'))
我們只需將需要支持的HTTP請求方法加入到視圖類變量”methods”中即可。沒加的話,默認只支持GET請求。
基於方法的視圖
上節介紹的HTTP請求方法的支持,的確比較方便,但是對於RESTFul類型的應用來說,有沒有更簡單的方法,比如省去那些if, else判斷語句呢?Flask中的”flask.views.MethodView”就可以做到這點,它是”flask.views.View”的子類。我們寫個user API的視圖吧:
from flask.views import MethodView class UserAPI(MethodView): def get(self, user_id): if user_id is None: return 'Get User called, return all users' else: return 'Get User called with id %s' % user_id def post(self): return 'Post User called' def put(self, user_id): return 'Put User called with id %s' % user_id def delete(self, user_id): return 'Delete User called with id %s' % user_id
現在我們分別定義了get, post, put, delete方法來對應四種類型的HTTP請求,注意函數名必須這么寫。怎么將它綁定到路由上呢?
user_view = UserAPI.as_view('users') # 將GET /users/請求綁定到UserAPI.get()方法上,並將get()方法參數user_id默認為None app.add_url_rule('/users/', view_func=user_view, defaults={'user_id': None}, methods=['GET',]) # 將POST /users/請求綁定到UserAPI.post()方法上 app.add_url_rule('/users/', view_func=user_view, methods=['POST',]) # 將/users/<user_id>URL路徑的GET,PUT,DELETE請求, # 綁定到UserAPI的get(), put(), delete()方法上,並將參數user_id傳入。 app.add_url_rule('/users/<user_id>', view_func=user_view, methods=['GET', 'PUT', 'DELETE'])
上例中”app.add_url_rule()”可以傳入參數default,來設置默認值;參數methods,來指定支持的請求方法。
如果API多,有人覺得每次都要加這么三個路由規則太麻煩,可以將其封裝個函數:
def register_api(view, endpoint, url, primary_id='id', id_type='int'): view_func = view.as_view(endpoint) app.add_url_rule(url, view_func=view_func, defaults={primary_id: None}, methods=['GET',]) app.add_url_rule(url, view_func=view_func, methods=['POST',]) app.add_url_rule('%s<%s:%s>' % (url, id_type, primary_id), view_func=view_func, methods=['GET', 'PUT', 'DELETE']) register_api(UserAPI, 'users', '/users/', primary_id='user_id')
現在,一個”register_api()”就可以綁定一個API了
延遲加載視圖
當某一視圖很占內存,而且很少會被使用,我們會希望它在應用啟動時不要被加載,只有當它被使用時才會被加載。也就是接下來要介紹的延遲加載。Flask原生並不支持視圖延遲加載功能,但我們可以通過代碼實現。這里,我引用了官方文檔上的一個實現。
from werkzeug import import_string, cached_property class LazyView(object): def __init__(self, import_name): self.__module__, self.__name__ = import_name.rsplit('.', 1) self.import_name = import_name @cached_property def view(self): return import_string(self.import_name) def __call__(self, *args, **kwargs): return self.view(*args, **kwargs)
def bar(): return '<h1>Hello Bar!</h1>'
我們先寫了一個LazyView,然后在views.py中定義一個名為bar的視圖函數:
現在讓我們來綁定路由:
app.add_url_rule('/lazy/bar', view_func=LazyView('views.bar'))
路由綁定在LazyView的對象上,因為實現了__call__方法,所以這個對象可被調用,不過只有當’/lazy/bar’地址被請求時才會被調用。此時”werkzeug.import_string”方法會被調用,看了下Werkzeug的源碼,它的本質就是調用”__import__”來動態地導入Python的模塊和函數。所以,這個”view.bar”函數只會在’/lazy/bar’請求發生時才被導入到主程序中。不過要是每次請求發生都被導入一次的話,開銷也很大,所以,代碼使用了”werkzeug.cached_property”裝飾器把導入后的函數緩存起來
同上一節的”register_api()”函數一樣,你也可以把綁定延遲加載視圖的代碼封裝在一個函數里。
def add_url_for_lazy(url_rule, import_name, **options): view = LazyView(import_name) app.add_url_rule(url_rule, view_func=view, **options) add_url_for_lazy('/lazy/bar', 'views.bar')
