爲何選擇使用flask?html
和其餘框架相比, Flask 之因此能脫穎而出,緣由在於它讓開發者作主,使其能對程序具備全面的創意控制。 前端
在 Flask 中,你能夠自主選擇程序的組件,若是找不到合適的,還能夠本身開發。
Flask 提供了一個強健的核心, 其中包含每一個 Web 程序都須要的基本功能,而其餘功能則交給行業系統中的衆多第三方擴展。
一句話歸納就是flask不是一個高度定製化的web框架,你能夠作到爲所欲爲,使用任何可能的擴展來完成你的項目。
python
狗書的代碼已上傳GitHub:Companion code to my O'Reilly book "Flask Web Development。jquery
Flask 有兩個主要依賴:路由、調試和 Web 服務器網關接口(Web Server Gateway Interface,WSGI)子系統由 Werkzeug(http://werkzeug.pocoo.org/)提供;模板系統由 Jinja2(http://jinja.pocoo.org/)提供。 Werkzeug 和 Jinjia2 都是由 Flask 的核心開發者開發而成。 git
Flask 並不原生支持數據庫訪問、 Web 表單驗證和用戶認證等高級功能。這些功能以及其餘大多數 Web 程序中須要的核心服務都以擴展的形式實現, 而後再與核心包集成。
安裝
github
pip install flask
初始化web
全部 Flask 程序都必須建立一個程序實例。 Web 服務器使用一種名爲 Web 服務器網關接口(Web Server Gateway Interface, WSGI)的協議,把接收自客戶端的全部請求都轉交給這個對象處理。
sql
from flask import Flask app = Flask(__name__)
Flask接受一個字符串做爲參數,這個參數決定程序的根目錄,以便於能找到相對於程序根目錄的資源文件的位置,一般這種狀況下都使用 __name__做爲Flask參數。shell
也就是說,此時web框架接收的請求都會經過flask實例化的對象進行處理。數據庫
這裏的初始化方式是最簡單的初始化方式,後面會使用到更爲複雜的初始化方式。
路由和視圖函數
程序實例須要知道對每一個 URL 請求運行哪些代碼,因此保存了一個 URL 到Python 函數的映射關係。處理 URL 和函數之間關係的程序稱爲路由。
在 Flask 程序中定義路由的最簡便方式,是使用程序實例提供的 app.route 修飾器,把修飾的函數註冊爲路由。
@app.route('/') def index(): return '<h1>Hello World!</h1>'
這個函數的返回值稱爲響應,是客戶端接收到的內容。若是客戶端是 Web 瀏覽器, 響應就是顯示給用戶查看的文檔(通常就是html頁面)。
程序實例用 run 方法啓動 Flask 集成的開發 Web 服務器:
if __name__ == '__main__': app.run(debug=True)
有一些選項參數可被 app.run() 函數接受用於設置 Web 服務器的操做模式。在開發過程當中啓用調試模式會帶來一些便利, 好比說激活調試器和重載程序。要想啓用調試模式, 咱們能夠把 debug 參數設爲 True。
第一個程序:
from flask import Flask app = Flask(__name__) @app.route('/') def index(): return '<h1>Hello World!</h1>' @app.route('/user/<name>') def user(name): return '<h1>Hello, {}!</h1>'.format(name) if __name__ == '__main__': app.run()
爲了不大量無關緊要的參數把視圖函數弄得一團糟, Flask 使用上下文臨時把某些對象變爲全局可訪問。有了上下文,就能夠寫出下面的視圖函數:
from flask import request @app.route('/') def index(): user_agent = request.headers.get('User-Agent') return '<p>Your browser is %s</p>' % user_agent
這裏咱們把request看成全局變量使用,實際生產中每一個線程都處理不一樣的請求,那麼他們的request必然是不一樣的。Falsk 使用上下文讓特定的變量在一個線程中全局可訪問,與此同時卻不會干擾其餘線程。
request就是一種python中的ThreadLocal對象。
只在線程中是全局變量。
#轉載至csdn,http://blog.csdn.net/hyman_c/article/details/52548540 import threading localobj = threading.local()#flask中的request就是這樣的概念 def threadfunc(name): localobj.name = name print('localobj.name is %s' % name) if __name__ == '__main__': t1 = threading.Thread(target=threadfunc, args=('Hyman',)) t2 = threading.Thread(target=threadfunc, args=('liuzhihui',)) t1.start() t2.start() t1.join() t2.join()
在多線程服務器中客戶端每創建一個連接,服務器就建立一個線程,每一個線程中就會有一個request來表示客戶端的連接請求信息。
flask上下文全局變量
沒激活程序上下文以前就調用 current_app.name 會致使錯誤,但推送完上下文以後就能夠調用了。 注意,在程序實例上調用 app.app_context() 可得到一個程序上下文。ctx.push()是激活上下文的操做,相似的,若是咱們想要回收上下文,用ctx.pop()。
from hello import app from flask import current_app app_ctx = app.app_context() app_ctx.push() current_app.name 'hello'
from flask import g g.name='manno'
g做爲flask程序全局的一個臨時變量,充當者中間媒介的做用,咱們能夠經過它傳遞一些數據。
request請求對象,封裝了客戶端發送的HTTP請求的內容。
session用戶會話,用來記住請求(好比先後一個GET請求和一個POST請求)之間的值,從數據格式上來講它是字典類型。它存在於鏈接到服務器的每一個客戶端中,屬於私有存儲,會保存在客戶端的cookie中。
session['name']=form.name.data
URL 映射是 URL 和視圖函數之間的對應關係。Flask 使用 app.route 修飾器或者非修飾器形式的 pp.add_url_rule() 生成映射。
app.url_map Map([<Rule '/' (GET, HEAD, OPTIONS) -> index>, <Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>, <Rule '/user/<name>' (GET, HEAD, OPTIONS) -> user>])
/ 和 /user/<name> 路由在程序中使用 app.route 修飾器定義。 /static/<filename> 路由是Flask 添加的特殊路由,用於訪問靜態文件。
請求鉤子
Flask 支持如下 4 種鉤子。
before_first_request:註冊一個函數,在處理第一個請求以前運行。
before_request:註冊一個函數,在每次請求以前運行。
after_request:註冊一個函數,若是沒有未處理的異常拋出,在每次請求以後運行。
teardown_request:註冊一個函數,即便有未處理的異常拋出,也在每次請求以後運行。
注:在請求鉤子函數和視圖函數之間共享數據通常使用上下文全局變量 g。例如, before_request 處理程序能夠從數據庫中加載已登陸用戶,並將其保存到 g.user 中。隨後調用視
圖函數時,視圖函數再使用 g.user 獲取用戶。
flask中具備四種鉤子被作成了修飾器,咱們在後端能夠進行調用作相關的操做.使用鉤子函數時,咱們須要藉助flask的全局變量g.g做爲中間變量,在鉤子函數和視圖函數中間傳遞數據.咱們先引入全局變量g。
第一步,引入全局變量g
from flask import g
第二步, 而後註冊一個視圖函數,用來顯示g中的數據
@app.route('/test') def test(): return g.string
這裏咱們使用request以前的鉤子
@app.before_first_request def bf_first_request(): g.string = 'before_first_request'
此時運行此路由顯示的是g中傳遞的string變量(固然也能夠不返回進行其餘操做)。
響應
flask的響應包括模板(本質是字符串)和狀態碼。
@app.route('/') def index(): return '<h1>Bad Request</h1>', 400
若是不想返回由 1 個、 2 個或 3 個值組成的元組, Flask 視圖函數還能夠返回 Response 對象。 make_response() 函數可接受 1 個、 2 個或 3 個參數(和視圖函數的返回值同樣),並
返回一個 Response 對象。
from flask import make_response @app.route('/') def index(): response = make_response('<h1>This document carries a cookie!</h1>') response.set_cookie('answer', '42')#建立對象,給對象加cookie return response
flask重定向
from flask import redirect @app.route('/') def index(): return redirect('http://www.example.com')
abort生成響應404,abort 不會把控制權交還給調用它的函數,而是拋出異常把控制權交給 Web 服務器。
from flask import abort @app.route('/user/<id>') def get_user(id): user = load_user(id) if not user: abort(404) return '<h1>Hello, %s</h1>' % user.name
使用Flask-Script支持命令行選項
Flask 的開發 Web 服務器支持不少啓動設置選項,但只能在腳本中做爲參數傳給 app.run()函數。這種方式並不十分方便,傳遞設置選項的理想方式是使用命令行參數。
Flask-Script 是一個 Flask 擴展,爲 Flask 程序添加了一個命令行解析器。 Flask-Script 自帶了一組經常使用選項,並且還支持自定義命令。
pip install flask-script
from flask.ext.script import Manager manager = Manager(app) if __name__ == '__main__': manager.run()
專爲 Flask 開發的擴展都暴漏在 flask.ext 命名空間下。 Flask-Script 輸出了一個名爲Manager 的類,可從 flask.ext.script 中引入。
這個擴展的初始化方法也適用於其餘不少擴展: 把程序實例做爲參數傳給構造函數,初始化主類的實例。 建立的對象能夠在各個擴展中使用。在這裏,服務器由 manager.run() 啓
動,啓動後就能解析命令行了。
from flask import Flask from flask_script import Manager app = Flask(__name__) manager = Manager(app) @app.route('/') def index(): return '<h1>Hello World!</h1>' @app.route('/user/<name>') def user(name): return '<h1>Hello, %s!</h1>' % name if __name__ == '__main__': manager.run()
使用manager能夠增長自定義命令
from flask_script import Manager app = Flask(__name__) manager=Manager(app) @manager.command def print_str(): print('hello world') if __name__ == '__main__': manager.run()
設置cookie
@app.route('/set_cookie') def set_cookie(): response=make_response('Hello World'); response.set_cookie('Name','Hyman') return response
咱們還能夠指定cookie的有效時長,下面的代碼把有效時長設置成了30天.一般狀況下,咱們還能夠在瀏覽器上設置cookie的有效時長,並且瀏覽器上配置的有效時長優先級要高於咱們在代碼中設置的。
outdate=datetime.datetime.today() + datetime.timedelta(days=30) response.set_cookie('Name','Hyman',expires=outdate)
獲取cookie
@app.route('/get_cookie') def get_cookie(): name=request.cookies.get('Name') return name
<h1>My name is {{request.cookies.get('Name')}}</h1> {#html模板中獲取cookie#}
刪除cookie(三種方式)
(1) 能夠經過在瀏覽器中設置來清除cookie
(2) 使用Response的set_cookie進行清除
@app.route('/del_cookie') def del_cookie(): response=make_response('delete cookie') response.set_cookie('Name','',expires=0) return response
(3)使用Response的 delete_cookie方法
@app.route('/del_cookie2') def del_cookie2(): response=make_response('delete cookie2') response.delete_cookie('Name') return response
Jinja2模板引擎
Jinja2模板引擎是flask默認的模板引擎。
Flask 提供的 render_template 函數把 Jinja2 模板引擎集成到了程序中 。使用方式與django的render基本一致。
Jinja2 能識別全部類型的變量, 甚至是一些複雜的類型,例如列表、字典和對象。
<p>A value from a dictionary: {{ mydict['key'] }}.</p> <p>A value from a list: {{ mylist[3] }}.</p> <p>A value from a list, with a variable index: {{ mylist[myintvar] }}.</p> <p>A value from an object's method: {{ myobj.somemethod() }}.</p>
jinja2很大程度上和django的模板語言很相似,包括過濾器的使用,判斷,循環,模板繼承等。
來講點特別的,jinja2支持宏(相似於函數,使用也和函數很像)。
定義及使用宏:
{% macro render_comment(comment) %} <li>{{ comment }}</li> {% endmacro %} <ul> {% for comment in comments %} {{ render_comment(comment) }} {% endfor %} </ul>
重複使用宏須要將其保存在單獨的文件中,而後在須要使用的模板中導入:
{% import 'macros.html' as macros %} <ul> {% for comment in comments %} {{ macros.render_comment(comment) }} {% endfor %} </ul>
除了重複使用宏的方式還能夠重複導入代碼塊。
common.html
<h1>我是重複的代碼片</h1>
導入這個代碼塊
{% include 'common.html' %} {% include 'common.html' %} {% include 'common.html' %} {% include 'common.html' %} <h1>Hello World</h1>
jinja2還支持與django模板同樣的模板繼承功能。
要想在flask程序中集成 Bootstrap, 顯然要對模板作全部必要的改動。不過,更簡單的方法是使用一個名爲 Flask-Bootstrap 的 Flask 擴展(這也是我喜歡flask的緣由之一吧,擴展性強,插件還不少),簡化集成的過程。
pip install flask-bootstrap
初始化 Flask-Bootstrap
from flask.ext.bootstrap import Bootstrap bootstrap = Bootstrap(app)
初始化 Flask-Bootstrap 以後,就能夠在程序中使用一個包含全部 Bootstrap 文件的基模板。
{% extends "bootstrap/base.html" %} {% block title %}Flasky{% endblock %}
{% extends "bootstrap/base.html" %} {% block title %}Flasky{% endblock %} {% block navbar %} <div class="navbar navbar-inverse" role="navigation"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">Flasky</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a href="/">Home</a></li> </ul> </div> </div> </div> {% endblock %} {% block content %} <div class="container"> <div class="page-header"> <h1>Hello, {{ name }}!</h1> </div> </div> {% endblock %}
自定製錯誤頁面
@app.errorhandler(404) def page_not_found(e): return render_template('404.html'), 404 @app.errorhandler(500) def internal_server_error(e): return render_template('500.html'), 500
url_for() 函數最簡單的用法是以視圖函數名(或者 app.add_url_route() 定義路由時使用的端點名)做爲參數, 返回對應的 URL。例如,在當前版本的 hello.py 程序中調用 url_for('index') 獲得的結果是 /。調用 url_for('index', _external=True) 返回的則是絕對地址,在這個示例中是 http://localhost:5000/。
使用 url_for() 生成動態地址時,將動態部分做爲關鍵字參數傳入。例如, url_for('user', name='john', _external=True) 的返回結果是 http://localhost:5000/user/john。
若是 Web 程序的用戶來自世界各地,那麼處理日期和時間可不是一個簡單的任務。
有一個使用 JavaScript 開發的優秀客戶端開源代碼庫,名爲 moment.js(http://momentjs.com/),它能夠在瀏覽器中渲染日期和時間。 Flask-Moment 是一個 Flask 程序擴展,能把moment.js 集成到 Jinja2 模板中。
pip install flask-moment
此模塊依賴於moment.js 和jquery.js
from datetime import datetime @app.route('/') def index(): return render_template('index.html',current_time=datetime.utcnow())
渲染當前時間:
{% block scripts %} {{ super() }} {{ moment.include_moment() }} {% endblock %} <p>The local date and time is {{ moment(current_time).format('LLL') }}.</p> <p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
{{moment.lang("zh-CN")}}//設置語言 {{moment().format('YYYY-MM-DD,h:mm:ss a')}}//設置時間格式 經常使用格式化參數 YYYY 2014 年份 YY 14 2個字符表示的年份 Q 1..4 季度 M MM 4..04 月份 MMM MMMM 4月..四月 根據moment.locale()中的設置顯示月份 D DD 1..31 一月中的第幾天 Do 1日..31日 一月中的第幾天 DDD DDDD 1..365 一年中的第幾天 X 1410715640.579 時間戳 x 1410715640579 時間戳
flask表單處理
pip install flask-wtf
爲了實現 CSRF 保護, Flask-WTF 須要程序設置一個密鑰。 Flask-WTF 使用這個密鑰生成加密令牌,再用令牌驗證請求中表單數據的真僞。設置密鑰的方法
app = Flask(__name__) app.config['SECRET_KEY'] = 'hard to guess string'
定義表單類
from flask.ext.wtf import Form from wtforms import StringField, SubmitField from wtforms.validators import Required class NameForm(Form): name = StringField('What is your name?', validators=[Required()]) submit = SubmitField('Submit')
這點flask-wtf要比django的form強大,相似於model form的功能。
flash通常寫在邏輯函數中,好比登錄信息改變時做爲提醒在頁面輸出。在前端頁面flask開放函數get_flashed_messages給模板使用,用於取出flash信息。
例,兩次登錄的人姓名改變了,使用flash進行提示。
@app.route('/',methods=['GET','POST']) def index(): form = NameForm() if form.validate_on_submit(): old_name=session.get('name') if old_name is not None and old_name != form.name.data: flash('name has been changed') return redirect(url_for('index')) session['name']=form.name.data return render_template('index2.html',form=form) return render_template('index2.html',form=form)
<form method="POST"> {{form.hidden_tag()}} <p>{{form.name.label}}</p> {{form.name()}} <br>{{form.submit }} </form> <h6>flashed message</h6> <p> {% for message in get_flashed_messages() %} {{ message }} {% endfor %} </p>
使用 Flask-Bootstrap方式渲染
{% import "bootstrap/wtf.html" as wtf %} {{ wtf.quick_form(form) }}
wtf.quick_form() 函數的參數爲 Flask-WTF 表單對象,使用 Bootstrap 的默認樣式渲染傳入的表單。
用戶提交表單後, 服務器收到一個包含數據的 POST 請求。 validate_on_submit() 會調用name 字段上附屬的 Required() 驗證函數。若是名字不爲空,就能經過驗證, validate_on_submit() 返回 True。如今,用戶輸入的名字可經過字段的 data 屬性獲取。
Flask-SQLAlchemy 是一個 Flask 擴展,簡化了在 Flask 程序中使用 SQLAlchemy 的操做。SQLAlchemy 是一個很強大的關係型數據庫框架, 支持多種數據庫後臺。 SQLAlchemy 提供了高層 ORM,也提供了使用數據庫原生 SQL 的低層功能。
pip install flask-sqlalchemy
配置數據庫
from flask.ext.sqlalchemy import SQLAlchemy basedir = os.path.abspath(os.path.dirname(__file__)) app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] =\ 'sqlite:///' + os.path.join(basedir, 'data.sqlite') app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True db = SQLAlchemy(app)
db 對象是 SQLAlchemy 類的實例,表示程序使用的數據庫,同時還得到了 Flask-SQLAlchemy提供的全部功能。
定義數據庫模型
class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) def __repr__(self): return '<Role %r>' % self.name
建立數據庫
(venv) $ python hello.py shell >>> from hello import db >>> db.create_all()
刪除數據庫
db.drop_all()
增長行數據
>>> from hello import Role, User >>> admin_role = Role(name='Admin') >>> mod_role = Role(name='Moderator') >>> user_role = Role(name='User') >>> user_john = User(username='john', role=admin_role) >>> user_susan = User(username='susan', role=user_role) >>> user_david = User(username='david', role=user_role)
此時,咱們的對象還只是本身意淫出來的,並無往數據庫中提交,咱們須要經過數據庫會話管理對數據庫作改動 。
第一步,將要提交對象加入會話中:
>>> db.session.add(admin_role) >>> db.session.add(mod_role) >>> db.session.add(user_role) >>> db.session.add(user_john) >>> db.session.add(user_susan) >>> db.session.add(user_david)
或者:
db.session.add_all([admin_role, mod_role, user_role,user_john, user_susan, user_david])
爲了把對象寫入數據庫,咱們要調用 commit() 方法提交會話:
db.session.commit()
此時,咱們的對象已經真正的存在數據庫中,能夠查詢到了。
數據庫會話能保證數據庫的一致性。提交操做使用原子方式把會話中的對象所有寫入數據庫。若是在寫入會話的過程當中發生了錯誤, 整個會話都會失效。
修改數據行
>>> admin_role.name = 'Administrator' >>> db.session.add(admin_role) >>> db.session.commit()
刪除數據庫
>>> db.session.delete(mod_role)
>>> db.session.commit()
查詢行數據
Flask-SQLAlchemy 爲每一個模型類都提供了 query 對象。最基本的模型查詢是取回對應表中的全部記錄:
>>> Role.query.all() [<Role u'Administrator'>, <Role u'User'>] >>> User.query.all() [<User u'john'>, <User u'susan'>, <User u'david'>]
過濾查詢
>>> User.query.filter_by(role=user_role).all() [<User u'susan'>, <User u'david'>]
若是你想看他在底層到底執行的sql語句是什麼。
>>> str(User.query.filter_by(role=user_role)) 'SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'
爲了不一直重複導入,咱們能夠作些配置,讓 Flask-Script 的 shell 命令自動導入特定的對象。
from flask.ext.script import Shell def make_shell_context(): return dict(app=app, db=db, User=User, Role=Role) manager.add_command("shell", Shell(make_context=make_shell_context))
使用Flask-Migrate實現數據庫遷移
使用Flsak-Migrate數據庫遷移框架,能夠保證數據庫結構在發生變化時,改變數據庫結構不至於丟失數據庫的數據。
首先第一步,建立數據倉庫
配置 Flask-Migrate
from flask.ext.migrate import Migrate, MigrateCommand migrate = Migrate(app, db)#建立一個Migrate對象並關聯對應的應用程序類對象app和數據庫管理類對象db manager.add_command('db', MigrateCommand)
在維護數據庫遷移以前,要使用 init 子命令建立遷移倉庫:
python hello.py db init
'db'是在manager.add_command('db',MigrateComand)這句中咱們聲明的命令行對象名稱,init是Migrate命令,表示初始化遷移倉庫,運行完成以後,會在當前目錄下建立一個migrations的文件夾,用於進行遷移的數據庫腳本都放在這裏。
使用migarate子命令來建立數據庫遷移腳本,在此以前咱們先改動一下數據庫的模型來驗證遷移是否成功,咱們在User模型中添加age屬性。
添加好字段後,使用下面命令自動添加遷移腳本
python flask_blog.py db migrate
upgrade() 函數把遷移中的改動應用到數據庫中, downgrade() 函數則將改動刪除。
python flask_blog.py db upgrade
在視圖函數中處理數據庫管理。
@app.route('/',methods=['GET','POST']) def index(): form = NameForm() if form.validate_on_submit(): user = User.query.filter_by(name=form.name.data).first() if user is None: user=User(name=form.name.data) db.session.add(user)#提交數據 session['name']=form.name.data form.name.data='' return redirect(url_for('index')) return render_template('index.html',form=form,name=session['name'])
<form method="POST"> Hello {% if name %}{{name}} {% else %} stranger {% endif%} {{form.hidden_tag()}} <p>{{form.name.label}}</p> {{form.name()}} <br>{{form.submit }} </form>
這裏數據提交與shell中不一樣,咱們設置到配置信息裏了。
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']=True
以後咱們的數據都會自動被提交了。