第一章 安裝
1.安裝virtualenv
$ pip install virtualenv // or $ sudo easy_install virtualenv
$ virtualenv --version
2.Clone source code to local
$ cd flask-2017 //create a folder named flask-2017
$ git clone git@github.com:baby4bamboo/flasky.git
$ cd flasky
$ git checkout 1a
3.虛擬環(huán)境
$ virtualenv venv //創(chuàng)建虛擬環(huán)境
$ source venv/bin/activate //啟動(dòng)虛擬環(huán)境
$ deactivate //關(guān)閉虛擬環(huán)境
4.需要安裝的Flask 相關(guān)的包:
$ pip install flask //MarkupSafe, Jinja2, click, Werkzeug, itsdangerous, flask
5.python 命令行:
$ python
>>> import flask //檢查某個(gè)包是否安裝好
>>> exit() //退出python命令行
第二章 程序的基本結(jié)構(gòu)
Web 服務(wù)器使用一種名為 Web 服務(wù)器網(wǎng)關(guān)接口
(Web Server Gateway Interface,WSGI)的協(xié)議,把接收自客戶端的所有請(qǐng)求都轉(zhuǎn)交給這個(gè)對(duì)象處理。
git checkout 2b
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, %s!</h1>' % name
if __name__ == '__main__':
app.run(debug=True)
__name__=='__main__'是 Python 的慣常用法,在這里確保直接執(zhí)行這個(gè)腳本時(shí)才啟動(dòng)開(kāi)發(fā)Web 服務(wù)器。如果這個(gè)腳本由其他腳本引入,程序假定父級(jí)腳本會(huì)啟動(dòng)不同的服務(wù)器,因此不會(huì)執(zhí)行 app.run()
python hello.py //啟動(dòng)程序
Ctrl + C //退出程序
然后瀏覽器訪問(wèn)
http://127.0.0.1:5000/
請(qǐng)求上下文

>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> current_app.name
'hello'
>>> app_ctx.pop()
路由調(diào)度
Flask 使用 app.route 修飾器或者非修飾器形式的 app.add_url_rule() 生成映射。
$ python
>>> from hello import app
>>> app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])
Hook
? before_first_request:注冊(cè)一個(gè)函數(shù),在處理第一個(gè)請(qǐng)求之前運(yùn)行。
? before_request:注冊(cè)一個(gè)函數(shù),在每次請(qǐng)求之前運(yùn)行。
? after_request:注冊(cè)一個(gè)函數(shù),如果沒(méi)有未處理的異常拋出,在每次請(qǐng)求之后運(yùn)行。
? teardown_request:注冊(cè)一個(gè)函數(shù),即使有未處理的異常拋出,也在每次請(qǐng)求之后運(yùn)行
響應(yīng)
Flask將視圖函數(shù)的返回值,作為響應(yīng)結(jié)果,通常就是一個(gè)字符串
也可以接受第二個(gè)參數(shù),為響應(yīng)的狀態(tài)碼,例如200,404等
也可以接受第三個(gè)參數(shù),是一個(gè)由首部(header)組成的字典
@app.route('/')
def index():
return '<h1>Bad Request</h1>', 400
當(dāng)然,更好的選擇是返回一個(gè)respones對(duì)象
from flask import make_response
@app.route('/')
def index():
response = make_response('<h1>This document carries a cookie!</h1>')
response.set_cookie('answer', '42')
return response
有一種特殊的響應(yīng),叫做重定向(14章會(huì)有例子)
from flask import redirect
@app.route('/')
def index():
return redirect('http://www.example.com')
還有一種特殊的響應(yīng),由abort()函數(shù)生成,用來(lái)處理錯(cuò)誤
abort 不會(huì)把控制權(quán)交還給調(diào)用它的函數(shù),而是拋出異常把控制權(quán)交給 Web 服務(wù)器
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擴(kuò)展
$ pip install flask-script
文件的改動(dòng):
from flask.ext.script import Manager
manager = Manager(app)
# ...
if __name__ == '__main__':
manager.run()
這個(gè)擴(kuò)展的初始化方法也適用于其他很多擴(kuò)展:把程序?qū)嵗鳛閰?shù)傳給構(gòu)造函數(shù),初始化主類(lèi)的實(shí)例。創(chuàng)建的對(duì)象可以在各個(gè)擴(kuò)展中使用。在這里,服務(wù)器由 manager.run() 啟動(dòng),啟動(dòng)后就能解析命令行了。
python hello.py runserver --host 0.0.0.0 //監(jiān)聽(tīng)所有窗口
第三章 模板
面對(duì)一個(gè)請(qǐng)求,其實(shí)server做了兩件事情,分成‘業(yè)務(wù)邏輯’和‘表現(xiàn)邏輯’
例如用戶輸入了賬號(hào)密碼,點(diǎn)擊登錄
業(yè)務(wù)邏輯:在檢查賬號(hào)密碼是否匹配,如果匹配成功則登錄
表現(xiàn)邏輯:跳轉(zhuǎn)到已經(jīng)登錄用戶的首頁(yè)
Jinja2模板引擎
Jinja有兩種定界符。{% ... %}和{{ ... }}。前者用于執(zhí)行像for循環(huán)或賦值等語(yǔ)句,后者向模板輸出一個(gè)表達(dá)式的結(jié)果。
參見(jiàn): http://jinja.pocoo.org/docs/templates/#synopsis

if
{% if user %}
Hello, {{ user }}!
{% else %}
Hello, Stranger!
{% endif %}
for
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
macro
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>
單獨(dú)文件導(dǎo)入
{% import 'macros.html' as macros %}
<ul>
{% for comment in comments %}
{{ macros.render_comment(comment) }}
{% endfor %}
</ul>
模板繼承
base.html
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Application</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
extend.html
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style>
</style>
{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}
模板渲染
<h1>Hello, {{ name }}!</h1>
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
name=name,左邊的name表示模板中的占位符,右邊的name表示url中傳進(jìn)來(lái)的參數(shù)
使用Flask-Bootstrap集成Twitter Bootstrap
$ pip install flask-bootstrap
hello.py:初始化 Flask-Bootstrap
from flask.ext.bootstrap import Bootstrap
# ...
bootstrap = Bootstrap(app)
doc 整個(gè) HTML 文檔
html_attribs <html> 標(biāo)簽的屬性
html <html> 標(biāo)簽中的內(nèi)容
head <head> 標(biāo)簽中的內(nèi)容
title <title> 標(biāo)簽中的內(nèi)容
metas 一組 <meta> 標(biāo)簽
styles 層疊樣式表定義
body_attribs <body> 標(biāo)簽的屬性
body <body> 標(biāo)簽中的內(nèi)容
navbar 用戶定義的導(dǎo)航條
content 用戶定義的頁(yè)面內(nèi)容
scripts 文檔底部的 JavaScript 聲明
鏈接
url_for('index') //得到相對(duì)路徑,這里會(huì)得到'/'
url_for('index', _external=True) //得到絕對(duì)路徑,這里是'http://localhost:5000/'。(外部使用的時(shí)候要用,例如郵件)
url_for('user', name='john', _external=True) //返回結(jié)果是 http://localhost:5000/user/john。
url_for('index', page=2) // 返回結(jié)果是 /?page=2
使用Flask-Moment本地化日期和時(shí)間
1.安裝
pip install flask-moment
2.hello.py 中初始化Moment:
from flask.ext.moment import Moment
moment = Moment(app)
3.基模板(templates/base.html)中引入moment.js
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
4.把變量current_time 傳入模板進(jìn)行渲染
from datetime import datetime
@app.route('/')
def index():
return render_template('index.html',current_time=datetime.utcnow())
5.在模板中渲染 current_time
templates/index.html
{% block page_content %} //page_content 表示顯示的內(nèi)容,之前錯(cuò)誤的放在了{(lán)% block scripts %}中,發(fā)現(xiàn)不能在頁(yè)面顯示
<p>The local date and time is {{ moment(current_time).format('LLL') }}</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
{% endblock %}
第四章 web表單
Flask-WTF 能保護(hù)所有表單免受跨站請(qǐng)求偽造(Cross-Site Request Forgery,CSRF)的攻擊
hello.py
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
$ pip install flask-wtf


M
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')
T
templates/index.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
V
hello.py:
@app.route('/', methods=['GET', 'POST'])
def index():
name = None
form = NameForm() //建一個(gè)M
if form.validate_on_submit():
name = form.name.data
form.name.data = ''
return render_template('index.html', form=form, name=name) //把得到的數(shù)據(jù)傳給T
MTV:
M是Model,數(shù)據(jù)結(jié)構(gòu),
T是template,模板,最后渲染出來(lái)的頁(yè)面的樣子
V是view,先建一個(gè)M,然后通過(guò)一系列的邏輯和運(yùn)算,得到需要的數(shù)據(jù),傳給T
注:這里我寫(xiě)的時(shí)候出現(xiàn)了三個(gè)錯(cuò)誤
app.config['SECRET_KEY'] = 'hard to guess string' //這句忘記加了
{{ wtf.quick_form(form) }} //寫(xiě)成了{(lán)% wtf.quick_form(form) %}
name = form.name.data //寫(xiě)成了 name = form.name.data()
session and 重定向
from flask import Flask, render_template, session, redirect, url_for
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'))
hello.py flash消息
from flask import Flask, render_template, session, redirect, url_for, 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('Looks like you have changed your name!')
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html',form = form, name = session.get('name'))
base.html
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×
</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
第七章 web表單
項(xiàng)目結(jié)構(gòu)

工廠函數(shù)
app/__init__.py :
bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()
def create_app(config_name):
app = Flask(__name__) //實(shí)例化一個(gè)Flask對(duì)象,命名為app
app.config.from_object(config[config_name]) //把我們配置的config,設(shè)置給這個(gè)app
config[config_name].init_app(app) //config中定義的初始化函數(shù)
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
from .main import main as main_blueprint //在app中,注冊(cè)blueprint
app.register_blueprint(main_blueprint)
return app
blueprint
app/main/__init__.py:創(chuàng)建藍(lán)本:
from flask import Blueprint
main = Blueprint('main', __name__)
from . import views, errors
實(shí)例化一個(gè)藍(lán)本,命名為main,接受兩個(gè)參數(shù),第一個(gè)是藍(lán)本的名字,第二個(gè)是藍(lán)本所在的包或模塊,一般都是__name__
views(路由設(shè)定),errors(錯(cuò)誤處理)必須在創(chuàng)建藍(lán)本之后導(dǎo)入,以防止循環(huán)依賴(lài)。
app/main/view.py:
# from ...
@main.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
#...
return redirect(url_for('.index'))
return render_template('index.html',form=form, name=session.get('name'),known=session.get('known', False))
1)不再像單文件@route('/'),有了藍(lán)本 以后,路由都是由藍(lán)本提供,所以前面要加上藍(lán)本的名字
@main.route('/', methods=['GET', 'POST'])。
2)url_for 不再像單文件的url_for('index'),現(xiàn)在需要指明相應(yīng)的藍(lán)本url_for('main.index')
url_for實(shí)際上就是根據(jù)后面的參數(shù)來(lái)生成url的,以便程序跳轉(zhuǎn),所以一般都這樣用redirect(url_for('.index'))
3)藍(lán)本的端點(diǎn)都是有命名空間的,其實(shí)就是藍(lán)本的名字,這樣可以避免沖突。例如main.index和auth.index就是兩個(gè)頁(yè)面,可以同時(shí)存在。
4)url_for('main.index')可以簡(jiǎn)寫(xiě)成url_for('.index'),這里省略的命名空間就是當(dāng)前請(qǐng)求所在的藍(lán)本,例如現(xiàn)在的main,跨藍(lán)本的重定向必須帶有命名空間的端點(diǎn)名。
7.5 需求文件
(venv) $ pip freeze >requirements.txt //生成需求文件
(venv) $ pip install -r requirements.txt //使用需求文件去安裝各種包