轉載於:http://pdf.us/2017/10/03/451.html,感謝這位大神
《Flask Web開發:基於Python的Web應用開發實戰》學習筆記
這裏是第一部分的學習筆記。第一部分:Flask簡介
準備工作Git
git clone https://github.com/miguelgrinberg/flasky.git
git checkout 1a #簽出到某個版本
git reset --hard #若修改代碼,強行還原到簽出狀態
git fetch --all
git fetch --tags
git reset --hard origin/master
fetch從遠程倉庫更新本地倉庫的提交歷史和標籤,但並不改動原文件;git reset才真正寫入。
git diff 2a 2b #查看版本間的區別
git tag #查看所有標籤
git log --oneline --graph #查看標籤歷史
第一章 安裝
flask官網:http://flask.pocoo.org/
使用虛擬環境
pip install virtualenv (15.1.0)
yum install python-virtualenv (1.10.1)
virtualenv venv #創建虛擬環境
. venv/bin/activate #進入虛擬環境
deactivate #退出虛擬環境
pip install flask #安裝flask
第二章 程序的基本結構
初始化
from flask import Flask
app = Flask(__name__)
__name__參數用於決定程序的根目錄,以便找到相對根目錄的其它資源
路由和視圖函數
路由,處理URL與函數之間的關係,常使用app.route修飾器
視圖函數的返回值是響應
動態類型:
默認,字符串;int,整數,如<int:id>;float,浮點數;path,包含斜線/的字符串
啓動服務器
if __name__='__main__':
app.run(debug=True) #激活調試器和重載程序
一個完整的程序
hello.py:
請求-響應循環
上下文在線程級別全局可訪問
user_agent=request.headers.get('User-Agent') #該方法獲取User-Agent
Flask上下文全局變量:
變量名 | 上下文 | 說明 |
current_app | 程序上下文 | 當前激活程序的程序實例 |
g | 程序上下文 | 處理請求時用作臨時存儲,每次請求均會重設 |
request | 請求上下文 | 請求對象,封裝客戶端請求內容 |
session | 請求上下文 | 用戶會話,存儲請求間需要記住的內容 |
app.app_context()可以獲得程序上下文,之後再.push()推送上下文,然後纔可以使用current_app;收回程序上下文則是pop()。
form flask import current_app
app_ctx=app.app_context()
app_ctx.push()
current_app.name
app_ctx.pop()
請求調度
生成映射的方法:1、app.route修飾器;2、app.add_url_rule()。app.add_url_rule('/ma/','index',hello.index)
查看url映射:app.url_map。 請求方法中,HEAD,OPTIONS由Flask自動處理,不需要單獨寫方法。
請求鉤子
請求鉤子用於在請求處理之前或之後執行,使用修飾器實現。
1、before_first_request:在處理第一個請求這前執行;
2、before_request:在每次請求前執行;
3、after_request:若無未處理的異常,在每次請求後執行;
4、teardown_request:即便有未處理的異常,也在每次請求後執行
在請求鉤子函數和視圖函數之前共享數據,一般使用g
響應
響應可指定狀態碼,如:return '<h1>Hello,World!</h1>',400
響應還可帶第三個參數,由首部(header)組成的字典。響應也可以返回給Response對象,該對象也包含三個參數。與前面的相對應。response設置cookie的例子:
重定向302
...
return redirect('http://pdf.us/')
abort函數,用於處理錯誤,但不會將控制權還給調用它的函數,而是拋出異常將控制權交給Web服務器
...
user=load_user(id)
if not user:
abort(404)
return '<h1>Hello,%s</h1>' % user
Flask擴展
Flask-Script擴展,支持命令行選項,安裝:pip install flask-script
在程序中註冊擴展的方法
manager=Manager(app)
#...
if __name=='__main__':
manager.run()
專門爲Flask開發的擴展都暴露在falsk.ext命令空間下。
python hello.py runserver -h 0.0.0.0 -p 80 -d -r #啓動開發服務器的正確姿勢
第三章 模板
業務邏輯與表現邏輯
其中,index.html和user.html在templates目錄下。user.html內容爲:<h1>Hello,{{name}}</h1>
模板中的變量
形如{{ mydict['key'] }} {{ mylist[3] }} {{ myobj.somemethod() }} 都支持
過濾器
{{ <h1>Hello</h1>|safe }}
過濾器 | 說明 |
safe | 渲染值時不轉義 |
capitalize | 首字母大寫 |
lower | 小寫 |
upper | 大寫 |
title | 每個單詞首字母大寫 |
trim | 渲染時去掉首尾空格 |
striptags | 渲染之前把值中所有HTML標籤都刪掉 |
控制結構
條件控制 if...else...endif
for循環
宏,類似於函數
宏也可以導入使用
能重複使用的代碼片也可以使用導入
模板的繼承
基模板base.html
衍生模板index.html
注意head塊,因爲不是空的,所以使用super()獲取原來的內容。
使用Flask-Bootstrap
pip install flask-bootstrap
from flask.ext.bootstrap import Bootstrap #from flask_bootstrap import Bootstrap
#...
bootstrap = Bootstrap(app)
使用{% extends "bootstrap/base.html" %},然後可以定義各種塊。在定義styles和scripts塊時,需要特別注意,要使用{{super()}},否則基模板中的相應塊中的內容會丟失。
自定義錯誤頁面
採用模板繼承機制,簡化模板製作
鏈接
url_for('index') 得到 '/' 這樣引用:{{ url_for('index') }}
url_for('index',_external=True) 得到絕對地址
生成動態地址時,將動態部分作爲關鍵字參數傳入:url_for('user',name='john',_external=True)
動態地址還可以傳入額外參數:url_for('index',page=2) 生成:/?page=2
靜態文件
通常存放地static文件夾下面,可以有子文件夾
url_for('static',filename='css/style.css',_external=True)將返回.../static/css/style.css
Flask-Moment本地化日期時間
pip install flask-moment
from flask.ext.moment import Moment #from flask_moment import Moment
moment=Moment(app)
使用moment時,還需要引入jquery.js庫【該庫bootstrap已經引用】和moment.js庫
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
使用:
from datetime import datetime
#...
@app.route('/')
def index():
return render_template('index.html',current_time=datetime.utcnow())模板:
{{ moment(current).format('LLL') }} #L~LLLL代表不同複雜度,LTS
{{ moment(current).fromNow(refresh=True) }} #相對時間,a minute ago
Flask-Moment實現了moment.js中的format(),fromNow(),fromTime(),calendar(),valueOf(),unix()方法,具體查看文檔http://momentjs.com/docs/#/displaying/
設置語言:{{ moment.lang('es') }}
第四章 Web表單
request.form能獲取POST請求中提交的表單數據
或者使用Flask-WTF擴展
pip install flask-wtf
WTF需要設置一個密鑰,用於驗證表單數據真僞,設置方法:
app.config['SECRET_KEY'] = 'yourkey'
app.config字典用於存儲框架、擴展和程序本身的配置變量。
表單類
每個Web表單都繼承自Form類,這個類定義表單中的字段,每個字段都用對象表示,字段對象可附屬驗證函數。表單的字段都定義爲類變量,類變量的值是相應字段類型的對象。
定義表單類
其中,from flask.ext.wtf import Form可用from flask_wtf import Form替換
字段類型 | 說明 |
StringField | 文本字段 |
TextField | 多行文本 |
PasswordField | 密碼文本 |
HiddenField | 隱藏文本 |
DateField | 文本,值爲datetime.date格式 |
DatetimeField | 文本,值爲datetime.datetime格式 |
IntegerField | 文本,值爲整數 |
DecimalField | 文本,值爲decimal.Decimal |
FloatField | 文本,值爲浮點 |
BooleanField | 複選框,值爲True或False |
RadioField | 一線單選框 |
SelectField | 下拉列表 |
SelectMultipleField | 下接列表,可多選 |
FileField | 文件上傳 |
SubmitField | 提交按鈕 |
FormField | 嵌套表單 |
FieldList | 一組指定類型的字段 |
驗證函數 | 說明 |
電子郵件地址 | |
EqualTo | 比較兩個字段的值,常用於二次密碼驗證 |
IPAddress | IPv4地址 |
Length | 字符串上度 |
NumberRange | 數字範圍 |
Optional | 無輸入值時跳過其它驗證函數 |
Required | 不爲NULL |
Regexp | 使用正則表達式驗證 |
URL | URL |
AnyOf | 確保輸入值在可選值列表中 |
NoneOf | 確認輸入值不在可選值列表中 |
渲染表單
表單字段可調用,在模板中調用後會渲染成HTML。
例如視圖函數中將表單通過參數form傳入模板,則在模板中這樣渲染:
更好的方式是使用bootstrap:
{{ wtf.quick_form(form) }}
在視圖函數中處理表單
form.validate_on_submit方法在所有字段通過驗證函數,且點擊提交後,爲True,否則爲False.
重定向和用戶會話
最好不要將POST請求作爲瀏覽器發送的最後一個請求!
Post/重定向/Get模式
默認情況下,用戶會話session保存在客戶端cookie中,使用SECRET_KEY進行加密簽名,如果篡改,簽名就會失效
Flash消息
確認消息、警告消息、錯誤消息
Flask開放函數get_flashed_messages()給模板,用於模板獲取並渲染消息
{{ message }}
{% endfor %}
第五章 數據庫
對象關係映射——ORM——SQL
對象文檔映射——ODM——NoSQL
Flask-SQLAlchemy
pip install flask-sqlalchemy
數據庫引擎 | URI |
MySQL | mysql://username:password@hostname/database |
Postgres | postgresql://username:password@hostname/database |
SQLite | sqlite:////absolute/path/to/database |
SQLite | sqlite:///c:/absolute/path/to/database |
使用mysql時,pip install mysql-python 安裝MySQLdb模塊,然後手動新建數據庫database
初始化數據庫
定義模型
模型是一個類,模型一般對應一張表,類屬性對應數據庫表中的列
常用列類型及對應的Python類型
類型名 | Python類型 | 說明 |
Integer | int | 普通整數,32位 |
SmallInteger | int | 整數,16位 |
BigInteger | int/long | 不限制精度整數 |
Float | float | 浮點數 |
Numeric | decimal.Decimal | 定點數 |
String | str | 變長字符串 |
Text | str | 變長字符串,較長 |
Unicode | unicode | 變長Unicode字符串 |
UnicodeText | unicode | 變長Unicode字符串,較長 |
Boolean | bool | 布爾值 |
Date | datetime.date | 日期 |
Time | datetime.time | 時間 |
DateTime | datetime.datetime | 日期和時間 |
Interval | datetime.timedelta | 時間間隔 |
Enum | str | 一組字符串 |
PickleType | 任何Python對象 | 自動使用Pickle序列化 |
LargeBinary | str | 二進制文件 |
選項名 | 說明 |
primary_key | 主鍵 |
unique | 不允許出現重複值 |
index | 索引 |
nullable | 允許使用空值 |
default | 爲該列定認默認值 |
關係
一對多關係:Role(一),User(多)
關於一對多關係的進一步說明:在多的一方添加外鍵,外鍵指明瞭對應另外一方的哪一列。在一的一方的users屬性代表這個關係的面向對象視角,對Role類的實例(行)可以通過users屬性返回關聯列表,relationship第一個參數指明瞭關係的另一端,backref參數向User模型添加role屬性,從而定義反向關係。
選項名 | 說明 |
backref | 在關係的另一個模型中添加反向引用 |
primaryjoin | 明確指定兩個模型之間使用的聯結條件。只在模棱兩可的關係中指定 |
lazy | 指定如何加載相關記錄 |
select(首次訪問按需加載) | |
immediate(源對象加載後就加載) | |
joined(加載記錄,但使用聯結) | |
subquery(立即加載但使用子查詢) | |
noload(永不加載) | |
dynamic(不加載記錄,但提供加載記錄的查詢) | |
uselist | 若設爲False,不使用列表,而使用標量值 |
order_by | 指定排序方式 |
secondary | 指定多對多關係中關係表的名字 |
secondaryjoin | SQLAlchemy無法自行決定時,指定多對多關係中的二級聯結條件 |
一對一關係:使用一對多關係,但調用db.relationship()時,把uselist設爲False,把多變成一;
多對一關係:使用一對多關係,對調兩個表;或者把外鍵和db.relationship()都放到多這一側;
多對多關係:使用關係表
數據庫操作
根據模型類創建數據庫:db.create_all()
如果數據庫表已經存在於數據庫,那麼create_all將不會重新創建或更新這個表。如果修改數模型後,要將修改應用到數據庫,則只有先刪除再重新創建[會清空數據]:
db.create_all()
>>> 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)
>>> user_dong=User(username='dong',role_id=1)
>>> 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(user_dong)
### db.session.add_all([admin_role,mod_role,user_role,user_joho,user_susan,user_david,user_dong])
>>> db.session.commit()
### db.session.rollback() 回滾會話
>>> db.session.add(admin_role)
>>> db.session.commit()
>>> db.session.commit()
>>> Role.query.all()
[<Role u'Administrator'>, <Role u'User'>]
### 使用過濾器
>>> 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 \nFROM users \nWHERE %s = users.role_id'
常用的查詢過濾器:
過濾器 | 說明 |
filter() | 把過濾器添加到原查詢上,返回一個新查詢 |
filter_by() | 把等值過濾器添加到原查詢上,返回一個新查詢 |
limit() | 限制原查詢返回結果數量,返回一個新查詢 |
offset() | 偏移原查詢返回結果,返回一個新查詢 |
order_by() | 對原查詢結果進行排序,返回一個新查詢 |
group_by() | 對原查詢進行分組,返回一個新查詢 |
查詢執行函數
方法 | 說明 |
all() | 以列表形式返回所有結果 |
first() | 返回查詢第一個結果,若無,返回None |
first_or_404() | 返回查詢第一個結果,若無,中止,返回404 |
get() | 返回指定主鍵對應的行,若無,返回None |
get_or_404() | 返回指定主鍵對應的行,若無,中止,返回404 |
count() | 返回查詢結果數量 |
paginate() | 返回Paginate對象,包含指定範圍的結果 |
一對多關係的查詢
>>> users=user_role.users
>>> list(users)
[<User u'susan'>, <User u'david'>]
>>> users[0].role
<Role u'User'>
>>> user_role.users.order_by(User.username).all()
[<User u'david'>, <User u'susan'>]
>>> user_role.users.count()
2L
在視圖函數中操作數據庫
注意,這裏並沒有db.session.commit(),
因爲app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
集成Python shell
使用shell時,每次需要導入app,db,User,Role,爲簡化操作,可以爲shell添加上下文:
數據庫遷移
不丟失數據的更新數據庫
pip install flask-migrate
from flask.ext.migrate import Migrate,MigrateCommand
#from flask_migrate import Migrate,MigrateCommand
#...
migrate=Migrate(app,db)
manager.add_command('db',MigrateCommand)
維護數據庫前,先創建遷移倉庫:
python hello.py db init
會生成migrations目錄
upgrade()把改動應用到數據庫
downgrade()將改動刪除
自動創建的遷移不一定總是正確,需要認真檢查!!
python hello.py db migrate -m "inital migration"
檢查並修正好腳本後(位於migtarions/versions目錄)後,使用db upgrade遷移:
python hello.py db upgrade
第六章 電子郵件
pip install flask-mail
MAIL_SERVER:localhost
MAIL_PORT:25
MAIL_USE_TLS:False
MAIL_USE_SSL:False
MAIL_USERNAME:None
MAIL_PASSWORD:None初始化:
from flask_mail import Mailapp.config['MAIL_SERVER']='smtp.126.com'
app.config['MAIL_PORT']=465
app.config['MAIL_USE_SSL']=True
app.config['MAIL_USERNAME']=os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD']=os.environ.get('MAIL_PASSWORD')
#export MAIL_USERNAME='mymailname'
#export MAIL_PASSWORD='mymailpassword'mail=Mail(app)
命令行測試發送郵件
>>> from hello import mail
>>> msg=Message('test',sender='[email protected]',recipients=['[email protected]'])
### 奇怪的是隻能自己給自己發,給別人發會報535錯誤,被當成垃圾郵件啦?
### msg.body='text body'
### msg.html='<h1>test mail</h1>
>>> with app.app_context():
... mail.send(msg)
在程序中添加發送郵件功能
爲配合,需要templates目錄下新建mail子目錄,裏面存放郵件的模板。模板示例:
User <b>{{ user.username }}</b> has joined.
異步發送郵件
第七章 大型程序的結構
項目結構:
配置選項
使用配置類。公共配置放於Config類,開發,測試,生產分別使用繼承自Config類的類,最後生成一個config字典。調用字典時:config['DevelopmentConfig']()生成所有配置。
配置條目相應要做修改:
app.config['SECRET_KEY'] = os.environ.get('Key') or 'hard to guess string'
改爲:
SECRET_KEY= os.environ.get('Key') or 'hard to guess string'
如果環境中有,則使用環境中的值,環境中無,則使用配置中的值
程序包
使用程序工廠函數
延遲創建程序實例,創建時使用工廠函數。工廠函數在app包的構造文件中定義__init__.py
在藍本中實現程序功能
藍本中定義的路由處於休眠狀態,只有藍本註冊到程序上後,路由才成爲程序的一部分。
app/main/__init__.py創建藍本
註冊藍本到程序,app/__init__.py中
藍本中的錯誤處理程序:app/main/errors.py
藍本中的路由:app/main/views.py
Flask會爲藍本全部端點加上命名空間,空間名就是藍本名,所以這裏的url_for函數參數爲main.index可簡寫爲.index。同一藍本中重定向可以簡寫,但是跨藍本的重定向必須使用有命名空間的端點名。
啓動腳本
manage.py
需求文件
requirements.txt用於記錄所有依賴包及精確的版本號
生成:pip freeze >requirements.txt
使用:pip install -r requirements.txt
單元測試
test/test_basics.py
setUp()和tearDown()分別在測試前後運行,名字以test_開頭的函數都作爲測試執行。
test/__init__.py可爲空,因爲unittest會掃描所有模塊並查找測試。
爲方便測試,可以添加啓動命令:
測試:python manager.py test
創建數據庫
python manager.py db upgrade