Flask學習筆記(未完結(jié))

準備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)

Image from Flask Web

請求/應(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ù),即使有未處理的異常拋出,也在每次請求之后運行。
Image from Flask Web開發(fā)實戰(zhàn), page 84

例:

@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 會將其渲染成 &lt;h1&gt;Hello&lt;/ h1&gt; ,瀏覽器能顯示這個 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(' &#9835;')

控制結(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ù)只有兩個要求:

  1. 接收兩個參數(shù): >表單類實例, >元素類實例
  2. 返回驗證異常類的實例
#定義驗證函數(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)

上傳文件的模板

注意表單元素的三個屬性:

  1. action: 在哪個頁面提交 (默認當前頁)
  2. method: 默認GET, 我們需要用POST
  3. 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>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容