《Flask Web開發:基於Python的Web應用開發實戰》學習筆記(一)

轉載於: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 hello import app
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

from flask import redirect
...
return redirect('http://pdf.us/')

abort函數,用於處理錯誤,但不會將控制權還給調用它的函數,而是拋出異常將控制權交給Web服務器

from flask import abort
...
user=load_user(id)
if not user:
abort(404)
return '<h1>Hello,%s</h1>' % user
Flask擴展

Flask-Script擴展,支持命令行選項,安裝:pip install flask-script

在程序中註冊擴展的方法

from flask.ext.script import Manager    #最新的寫法是from flask_script import Manager
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庫

{% block scripts %}
{{ 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 一組指定類型的字段
驗證函數 說明
Email 電子郵件地址
EqualTo 比較兩個字段的值,常用於二次密碼驗證
IPAddress IPv4地址
Length 字符串上度
NumberRange 數字範圍
Optional 無輸入值時跳過其它驗證函數
Required 不爲NULL
Regexp 使用正則表達式驗證
URL URL
AnyOf 確保輸入值在可選值列表中
NoneOf 確認輸入值不在可選值列表中
渲染表單

表單字段可調用,在模板中調用後會渲染成HTML。

例如視圖函數中將表單通過參數form傳入模板,則在模板中這樣渲染:

更好的方式是使用bootstrap:

{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
在視圖函數中處理表單

form.validate_on_submit方法在所有字段通過驗證函數,且點擊提交後,爲True,否則爲False.

重定向和用戶會話

最好不要將POST請求作爲瀏覽器發送的最後一個請求!

Post/重定向/Get模式

默認情況下,用戶會話session保存在客戶端cookie中,使用SECRET_KEY進行加密簽名,如果篡改,簽名就會失效

Flash消息

確認消息、警告消息、錯誤消息

Flask開放函數get_flashed_messages()給模板,用於模板獲取並渲染消息

{% for message in 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.drop_all()
db.create_all()
插入行
>>> from hello import Role,User,db
>>> 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()   回滾會話
因爲id在提交前不會生成,主鍵由SQLAlchemy自己處理,不需要設置
修改行
>>> admin_role.name='Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
刪除行
>>> db.session.delete(mod_role)
>>> 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

初始化(注意,需要傳入db)
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

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 flask_mail import Message
>>> 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'

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章