為什么選擇使用flask?
和其他框架相比, Flask 之所以能脫穎而出,原因在於它讓開發者做主,使其能對程序具有全面的創意控制。
在 Flask 中,你可以自主選擇程序的組件,如果找不到合適的,還可以自己開發。
Flask 提供了一個強健的核心, 其中包含每個 Web 程序都需要的基本功能,而其他功能則交給行業系統中的眾多第三方擴展。
一句話概括就是flask不是一個高度定制化的web框架,你可以做到隨心所欲,使用任何可能的擴展來完成你的項目。
狗書的代碼已上傳GitHub:Companion code to my O'Reilly book "Flask Web Development。
Flask 有兩個主要依賴:路由、調試和 Web 服務器網關接口(Web Server Gateway Interface,WSGI)子系統由 Werkzeug(http://werkzeug.pocoo.org/)提供;模板系統由 Jinja2(http://jinja.pocoo.org/)提供。 Werkzeug 和 Jinjia2 都是由 Flask 的核心開發者開發而成。
Flask 並不原生支持數據庫訪問、 Web 表單驗證和用戶認證等高級功能。這些功能以及其他大多數 Web 程序中需要的核心服務都以擴展的形式實現, 然后再與核心包集成。
安裝
pip install flask
初始化
所有 Flask 程序都必須創建一個程序實例。 Web 服務器使用一種名為 Web 服務器網關接口(Web Server Gateway Interface, WSGI)的協議,把接收自客戶端的所有請求都轉交給這個對象處理。
from flask import Flask app = Flask(__name__)
Flask接受一個字符串作為參數,這個參數決定程序的根目錄,以便於能找到相對於程序根目錄的資源文件的位置,通常這種情況下都使用 __name__作為Flask參數。
也就是說,此時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
之后我們的數據都會自動被提交了。