[Python][flask][flask-wtf]關於flask-wtf中API使用實例教程


簡介:簡單的集成flask,WTForms,包括跨站請求偽造(CSRF),文件上傳和驗證碼。

一.安裝(Install)

此文仍然是Windows操作系統下的教程,但是和linux操作系統下的運行環境相差甚微。

使用Python版本3.5.2.

上一篇文章提到Virtualenv環境運行Python,這次仍然建立Python虛擬運行環境以便實現不同數據包的隔離。

  • 創建wtfdemo虛擬運行環境

用控制台(管理員運行模式)進入(cd)到想要創建工程的路徑下,創建wtfdemo文件夾。

mkdir wtfdemo

進入(cd)wtfdemo文件夾,創建Python虛擬運行環境。

virtualenv flaskr

出現如下字樣,說明虛擬環境創建成功

PS:本次提供第二種創建Python虛擬運行環境的使用方法

virtualenv -p source_file\python.exe target_file

為什么提出第二種創建方法,你會發現,當你的Python Web程序開發多了以后,PC上難免安裝了很多版本的Python運行環境。

舉例:當PC上同時安裝了Python2.7和Python3.5,運行virtualenv flaskr后,建立的Python虛擬運行環境是Python2.7版本的,但是我們的開發環境是Python3.5。

在控制台中輸入一下指令,你就會發現問題。

virtualenv -h

出現下面圖片顯示,默認的virtualenv安裝路徑是Python2.7,也就是默認的安裝的虛擬環境是Python2.7版本。

所以,在這種情況下,請指定你需要的Python版本,並建立虛擬運行環境。

  • 安裝flask-wtf庫文件

進入Python虛擬運行環境(在上一篇博文中寫過),執行以下指令。

pip install flask
pip install flask-wtf
pip install flask-login
pip install flask-sqlalchemy

出現如下圖所示,說明安裝成功。

flask安裝成功。

flask-wtf安裝成功。

flask-login安裝成功。(本次使用flask-wtf庫時需要輔助以該運行庫)

flask-sqlalchemy安裝成功。

至此,環境配置階段結束。

 

二.Flask-WTF

flask-wtf實現了簡單集成WTForms,包括CSRF,文件上傳以及Google內嵌的驗證碼。

特性:

①集成了WTForms。

②包含了帶有CSRF的安全表單

③全局的CSRF保護

④驗證碼Recaptcha的支持

⑤支持Flask-Upload的文件上傳

⑥多語言集成

但,成也集成敗也集成,flask-wtf不包含WTForms中許多重要的API,在進行復雜作業時顯得力不從心。

 

實際上,flask-wtf下整合的WTForm單元已經能基本滿足一些小型網站的建設使用了。

三.在實例中應用

  • 文件結構

上一篇博文中,文件結構很是混亂,在實際維護中很不方便,會留下很多隱患,比如后期維護人員無法很快的切人項目中進行解讀代碼。

此處將各個模塊進行分離,抽離出各個不同的模塊。這樣的有點是單個文件只操作自己的功能,代碼可讀性強,邏輯清晰。缺點就是大型項目時文件會有很多碎小的文件,需要經常使用from import語句從不同的文件中引用出不同的功能。

但是相對於優點,缺點所帶來的風險是很小的。實際工作中,這樣的分文件管理項目是極力推薦的,邏輯清晰,可維護性高是每一個優秀的項目應該具有的最基本屬性。

本文采用以下文件結構。

wtfdemo→flaskr
    →templates →login.html
            →index.html
→uploads →wtf.py →model.py →views.py →run.py
→config.py →form.py

本次應用實現一個簡單的用戶登錄功能模塊,文中涵蓋的flask-login包將在下一篇博文中詳細解釋,如果想馬上了解,請查看flask-login官方文檔

  • 詳細參數解析

config.py代碼如下所示。

 1 import os
 2 
 3 basedir = os.path.abspath(os.path.dirname(__file__))
 4 
 5 SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'test.db')
 6 SQLALCHEMY_TRACK_MODIFICATIONS = False
 7 
 8 CSRF_ENABLED = True
 9 SECRET_KEY = 'you will never guess'
10 
11 RECAPTCHA_PUBLIC_KEY = '6LeYIbsSAAAAACRPIllxA7wvXjIE411PfdB2gt2J'
12 RECAPTCHA_PRIVATE_KEY = '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu'

一句一句解釋

①OS模塊是Python標准庫中的一個用於訪問操作系統功能的模塊,OS模塊提供了一種可移植的方法使用操作系統的功能。

這里利用os.path.abspath()函數,取出該文件下的絕對路徑。

②將取出的絕對路徑下存放到basedir中,以便被其他文件引用。

③SQLALCHEMY_DATABASE_URI用於連接數據庫,SQLALCHEMY_TRACK_MODIFICATIONS追蹤對象的修改並且發送信號,這里在上一篇文章講解過。

④CSRF_ENABLED為是否開啟flask-wtf下的CSRF(跨站請求偽造)保護,True為開啟保護狀態。

⑤SECRET_KEY設置Flask的應用密匙,同時CSRF開啟保護時,同時需要一個密匙,如果不在文件中明確指出,通常與你的 Flask 應用密鑰一致。如果想要使用不同的密匙,請在文件中配置CSRF單獨的密匙。

WTF_CSRF_ENABLED = False

⑥RECAPTCHA_PUBLIC_KEY與RECAPTCHA_PRIVATE_KEY分別為,必需公鑰與必需私鑰。

 

wtf.py代碼如下。

 1 from flask import Flask
 2 from flask_sqlalchemy import SQLAlchemy
 3 from config import SQLALCHEMY_DATABASE_URI, SQLALCHEMY_TRACK_MODIFICATIONS
 4 from flask_login import LoginManager, AnonymousUserMixin
 5 
 6 app = Flask(__name__)
 7 app.config.from_object('config')
 8 db = SQLAlchemy(app)
 9 lm = LoginManager()
10 lm.init_app(app)
11 lm.login_view = 'login'
12 lm.login_message = 'Please log in!'
13 lm.login_message_category = 'info'
14 lm.anonymous_user = AnonymousUserMixin
15 lm.session_protection = 'strong'
16 lm.refresh_view = 'login'
17 lm.needs_refresh_message = 'Please enter your info'
18 lm.needs_refresh_message_category = "refresh_info"
19 
20 from model import User
21 import views

①引用包中的函數說明。

Flask函數為flask架構的主函數,一切flask架構的Web開發都是由此函數引申出來的。

SQLAlchemy函數為flask-sqlalchemy包的主函數,由此函數綁定相應的flask架構應用,就可以實現連接數據庫的操作。

SQLALCHEMY_DATABASE_URI與SQLALCHEMY_TRACK_MODIFICATIONS為config.py中配置相應的參數,在此文件中被引用以便使用。

LoginManager函數是flask-login包的主函數,由此將flask架構和flask-login連接成一個整體。

②app.config.from_object函數將config文件下能配置到app的條目,綁定到app上。結果等同於直接綁定,等同結果如下。

app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = SQLALCHEMY_TRACK_MODIFICATIONS

③其他關於flask-login包下的函數,在下篇博文中將詳細的講解,本篇文章重點介紹flask-wtf包中的API使用。

 

form.py代碼構成如下。

 1 from flask_wtf import Form
 2 from wtforms import TextField, BooleanField
 3 from wtforms.validators import DataRequired
 4 from flask_wtf.recaptcha import RecaptchaField
 5 
 6 from werkzeug import secure_filename
 7 from flask_wtf.file import FileField
 8 
 9 class LoginForm(Form):
10     username = TextField('username', validators=[DataRequired()])
11     password = TextField('password', validators=[DataRequired()])
12     remember_me = BooleanField('remember_me', default=False)
13     recaptcha = RecaptchaField()
14         
15 class UploadForm(Form):
16     file = FileField()

①Form為flask-wtf的基礎form類型,一切的網頁表單都是繼承Form父類的屬性,並添加自己獨有的處理

②TextField, BooleanField是WTForm構建的文字域和復選框域,對應HTML的<input type='text'/>和<input type='checkbox'/>標簽。

③DataRequired驗證TextField是否為空,為空返回一個錯誤信息(error message)

④在將文件保存在文件系統之前,要堅持使用secure_filename函數來確保文件名是安全的。

⑤flask-wtf整合了flask-upload中的部分函數,所以FileField不是從WTForm中引用出來的。FileField顧名思義,對應HTML標簽<input type='file'/>標簽。

⑥分析LoginForm類

1 class LoginForm(Form):
2     username = TextField('username', validators=[DataRequired()])
3     password = TextField('password', validators=[DataRequired()])
4     remember_me = BooleanField('remember_me', default=False)
5     recaptcha = RecaptchaField()

類成員構成: 變量名 = xxxField(),其中xxxField中可以選擇傳入<input/>標簽的name,validators屬性判斷如果TextBox中為None則返回錯誤消息

變量名可以在相對應的HTML文件中使用對應的 form.變量名 使用。

解析:此處添加兩個TextBox一個輸入用戶名,一個輸入密碼。一個CheckBox復選框,實現記住用戶功能。最后一個為登陸時防止惡意登陸的驗證碼。

 

model.py代碼構成。

 1 from wtf import db
 2 from flask_login import UserMixin
 3 
 4 class User(db.Model, UserMixin):
 5     id = db.Column(db.Integer, primary_key=True)
 6     username = db.Column(db.String(80), unique=True)
 7     password = db.Column(db.String(80), unique=True)
 8     
 9     def __init__(self, username, password):
10         self.username = username
11         self.password = password
12         
13     def __repr__(self):
14         return '<User %r>' % self.username

①將wtf中模塊中的db參數引用到model.py中,以創建ORM數據庫映射關系

②其中UserMixin為flask-login中的用戶類,其中包含了一些可以在實現用戶登錄時驗證時需要的屬性。

③User類中定義id,用戶名及密碼屬性。__init__為User類的構造函數,__repr__方便在調試中輸出數據時使用。

 

views.py代碼構成。(下邊的代碼包含了Flask框架下的一些簡單應用,請在有一定Flask架構基礎的情況下,可以快速的切入下段代碼。其中flask-login在本文只一筆帶過,將在下篇博文中詳細解析flask-login包的API使用實例教程)

 1 from flask import render_template, flash, redirect, session, url_for, request, g
 2 from flask_login import login_user, logout_user, current_user, login_required, AnonymousUserMixin, fresh_login_required, login_fresh
 3 from wtf import app, db, lm, User
 4 from form import LoginForm, UploadForm
 5 
 6 @app.before_request
 7 def before_request():
 8     g.user = current_user
 9  
10 @lm.user_loader
11 def load_user(id):
12     user = User.query.filter_by(id=id).first()
13     return user
14 
15 @app.route('/login', methods=['GET', 'POST'])
16 def login():
17     if g.user is not None and g.user.is_authenticated:
18         login_fresh()
19         session['_fresh'] = False
20         return redirect(url_for('index'))
21     if g.user.is_active == True:
22         flash('active is True')
23     else:
24         flash('active is False')
25     form = LoginForm()
26     if form.validate_on_submit():
27         user = User.query.filter_by(username=form.username.data, password=form.password.data).first()
28         if(user is not None):
29             login_user(user,remember=form.remember_me.data)
30             return redirect(request.args.get("next") or url_for('index'))
31     return render_template('login.html', form=form)
32     
33 @app.route('/', methods = ['GET', 'POST'])
34 @app.route('/index', methods=['GET', 'POST'])
35 @login_required
36 def index():
37     form = UploadForm()
38 
39     if form.validate_on_submit():
40         filename = secure_filename(form.file.data.filename)
41         form.file.data.save('uploads/' + filename)
42         return redirect(url_for('upload'))
43 
44     return render_template('upload.html', form=form)
45     
46 @app.route('/logout')
47 @login_required
48 def logout():
49     logout_user()
50     return redirect(url_for('login'))

①from form import LoginForm, UploadForm, InfoForm

將form.py中定義的表單類引入到該文件中。

②before_request函數,定義為flask-login綁定用戶時使用,在發起請求之前首先調用此函數。

③load_user函數,定義為根據ID取出用戶實體。

④login函數,詳細分析:

@app.route('/login', methods=['GET', 'POST'])         #接受前段POST和GET的表單信息,綁定到發布/login頁面上
def login():
    if g.user is not None and g.user.is_authenticated:#判斷用戶是否登錄過系統,或者是否使用過記住用戶這個功能        
return redirect(url_for('index')) #如果滿足條件,則跳轉到/index界面
form = LoginForm() #實例化LoginForm() if form.validate_on_submit(): #validate_on_submit函數判斷form表單中是否有提交動作,如果有,則獲取該動作 user = User.query.filter_by(username=form.username.data, password=form.password.data).first()#獲取是否存在用戶(這種未加密登錄方法不推薦在實際項目中使用) if(user is not None): #判斷取出的數據庫用戶實體是否為空 login_user(user,remember=form.remember_me.data)#將用戶實體,記住用戶選項,綁定到flask-login中 return redirect(request.args.get("next") or url_for('index'))#重定向到next頁面或是index頁面 return render_template('login.html', form=form) #使用模板函數,將form綁定到login.html頁面上

⑤index函數分析

@app.route('/', methods = ['GET', 'POST'])            #接受前段POST和GET的表單信息,綁定到發布跟目錄上
@app.route('/index', methods=['GET', 'POST'])        #接受前段POST和GET的表單信息,綁定到發布/index頁面上
@login_required                                       #驗證用戶是否登錄
def index():                                         
form = UploadForm() #實例化UploadForm() if form.validate_on_submit():                  #判斷表單中是否存在提交動作 filename = secure_filename(form.file.data.filename)#secure_filename函數確保文件名是安全,並賦值到filename中 form.file.data.save('uploads/' + filename) #將文件保存到工程目錄的位置 return redirect(url_for('index')) #上傳完文件后將頁面重定向到index.html return render_template('index.html', form=form) #使用模板函數

 ⑥logout函數分析

@app.route('/logout') #將函數綁定到/logout鏈接上
@login_required       #驗證用戶是否登錄
def logout():
    logout_user()     #清空session中的緩存
    return redirect(url_for('login'))#重定向到login.html頁面上

 

login.html代碼構成

<html>
<head>
Login-Demo
</head>
<body>
<form method="POST" action="">
    {% with messages = get_flashed_messages() %}
    {% if messages %}
    <ul>
    {% for message in messages %}
        <li>{{ message }} </li>
    {% endfor %}
    </ul>
    <hr/>
    {% endif %}
    {% endwith %}
    {{form.csrf_token}}
    {{form.username(size=80)}}
    {% for error in form.username.errors %}
        <span style="color: red;">[{{error}}]</span>
    {% endfor %}<br>
    {{ form.password(size=80) }}
    {% for error in form.password.errors %}
        <span style="color: red;">[{{error}}]</span>
    {% endfor %}<br>
    <p>{{ form.remember_me }} Remember Me</p>
    <p></p>
    <p><input type="submit" value="Sign In"></p>
    <a href="{{ url_for('logout') }}">Logout</a>
</form>
</body>
</html>

 

一下講解表單中各個語句的含義。

{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
    <li>{{ message }} </li>
{% endfor %}
</ul>
<hr/>
{% endif %}
{% endwith %}

①get_flashed_messages函數獲取flask后端發動的flash消息。

②判斷如果是消息類型的話循環輸出

{{form.csrf_token}}
{{form.username(size=80)}}
{% for error in form.username.errors %}
    <span style="color: red;">[{{error}}]</span>
{% endfor %}<br>
{{ form.password(size=80) }}
{% for error in form.password.errors %}
    <span style="color: red;">[{{error}}]</span>
{% endfor %}<br>
<p>{{ form.remember_me }} Remember Me</p>
<p><input type="submit" value="Sign In"></p>
<a href="{{ url_for('logout') }}">Logout</a>

①form.csrf_token設置開啟CSRF保護

②form.username(size=80),創建一個長度為80px的TextBox,對應前面提到的LoginForm創建的成員變量username

③for error in form.username.errors,此處抓取validators=[DataRequired()]返回的錯誤信息(error message)

④form.password(size=80),創建一個長度為80px的TextBox,對應前面提到的LoginForm創建的成員變量password

⑤form.remember_me,創建一個CheckBox,對應前面提到的LoginForm創建的成員變量remember_me

⑥<input type="submit" value="Sign In">添加一個按鈕,點擊后觸發form.validate_on_submit()函數

⑦<a href="{{ url_for('logout') }}">前段調用后端函數的一種方法,調用后端函數view.py中的logout函數

 

index.html代碼構成

<html>
<head>
{% if filedata %}
<h3>{{ filedata.filename }}</h3>
{% endif %}
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
    <li>{{ message }} </li>
{% endfor %}
</ul>
<hr/>
{% endif %}
{% endwith %}
<a href="{{ url_for('logout') }}">Logout</a>
<form method="post" enctype="multipart/form-data">
    {{ form.hidden_tag() }}
    {{ form.file }}
    <input type="submit">
</form>
</body>
</html>

將UploadForm類中的file呈現在頁面上。

 

最后,我們為了運行程序,添加run.py文件。

from wtf import app
app.run(debug=True)

 運行如下代碼,執行此次實例程序。

 

四.運行實例程序

打開瀏覽器輸入 http://127.0.0.1:5000/login,進入登錄界面。

關於數據庫的操作已經在上一篇博文里寫的很清楚了,在數據庫中已經添加了用戶(username=john,password=1)。

①當直接點擊Sign In按鈕之后,會出現如下圖顯示的效果。

出現上面[This field is required.]是from.py文件中validators=[DataRequired()]的功勞,這段代碼幫你驗證了提交表單中TextBox是否為空。

②當輸入用戶名和密碼之后,你會發現,點擊Sign In按鈕之后,並沒有跳轉到預期的index.html界面。

Q:為什么沒有跳轉。

A:這和我們之前的一個疏忽造成的,之前的代碼我已經留下了一個位置修改這個問題。

下面我們來分析,問題是點擊按鈕無法執行代碼中的跳轉,說明后端代碼綁定控件的時候出現問題。

那是什么問題呢,flask-wtf已經為你提供了一個可以查出問題的方法,使用form.errors檢索出在提交表單之后發生的問題。

在views.py文件的login函數中添加如下代碼(在實例化LoginForm之后添加)

flash(form.errors)

重新運行程序,輸入用戶名和密碼,點擊Sign In按鈕。

看到{'recaptcha': ['The response parameter is missing.']}你發現問題了么,對的,就是因為筆者的一個不小心,沒有在HTML文件實例化recaptcha,由於驗證碼和按鈕有一個相互作用的關系,試想當你登錄一個系統,不輸入驗證碼直接點擊登錄,會發生一樣的問題。

解決辦法:

在login.html添加如下代碼。

<p>{{ form.remember_me }} Remember Me</p>
<p>{{ form.recaptcha }}</p>#添加的代碼
<p><input type="submit" value="Sign In"/><p>

再次重新啟動程序,這回我們就會看到我們需要的樣子。

 輸入用戶名和密碼,按驗證碼要求點擊相應的圖片,點擊Sign In按鈕,登錄成功!

點擊Choose File,讓我們上傳一個文件試試看,點擊upload按鈕之后,你會在項目根目錄下的Uploads文件夾找到你上傳的文件。

PS:本地運行並上傳文件,不會出現問題。如果你是發布在網絡中的時候,請注意一下幾個問題。

①圖片同名問題。

②上傳文件夾權限問題。(筆者之前在項目中處理的辦法是將讀寫權限賦予給everyone用戶,但是這樣會大大降低服務器的安全性)

 

關於flask-wtf中文件上傳的一些解析,將在以后講解flask-upload的時候一起講解。

關於本文中的flask-login包,我們將在下一篇文章中解析。

 

以上就是基本的flask-wtf使用實例說明,歡迎探討轉載及引用參考,你們的回應是我的動力。

如果文中存在錯誤或是某個地方說的不夠精確,勞煩批評指出。

以上,辛苦了。

 

參考:

[1] github源碼:https://github.com/lepture/flask-wtf

[2] flask-wtf官方文檔:https://flask-wtf.readthedocs.io/en/latest/

 

*本文為Alima原創,如果轉載請聯系筆者,轉載注明格式[轉載][博客園][Alima][關於flask-wtf中API使用實例教程],並在文首注明本文鏈接,多謝合作。

*非法轉載及非法抄襲博文將依照網絡著作權流程辦理,請尊重作者勞動成果,最終解釋權歸Alima與博客園共同所有,感謝合作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM