準備Flask環(huán)境
- 具備python環(huán)境
- 安裝virtualenv
yum install python-virtualenv //centos 安裝
apt-install python-virtualenv //debian 安裝
easy_install virtualenv //easy_install 安裝
- 構(gòu)建python虛擬環(huán)境
virtualenv [-p /usr/bin/python3 ] env //指定env為python環(huán)境路徑; -p 指定python路徑
source ./env/bin/activate //激活環(huán)境
deactivate //關(guān)閉環(huán)境
- 安裝flask
pip3 install flask
Flask基本結(jié)構(gòu)
Hello World
from flask import Flask
from flask import url_for
app = Flask(__name__)
#使用裝飾器指定'/'到函數(shù)'index'的路由
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
#通過URL為函數(shù)傳遞參數(shù)
@app.route('/login/<username>/')
def login(username):
return '<h1 style="color:blue;">Hello, %s</h1>' % username
#使用url_for()函數(shù),通過視頻函數(shù)名稱獲取URL
@app.route('/url_test')
def get_url():
index_url = url_for('index')
login_url = url_for('login', username='Mark')
return f'<a href={index_url}> Home Page </a>' + '</br>' + f'<a href={login_url}>Login</a>'
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
#也可以在shell中使用flask run 命令, 該命令自動檢測同目錄下的flask代碼文件, 并加載app對象
#flask routes #可以查看URL和視圖函數(shù)的映射關(guān)系
調(diào)試器與重載器
調(diào)試器
export FLASK_DEBUG=1 #開啟調(diào)試器
重載器(reloader)
監(jiān)視文件變化 , 自動加載新的代碼.
Flask Shell
在程序文件目錄執(zhí)行flask shell
, 會啟動一個python shell, 并自動加載目錄下的flask代碼文件.
Flask 目錄結(jié)構(gòu)
hello/ #項目目錄
- templates/ #模板文件
- static/ #靜態(tài)文件, 存放CSS / JavaScript
- app.py #項目文件
請求-響應(yīng)循環(huán)
請求/應(yīng)答上下文
Request對象
該對象封裝了從客戶端發(fā)來的請求報文, 我們可以從它獲取請求報文的所有數(shù)據(jù)
from flask import request
導(dǎo)入訪問請求對象(request) , request
是一個線程中的全局可訪問對象, 該對象包含了客戶端請求報文
(客戶端瀏覽器發(fā)給web服務(wù)器的http請求包)中的所有內(nèi)容.
from flask import request
@app.route('/browser')
def browser():
#從http請求包中獲取客戶端瀏覽器名稱
return '<h1>Your browser is %s' % \ request.user_agent.browser
#request.headers.get('User-Agent') 也可以獲取agent信息
#####
#當前請求的方法, "GET", "POST", "PUT"...
request.method
#請求連接中URL后邊的參數(shù), 如: www.abc.com/wx?p1=123&p2=456
request.args
flask.request 對象的屬性參數(shù)使用Dash查閱.
設(shè)置HTTP監(jiān)聽方法
可以在@app.route()
裝飾器方法中指定監(jiān)聽的請求方法.
@app.route('/login/', methods=['GET', 'POST']
def login():
pass
URL參數(shù)類型轉(zhuǎn)換
@app.route('/goback/<int:year>')
def go_back(year):
print(f'type of year is: {type(year)}')
return '<p>Welcome to %d!</p>' % (2018 - year)
請求勾子
勾子使用裝飾器實現(xiàn)
-
before_first_request
:注冊一個函數(shù),在處理第一個請求之前運行。- 常用于做一個初始化操作, 如: 創(chuàng)建數(shù)據(jù)庫表, 添加管理員用戶,etc.
-
before_request
:注冊一個函數(shù),在每次請求之前運行。- 常用于記錄一些統(tǒng)計數(shù)據(jù),如果用戶在線時間.
-
after_request
:注冊一個函數(shù),如果沒有未處理的異常拋出,在每次請求之后運行。- 用于善后操作, 例如用戶修改的數(shù)據(jù), 最后提交更改到數(shù)據(jù)庫
-
after_this_request
: 完成一個視圖函數(shù)請求后, 調(diào)用. -
teardown_request
:注冊一個函數(shù),即使有未處理的異常拋出,也在每次請求之后運行。
例:
@app.before_first_request
def before_request():
print('this message output before request')
響應(yīng)
**方案一: 視圖函數(shù)可以返回三個變量: **
- http的應(yīng)答報文的body, 類型為字符串.
<h1>Hello world</h1>
- 應(yīng)答報文的狀態(tài)碼, 類型為整型.
200
(可選參數(shù)) - 應(yīng)答報文header字段值,類型為字典.
{'server':'XXX', 'Date':'Wed, 05 Dec 2018'}
(可選參數(shù))
示例:
@app.route('/')
def index():
return '<h1>Hello World!</h1>',200,{'server':'markict'}
方案二: 通過返回response
對象來返回body,狀態(tài)代碼,頭部字段
make_response()
函數(shù)可以接受1個,2個或3個參數(shù)(對應(yīng)視圖函數(shù)的返回值),并返回一個response
對象. 我們也可以對response
對象做進一步設(shè)置(如:設(shè)置cookie), 然后由視圖函數(shù)進行返回.
from flask import Flask
from flask import request
from flask import make_response
@app.route('/browser')
def browser():
#構(gòu)建body的html, 通過request獲取瀏覽器類型
body = '<h1>Your browser is %s' % \
request.user_agent.browser
#設(shè)置狀態(tài)代碼
state_code = 200
#應(yīng)答報文頭部
header = {'server':'Markict', 'Date':'Fri 05 Dec 2018'}
#使用make_reponse()函數(shù)構(gòu)建response對象
response = make_response(body, state_code, header)
#為response對象設(shè)置cookie
response.set_cookie('answer', '42')
#返回response
return response
response
對象常用的屬性:
對象/方法名稱 | 描述 |
---|---|
response.headers | HTTP響應(yīng)報文首部 |
response.status_code | HTTP響應(yīng)報文狀態(tài)代碼 |
response.status | HTTP響應(yīng)報文狀態(tài), 文本表示 |
response.mimetype | HTTP響應(yīng)報文的內(nèi)容類型 |
response.set_cookie() | 用于為HTTP響應(yīng)報文設(shè)置Cookie |
響應(yīng)-重定向
重定向使用狀態(tài)代碼 302, 和報文頭部中的Location
字段指定重定向的目標站點.
我們可以使用視圖函數(shù)返回三個變量的形式, 也可以使用flask中的redirect()
函數(shù)來簡化操作.
from flask import redirect
@app.route('/')
def index():
#返回三個變量,來指定重定向的代碼和目標站點.
#return 'hello', 302, \
{'Location':'http://www.baidu.com'}
#使用redirect()函數(shù)進行重定向.
return redirect('http://www.baidu.com')
響應(yīng)-錯誤
使用abort
函數(shù)進行錯誤響應(yīng),把控制權(quán)交給browser.
from flask import abort
@app.route('/login/<user>')
def login(user):
#如果用戶名不是純字母,那么進行404錯誤響應(yīng)
if not user.isalpha():
abort(404)
return '<h1>hello, %s</h1>' % user
響應(yīng)格式
響應(yīng)格式是指定返回給客戶端(瀏覽器)的HTTP報文頭部中標明的數(shù)據(jù)類型(MIME Type), 常用的有:
text/plain, text/html, application/xml, application/json
可以通過response對象的mimetype
屬性指定
from flask import Flask, make_response, json
@app.route('/foo')
def foo():
data = {'name':'mark', 'gender':'male'}
r = make_response(json.dumps(data))
r.mimetype = 'application/json'
return response
Cookie
HTTP是無狀態(tài)(stateless)協(xié)議。也就是說,在一次請求響應(yīng)結(jié)束 后,服務(wù)器不會留下任何關(guān)于對方狀態(tài)的信息。為了解決這類問題,就有了 Cookie技術(shù)。Cookie技術(shù)通過在請求和響應(yīng)報文中添加Cookie數(shù)據(jù)來保 存客戶端的狀態(tài)信息。
Cookie由Server端生成并維護, 發(fā)給client端, client瀏覽器會保留一段時間, 并在該時間內(nèi), 當向server發(fā)起請求時攜帶Cookie,以標明身份.
response.set_cookie()
用于為響應(yīng)對象設(shè)置Cookie.
set_cookie()方法參數(shù):
屬性 | 描述 |
---|---|
key | Cookie的鍵名 |
value | Cookie值 |
max_age | cookie保存時間(秒), 關(guān)閉瀏覽器也會過期 |
expires | 具體過期時間, 一個datetime對象 |
domain | 設(shè)置Cookie可用域 |
secure | 如果設(shè)置為True,僅HTTPS可用 |
httponly | 禁止客戶端的javascript讀取Cookie |
from flask import Flask, make_response
...
@app.route('/set/<name>')
def set_cookie(name):
response = make_response(redirect(url_for('hello')))
response.set_cookie('name',name)
return response
在Cookie過期之前, 客戶端發(fā)給服務(wù)器的請求將攜帶Cookie.
Session
Cookie是明文傳輸,存在安全隱患, Session的方式是通過在server端預(yù)先設(shè)置一個secret_key, 然后把Cookie用這個key進行加密簽名,并發(fā)送給client. client只可以使用該session, 并不能修改它的值.
from flask import Flask, session, request, make_response, redirect
@app.route('/login/<name>')
def login(name):
app.secret_key = 'passwd123'
session['name'] = 'cookie_value' #設(shè)置Cookie
response = make_respones(f'hello Mr.{name}')
return response
@app.route('/greet')
def greet():
if 'cookie_value' in session: #驗證Cookie
return "<h1>Welcome</h1>"
else:
flask.abort()
@app.route('/logout')
def logout():
if "cookie_value" in session:
session.pop('name') #登出就是把設(shè)置的Cookie從session彈出
return redirect(url_for('index'))
默認情況下,session cookie會在用戶關(guān)閉瀏覽器時刪除。通過將 session.permanent屬性設(shè)為True可以將session的有效期延長為 Flask.permanent_session_lifetime屬性值對應(yīng)的datetime.timedelta對象,也 可通過配置變量PERMANENT_SESSION_LIFETIME設(shè)置,默認為31 天。
g
全局上下文
上下文件全局變量g
可以在任何視圖函數(shù)中使用, 為了方便 我們可以使用before_reequest鉤子函數(shù), 為g
設(shè)置相應(yīng)的值, 然后在各個視圖函數(shù)中使用.
from flask import g
@app.before_request
def get_name():
g.name = request.args.get('name') #也可以像使用字典一個使用`g`
#在其它視圖函數(shù)中可以直接引用g.name
返回上一頁面
通過request
對象中的referrer
屬性獲取上一頁面的URL(相對URL)
@app.route('/profile')
def profile():
#if no Cookie, return to login page
return redirect(request.referrer)
#request.referrer 返回的是絕對URL路徑
模板
Jinja2模板: 最簡單的模板就是一個包含響應(yīng)文本的文件, 在模板中可以使用{{variable}}
形式的變量進行占位. 默認flask程序在文件夾中的templates目錄中查找模板.
三種基本定界符:
-
{% ... %}
語句, 如: if, for, set, ... -
{{ ... }}
變量引用 -
{# ... #}
注釋
Jinja2模板引擎: flask程序使用jinjia2模板引擎渲染模板, 使用相應(yīng)的變量值替換模板中的占位符.
基本配置形式
模板1
...
<body>
<h1>Hello World</h1>
</body>
...
模板2
...
<body>
<h1>Hello, {{name}}</h1>
</body>
...
代碼
from flask import Flask
from flask import render_template
app = Flask(__name__)
@app.route('/')
def index():
#無變量模板
return render_template('index.html')
@app.route('/login/<user>/')
def login(user):
#可以使用Key=Value形式傳遞參數(shù)
#return render_template('user.html', name=user)
#也可以使用字典轉(zhuǎn)換為關(guān)鍵字參數(shù)的形式傳遞變量
return render_template('user.html', **{'name':user})
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
變量
變量類型
Jinjia引擎支持多種類型的變量, 如: list dict object:
<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>
模板內(nèi)自定義變量:
除了渲染時傳入變量, 我們可以在模板內(nèi)定義變量: {% set VarName = ... %}
{# 使用set標簽定義變量 #}
{% set navigation = [('/', 'Home'), ('/about', 'About')] %}
{# 引用定義的變量 #}
{{navigation[0][1}}
{# 將一部分模板數(shù)據(jù)定義為變量 #}
{% set navigation %}
<li><a href="/">Home</a>
<li><a href="/about">About</a>
{% endset %}
{# 引用定義的變量 #}
{{navigation}}
內(nèi)置上下文模版變量
變量 | 描述 |
---|---|
config | 當前配置對象 |
request | 當前請求對象 |
session | 當前會話對象 |
g | 請求綁定的全局變量 |
自定義上下文
如果多個模板使用同一變量, 那么在不同視圖函數(shù)中進行模板渲染時重復(fù)傳入相同的變量比較麻煩, 我們可以使用app.context_processor
裝飾器來注冊模板變量上下文處理函數(shù), 在被裝飾的函數(shù)中返回的變量,可以在所有模板中被使用.
@app.context_processor
def inject_foo():
foo = "I am foo"
return = dict(Var1=foo) #等同于 return {'Var1': foo}
@app.route('/watchlist')
def watchlist():
return render_template('watchlist.html')
#直接渲染, 無需傳入變量, 因為該模板中用到的變量, 在上下文外理函數(shù)(inject_foo)中己經(jīng)注冊.
模板函數(shù)
內(nèi)置全局函數(shù)
函數(shù) | 描述 |
---|---|
range([start,] stop [,step]) | 和python中的range相同用法 |
lipsum(n=5, html=True, min=20, max=100) | 生成隨機文本, 默認生成5個paragraphs(段落), 每段20-100個詞. |
dict(**items) | 和python中的dict用法相同 |
url_for() | Flask在模版中內(nèi)置的函數(shù)(非jinja2標準) |
get_flashed_messages() | Flask在模版中內(nèi)置的函數(shù)(非jinja2標準), 用于獲取flash消息 |
自定義全局模版函數(shù)
使用app.template_global
裝飾器將函數(shù)注冊為模板全局函數(shù).
@app.template_global()
def bar():
return "I am bar."
#可以在裝飾器傳入可選參數(shù)name='FuncName', 為自定義函數(shù)設(shè)置在模版中調(diào)用的名字.
@app.template_global(name='mybar')
def bar():
return "<p>I am a bar.</p>"
#在模板中調(diào)用時應(yīng)該使用mybar(), 而不是bar()
{{ mybar() }}
過濾器
也可以使用過濾器對變量進行修改:
過濾器名稱 | 說明 |
---|---|
safe | 渲染值時不轉(zhuǎn)義 |
capitalize | 首字轉(zhuǎn)大寫,其它轉(zhuǎn)小寫 |
lower | 把變量轉(zhuǎn)小寫 |
upper | 把變量轉(zhuǎn)大寫 |
title | 把變量中的每個單詞首字母轉(zhuǎn)大寫 |
trim | 把變量首尾空格去掉 |
striptags | 渲染之前把變量中的所有HTML標簽刪掉 |
出于安全考慮, Jinja2 會 轉(zhuǎn)義 所有變量。例 如, 如果一個變量的值為 \<h1>Hello</h1>
, Jinja2 會將其渲染成 <h1>Hello</ h1>
,瀏覽器能顯示這個 h1 元素,但不會進行解釋。很多情況下需要顯示變量中存儲 的 HTML 代碼,這時就可使用 safe 過濾器。
代碼示例
<body>
<h1>Hello, {{name|capitalize}}</h1>
<p>This is a value from a dictionary:{{d1['name']}} </p>
<p>This is a value from a list: {{l1[1]|upper}}</p>
</body>
如果需要對一段內(nèi)容進行過濾, 可以使用{% filter FILTER%} ... {% endfilter %}進行過濾.
{% filter upper %}
<p> Hello, I'm Liang </p>
{% endfilter %}
自定義過濾器
可以自定義函數(shù),然后使用@app.template_filter()
進行裝飾.
from flask import Markup
@app.template_filter()
def musical(s):
return s + Markup(' ♫')
控制結(jié)構(gòu)
判斷
{% if name %}
<h1>Hello, {{name|capitalize}}</h1>
{% else %}
<h1>Hello, Stranger</h1>
{% endif %}
循環(huán)
<ul>
{% for i in list1 %}
<li>{{i}}</li>
{% endfor %}
</ul>
局部模版
多個模版共有的內(nèi)容可以定義為部局模版, 然后通過include
引入 .
{# 為了和普通模板區(qū)分, 局部模版名稱前加單下劃線#}
{% include '_banner.html %}
宏
將宏定義到一個單獨文件. 該示例中文件名'macro.html'
{% macro render_comment(comments) %}
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
{% endmacro %}
在模板中import宏文件, 然后使用宏
<body>
{% import 'macro.html' as macros %}
<div>
{{ macros.render_comment(comment_list)}}
</div>
</body>
測試程序:
from flask import Flask
from flask import render_template
l1 = ['alice','cindy', 'bob']
@app.route('/about/')
def about():
return render_template('about.html', comment_list=l1)
模版繼承
可以把網(wǎng)頁的共同部分定義為基礎(chǔ)模板, 其它子模板繼承基模板. 例如: 頁面上的導(dǎo)航欄, 頁腳入在基模板中, 避免重復(fù)代碼.
子模板繼承基模板后, 會自動包含基模板內(nèi)容和結(jié)構(gòu), 為了讓子模板方便地覆蓋或插入內(nèi)容到基模板中,我們需要在基模 板中定義塊(block),在子模板中可以通過定義同名的塊來執(zhí)行替換或追加操作。
{% block body %}
...
{% endblock body %}
基模板示例:
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<meta charset="UTF-8">
<title> {% block title %} Template_Title {% endblock %}</title>
{% block styles %} {% endblock %}
{% endblock %}
</head>
<body>
<nav>
<ul><li><a href="{{ url_for('index') }}">Home</a> </li></ul>
</nav>
<main>
{% block content %} {% endblock %}
</main>
<footer>
{% block footer %}
...
{% endblock %}
</footer>
{% block scripts %} {% endblock %}
</body>
</html>
子模板示例1:
{# 子模板第一行必須引入基模版 #}
{% extends 'base.html' %}
{# 替換基模板中塊的內(nèi)容 #}
{% block content %}
<h1> {{ user.username }}'s Watchlist</h1>
<ul>
{% for movie in movies %}
<li> {{ movie.name }} --- {{ movie.year }}</li>
{% endfor %}
</ul>
{% endblock %}
子模板示例2:
{% extends 'base.html' %}
{% block content %}
<h1>Home Page</h1>
<ul>
{{ lipsum() }}
</ul>
{% endblock %}
也可以向基模板中追加內(nèi)容:
{% block styles %}
{# 使用super(),表示基模板中該block的內(nèi)容, 用于追加的操作 #}
{{ super() }}
<style>
.foo {
color: red;
}
</style>
{% endblock %}
靜態(tài)文件
靜態(tài)文件指的是內(nèi)容不需要動態(tài)生成的文件, 例如: 圖片/CSS文件/JavaScript.
在 Flask 中,我們需要創(chuàng)建一個 static 文件夾來保存靜態(tài)文件,它應(yīng)該和程序模塊、templates 文件夾在同一目錄層級,所以我們在項目根目錄創(chuàng)建它.
在 HTML 模板文件里,引入這些靜態(tài)文件需要給出資源所在的 URL。為了更加靈活,這些文件的 URL 可以通過 Flask 提供的 url_for()
函數(shù)來生成.
圖片
例如, 插入一個圖片:
<img src="{{url_for('static', filename='avatar.jpg')}}" width="50">
上例中url_for()
函數(shù)返回結(jié)果是'/static/foo.jpg'
CSS
例如, 插入一個CSS:
{% block styles %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css')}}">
{% endblock %}
Favicon
網(wǎng)站的icon, 收藏夾頭像. 一般為.ico
格式. 存放在static目錄下, 名為favicon.ico或favicon.png.
可以通過在模板頁面的<head>
部分添加一個link元素來實現(xiàn).
<head>
<link rel="icon" type="image/x-icon" href="{{ url_for("static", filename="favicon.ico")}}">
</head>
使用框架
手動編寫CSS或是JS比較麻煩, 可以使用框架. 使用框架有兩種方式:
-
下載框架庫, 并分類放置于static目錄
{% block styles %} <link rel="stylesheet" href="{{url_for('static',filename='css/bootstrap.min.css') }}"> {% endblock %}
-
直接引用框架CDN的URL
{% block styles %} <link rel="stylesheet" > {% endblock %}
使用宏加載框架, 可以避免重復(fù)編寫引用框架庫的代碼.
{% macro static_file(type, filename_or_url, local=True) %}
{% if local %}
{% set filenaem_or_url = url_for("static", filename=filename_or_url) %}
{% endif %}
{% if type == 'css' %}
<link rel="stylesheet" href="{{ filename_or_url }}" type="text/css">
{% elif type == 'js' %}
<script type="text/javascript" src="{{ filename_or_url }}"></script>
{% elif type == 'icon' %}
<link rel="icon" href="{{ filename_or_url }}">
{% endif %}
{% endmacro %}
在模板中導(dǎo)入宏以后, 可以調(diào)用宏, 傳入相關(guān)參數(shù) ,直接得到引用框架的html代碼
<head>
{% import 'macro.html' as macros %}
{{ macros.static_file("css", "css/bootstrap.min.css") }}
</head>
自定義錯誤頁
定義一個錯誤頁模板
{% extends 'base.html' %}
{# 替換基模板中的塊 #}
{% block title %} 404 - page not found {% endblock %}
{% block content %}
<h1>Page Not Found</h1>
<p>You are lost...</p>
{% endblock %}
使用@app.errorhandler()
裝飾器注冊自定義的錯誤處理函數(shù)
Web表單
簡介
表單的標簽為<form> ... </form>
示例:
<form method="get">
<label for="username">Username</label><br>
<input type="text" name="useranme" placeholder="Mark"><br>
<label for="password">Password</label><br>
<input type="password" name="password" placeholder="Zhang"><br>
<input id="remember" name="remember" type="checkbox" checked>
<label for="remember"><small>Remember</small></label><br>
<input type="submit" name="submit" value="Log in">
</form>
Flask-WTF處理表單
WTForms支持在Python中使用類定義表單,然后直接通過類定義生 成對應(yīng)的HTML代碼.
擴展Flask-WTF集成了WTForms,使用它可以在Flask中更方便地使 用WTForms。Flask-WTF將表單數(shù)據(jù)解析、CSRF保護、文件上傳等功能 與Flask集成,另外還附加了reCAPTCHA支持。
安裝flask-wtf
使用pip安裝flask-wtf包
pip3 install flask-wtf
Flask-WTF默認為每個表單啟用CSRF保護, 默認情況下,F(xiàn)lask-WTF使用程序密鑰來對CSRF令牌 進行簽名,所以我們需要為程序設(shè)置密鑰:
app.secret_key = 'secret string'
定義WTForms表單類
一個表單(form
)由若干個輸入字段(輸入框,復(fù)選框,按鍵...)組成.
表單由Python的類表示, 這個類是繼承了從WTForms導(dǎo)入的Form類.
表單中的輸入字段由表單類的類屬性表示.
示例
#導(dǎo)入表單基類Form, 以及表單中各輸入組件類
from flask_wtf import Form, StringField, PasswordField, BooleanField, SubmitField
#導(dǎo)入表單輸入組件的驗證器
from wtforms.validators import DataRequired, Length
#定義一個自己的表單類, 繼承了表單基類
class LoginForm(Form):
#在自定義中添加輸入組件, 并為輸入組件設(shè)置HTML屬性
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
remember = BooleanField('Remember me')
submit = SubmitField('Login')
常用的表單組件
字段類 | 描述 | 對應(yīng)的HTML表示 |
---|---|---|
BooleanField | checkbox, 值會被處理為True/False | <input type="checkbox"/> |
DateField DateTimeField |
文本字段,值處理為datetime.date 文本字段,值處理為datetime.datetime |
<input type="text"/> <input type="text"/><br />
|
FileField | 文件上傳字段 | <input type="file"/> |
FloatField | 浮點數(shù), 值被處理為浮點數(shù) | <input type="text"/><br /> |
IntegerField | 整數(shù), 值被處理為整數(shù) | <input type="text"/><br /> |
RadioField | 單選按鍵 | <input type="radio"/> |
SelectField | 下拉列表 | <select><option>...</option></select> |
SelectMultipleField | 多選下拉列表 | <select multiple><option>...</option></select> |
SubmitField | 提交按鈕 | <input type="summit"/> |
StringField | 文本字段 | <input type="text"/> |
HiddenField | 隱藏文本字段 | <input type="hidden"> |
PasswordField | 密碼文本 | <input type="password"/> |
TextAreaField | 多選文本字段 | <textarea> ... </textarea> |
實例化表單中的字段類的常用參數(shù)
參數(shù) | 描述 |
---|---|
label |
<label> 字段的值, 渲染后顯示在輸入字段前面的標簽文字 |
render_kw | 一個字典, 用設(shè)置表單字段的額外屬性(字段類不支持所有HTML單表元素屬性, 需要通過這個參數(shù)字典設(shè)置) |
validators | 一個列表, 包含一系列表單字段的驗證器 |
default | 字符串, 用來設(shè)置字段的默認值 |
輸出HTML代碼
#實例化自定義表單類
>>>myform = LoginForm()
#輸入自定義表單類中的HTML元素代碼
>>>myform.username()
'<input id="username" name="username" required type="text" value="">'
#輸入自定義表單類中的HTML元素代碼
>>>myform.submit()
'<input id="submit" name="submit" type="submit" value="Login">'
#生成表單中元素對應(yīng)的標簽代碼(相應(yīng)元素前邊的標簽名稱)
>>>myform.username.label()
'<label for="username">Username</label>'
為表單元素設(shè)置額外屬性
為表單元素設(shè)置例如: class, placeholder, style等屬性
使用render_kw設(shè)置
在定義自定義類時, 為類中的元素屬性傳入render_kw參數(shù) (字典型)
class LoginForm(Form):
username = StringField('Username', validators=[DataRequired()], render_kw={'placeholder':'YourName'})
password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)], render_kw={'placeholder':'PASSWD'})
在調(diào)用元素方法時通過傳入?yún)?shù)設(shè)置
在調(diào)用自定義類中的元素屬性時, 為其傳入?yún)?shù)(關(guān)鍵字參數(shù))
#通過傳入關(guān)鍵字參數(shù), 為生成的HTML元素代碼添加屬性
>>>myform.username(class_="test", placeholder="YourName")
'<input class="test" id="username" name="username" placeholder="YourName" required type="text" value="">'
<div style="color:green"> class是python的關(guān)鍵字, 所以輸入?yún)?shù)時使用class_
代替class, 渲染后會自動更正</div>
在模板中渲染表單
#自定義表單類
class LoginForm(Form):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
remember = BooleanField('Remember me')
submit = SubmitField('Login')
#定義視圖函數(shù)
@app.route('/login')
def login():
#實例化表單類
mf = LoginForm()
#將實例化的表單類對象傳給模板,進行渲染
return render_template('login.html', form=mf)
模板文件
{% extends "base.html" %}
{% block content %}
<form method="post">
{# 在模板中調(diào)用元素方法時無需加括號,直接提供方法名即可 #}
{# 如果需要添加額外HTML屬性,也可以加括號以添加屬性 #}
{{ form.username.label }} {{ form.username }} <br/>
{{ form.password.label }} {{ form.password }} <br/>
{{ form.remember }} {{ form.remember.label }} <br/>
{{ form.submit }}
</form>
{% endblock %}
可以通過render_kw
參數(shù), 在模板文件中為HTML元素添加額外屬性, 例如<class>
, 通過bootstrap框架為表單元素設(shè)置樣式
{% extends "base.html" %}
{# 通過替換基模板中的styles block,為本模板設(shè)置bootstrap框架 #}
{% block styles %}
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap.min.css') }}"/>
{% endblock %}
{% block content %}
<form method="post">
{{ form.csrf_token }} <!---渲染CSRF令牌隱藏字段--->
{# 為元素設(shè)置class屬性, 以便應(yīng)用style框架 #}
{{ form.username.label }} {{ form.username(class='form-control') }} <br/>
{{ form.password.label }} {{ form.password(class='form-control') }} <br/>
{{ form.remember }} {{ form.remember.label }} <br/>
{{ form.submit(class='btn btn-primary') }}
</form>
{% endblock %}
處理表單數(shù)據(jù)
在表單中類型為"submit"的元素被點擊時, 表單將被提交, 表單提交主要由三個屬性控制:
屬性 | 默認值 | 說明 |
---|---|---|
action | 當前URL,即頁面對應(yīng)的URL | 表單提交時發(fā)送請求的URL |
method | GET | 提交表單的方法, 常用GET和POST |
enctype | application/x-www-form-urlencoded | 表單數(shù)據(jù)編碼方式 |
GET方法僅支持3000個字符以內(nèi)的提交, 并且暴露在URL中, 相比來說POST更安全.
表單驗證
客戶端驗證
使用JS或HTML驗證.
<!---HTML驗證--->
<input type="text" name="username" required/>
服務(wù)端驗證----WTForms驗證機制
WTForms的驗證方式是: 在實例化表單類時傳入表單數(shù)據(jù), 然后對類實例調(diào)用validate()方法, 這將會對提交換數(shù)據(jù)逐個調(diào)用元素字段實例化時定義的驗證器, 驗證結(jié)果以布爾值返回.
#使用wtforms, 提交表單時, 表單類實例不會自動接收提交的數(shù)據(jù), 需要通過flask.request.form手工傳入提交的數(shù)據(jù)
from wtforms import Form, StringField, PasswordField, BooleanField
>>> from wtforms.validators import DataRequired, Length
>>> class LoginForm(Form):
... username = StringField('Username', validators=[DataRequired()])
... password = PasswordField('Password', validators=[DataRequired() , Length(8, 128)])
#實例化表單類時,傳入各字段待驗證數(shù)據(jù)
>>> form = LoginForm(username='', password='123')
>>> form.data # 表單數(shù)據(jù)字典 {'username': '', 'password': '123'}
>>> form.validate() False
>>> form.errors # 錯誤消息字典 {'username': [u'This field is required.'], 'password': [u'Field must be at least 6 characters long.']}
<div style="color:red"><b>因為我們的表單使用POST方法提交,如果單純使用WTForms,我 們在實例化表單類時需要首先把request.form
傳入表單類,而使用flask_wtf時,表單類繼承的FlaskForm基類默認會從request.form獲取表單數(shù) 據(jù),所以不需要手動傳入。</b></div>
#為了方便,一定要用flask_wtf
from flask_wtf import Form, StringField, PasswordField, BooleanField
>>> from wtforms.validators import DataRequired, Length
>>> class LoginForm(Form):
... username = StringField('Username', validators=[DataRequired()])
... password = PasswordField('Password', validators=[DataRequired() , Length(8, 128)])
@app.route('/login', methods=['GET', 'POST'])
def login():
mf = LoginForm()
if request.method == 'POST' and mf.valoidate():
#deal with the posted data
username = mf.username.data
passwd = mf.password.data
pass
return redirect(url_for('index'))
return render_template('login.html', form=mf)
上例中的if語句意思是:
if 用戶提交表單 and 數(shù)據(jù)通過驗證:
獲取并處理數(shù)據(jù)
Flask-WTF提供的validate_on_submit()方法合并了這兩個操作, 因此上面的代碼可以簡化為:
@app.route('/login', methods=['GET', 'POST'])
def login():
mf = LoginForm()
if my.validate_on_submit():
#deal with the posted data
username = mf.username.data
passwd = mf.password.data
pass
return redirect(url_for('index'))
return render_template('login.html', form=mf)
通過表單類實例.表單元素.data
獲取相應(yīng)表單元素的數(shù)據(jù)
在模板中處理錯誤消息
通過在模板中判斷form.element.errors
是否為true,查看驗證是否通過, 如果驗證不通過, 渲染錯誤提示消息
{% block content %}
<form method="post">
{{ form.csrf_token }}
{{ form.username.label }} {{ form.username(class='form-control') }} <br/>
{% if form.username.errors %}
{% for message in form.username.errors %}
<small class="error">{{ message }}</small><br/>
{% endfor %}
{% endif %}
{{ form.password.label }} {{ form.password(class='form-control') }} <br/>
{% if form.password.errors %}
{% for message in form.password.errors %}
<small class="error">{{ message }}</small><br/>
{% endfor %}
{% endif %}
{{ form.remember }} {{ form.remember.label }} <br/>
{{ form.submit(class='btn btn-primary') }}
</form>
{% endblock %}
自定義驗證器
行內(nèi)驗證
在自定義的表單類中定義特殊名稱的驗證函數(shù), 驗證函數(shù)名稱格式為:
validate_elementName(form, field):
名稱為elementName的表單元素會自動被這個validate_elementName的函數(shù)驗證, 該函數(shù)接收兩個參數(shù), 一個是表單類的實例,第二個是元素類的實例. 返回內(nèi)容為驗證異常類的實例.
有多少元素需要驗證, 就需要定義多少驗證函數(shù).
class TestForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Passowrd')
remember = BooleanField('Remember me')
submit = SubmitField('Login')
def validate_password(form, field):
if len(field.data) < 6:
raise ValidationError("Passwd must be longer than 5")
全局驗證器
只需要定義一個驗證函數(shù), 該驗證函數(shù)只有兩個要求:
- 接收兩個參數(shù): >表單類實例, >元素類實例
- 返回驗證異常類的實例
#定義驗證函數(shù)
from wtforms.validators import ValidationError
def my_validator(form, field):
if len(field.data) < 6:
raise ValidationError("passwd should longer than 5")
#使用驗證函數(shù)
class my_form(FlaskForm):
#驗證器必須是可調(diào)用對象, 所以把驗證器作為參數(shù)傳入不可以帶括號
passwd = PasswordField('Passowrd', validators=[my_validator,])
上述的驗證器可重用度不高, 實際環(huán)境中驗證不同的元素可能返回的異常字符不一樣, 并且驗證內(nèi)容和驗證方式也不一樣, 往往需要傳入相應(yīng)的參數(shù),通知驗證器如何驗證.
這時我們需要把驗證函數(shù)設(shè)計為工廠函數(shù)(一個可以產(chǎn)生驗證器的函數(shù):"產(chǎn)生函數(shù)的函數(shù)"):
# 定義驗證器工廠函數(shù)
from wtforms.validators import ValidationError
def valid_generator(message=None):
if message is None:
message = "the input is invalidate"
def _my_validator(form, field):
if len(field.date) < 6:
raise ValidationError(message)
return _my_validator
# 使用驗證函數(shù)
class my_form(FlaskForm):
#驗證器必須是可調(diào)用對象, 驗證器工廠函數(shù)生成的就是可調(diào)用對象, 所以調(diào)用工廠函數(shù)時應(yīng)該帶括號.
passwd = PasswordField('Passowrd', validators=[valid_generator('passwd should be at least 6!'),])
文件上傳
定義上傳表單:
我們使用Flask-WTF提供的FileField類, 而不是直接使用WTForms的FileField類. 但是它是繼承于WTForms的FileField類的, 并添加了和Flask的集成.
from flask_wtf.file import FileField, FileRequired, FileAllowed
from flask_wtf import FlaskForm
from wtforms import SubmitField
# 定義一個上傳文件的表單, 并驗證文件名后綴
class UploadForm(FlaskForm):
photo = FileField('Upload Image', validators=[FileRequired(), FileAllowed(['jpg', 'jpeg', 'png', 'gif'])])
submit = SubmitField()
# 在模板中渲染表單
@app.route('/upload', methods=['GET', 'POST'])
def upload():
mf = UploadForm()
if mf.validate_on_submit():
# deal with the uploaded file
file = mf.upload.data
pass
return redirect(url_for('index'))
return render_template('upload.html', form=mf)
上傳文件的模板
注意表單元素的三個屬性:
- action: 在哪個頁面提交 (默認當前頁)
- method: 默認GET, 我們需要用POST
- enctype: 上傳數(shù)據(jù)編碼方式, 必須是"multipart/form-data", 否則會把文件名作為表單數(shù)據(jù)提交.
{% extends "base.html" %}
{% block content %}
<h1>Upload Page</h1>
{# 表單必須添加 method屬性 和enctype屬性, 否則無法上傳 #}
<form method="post" enctype="multipart/form-data">
{{ form.csrf_token }}
{{ form.photo.label }} <br/>
{{ form.photo }} <br/>
{{ form.submit }}
</form>
{% endblock %}
處理上傳文件
<div style="color:red"> 待續(xù)... </div>