本文學(xué)習(xí)來源The-Flask-Mega-Tutorial-zh,學(xué)習(xí)如何使用Web表單网梢,再次表達對譯者的感謝履澳,正是因為他,才能學(xué)習(xí)到這么好的教程哭廉。本篇僅作為自己Flask入門的記錄畜普,想通過此來記錄代碼和自己不懂的概念。
Flask-WTF簡介
讓我們學(xué)習(xí)之前先了解下Flask-WTF插件,通過pip3 install Flask——WTF
群叶,在每次安裝插件后都建議打開Python解釋器測試是否安裝成功吃挑,測試命令為>>>import flask_wtf
,若接下來為>>>
則表明模塊導(dǎo)入成功,在WTForms的介紹中顯示街立,WTForms可以生成表單字段的HTML舶衬,也可以在模板中自定義它,來實現(xiàn)代碼和表單的分離赎离,而Flask-WTF
為WTForms提供了一個簡單接口逛犹。
配置應(yīng)用
在文中好幾處提到了松耦合這個概念,在配置應(yīng)用前讓我們講講松耦合,它主要是為了模塊的獨立性,在維基百科的編程部分解釋道:耦合是指一個組件直接了解其他組件的程度,Flask-WTF
就是一個松耦合的例子虽画。在接下來的應(yīng)用配置中也會用到舞蔽,先讓我們看個例子,通過app.config
來配置應(yīng)用
my_app = Flask(__name__)
app.config['SECRET_KEY'] = 'you-will-never-guess'
這樣使得配置和應(yīng)用代碼處于了一個文件码撰,隨著程序規(guī)模的增大渗柿,會越來越不利于維護,通過松耦合便可以解決這個問題脖岛。讓我們在microblog/
目錄下新建config.py模塊進行配置
import os
class Config(object):#python中所有的類都繼承自object類
SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
在這里提供了一個os.environ是存放環(huán)境變量的一個類朵栖,通過get方法來獲得SECRET_KEY
的值,在這里os.environ是一個字典變量柴梆。而SECRET_KEY
是用來對Flask的敏感部分進行加密陨溅。此處or
為表達式,連接第一個項用來查找SECRET_KEY
值绍在,第二個是hardcode字符串门扇,這樣就提高了Flask應(yīng)用的安全性能。
然后我們在__init__.py
里面進行初始化偿渡,使得Flask來讀取并使用config.py
配置文件:
from flask import Flask
from config import Config
my_app = Flask(__name__)
my_app.config.from_object(Config)
from app import routes
在這里有一句代碼是my_app.config.from_object(Config)
臼寄,在配置中使用類和繼承我們需要調(diào)用from_object
,可以參考技術(shù)手冊
配置完成后卸察,我們可以在交互環(huán)境中進行測試
>>> from microblog import my_app
>>> my_app.config['SECRET_KEY']
如果沒有問題則會顯示硬編碼的值'you-will-never-guess'脯厨,下面來看看用戶登錄表單的實現(xiàn)。
用戶登錄表單
Flask-WTF插件中坑质,支持將表單字段定義為類屬性合武,為了使結(jié)構(gòu)更加明了和維護方便,我們使用app/forms.py
來實現(xiàn)
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Sign In')
在這里可以看出導(dǎo)入的FlaskForm
作為LoginForm
的父類涡扼,導(dǎo)入的StringField稼跳、PasswordField,分別有著參數(shù)Username、Password吃沪,后面的參數(shù)validators則表明了驗證規(guī)則汤善,BooleanField則定義了一個Checkbox類型的,若加上default='checked'默認勾選此框票彪,而SubmitField則創(chuàng)建了一個submit按鈕红淡,導(dǎo)入的DataRequired
是用來進行驗證必填項的,也就是不填User和password會產(chǎn)生報錯降铸,下面來看看表單模板在旱。
表單模板
下面完成將表單添加進模板里,在templates
下新建login.html
:
{% extends "base.html" %}
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}
</p>
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
首先指明此模板繼承于基類模板推掸,在form中定義了三個屬性桶蝎,action 指向了某個真實服務(wù)器的臨時 URL 驻仅,置空則表示當(dāng)前頁面,而在method屬性里面指定值為POST
登渣,而有時候我們使用的方法為GET
我們看一下POST
和GET
方法區(qū)別噪服;
使用GET
會使得提交的內(nèi)容在URL中可見,如果使用GET
方法提交表單胜茧,會使密碼等泄露粘优,而POST
通常用于提交表單和敏感數(shù)據(jù),更多的信息可以參考此網(wǎng)站
而對于form.hidden_tag()
使得跨站請求偽造保護得到了支持竹揍。它的模板參數(shù)生成了一個隱藏字段敬飒,其中包含一個用于保護表單免受CSRF攻擊的token
邪铲。在Flask-WTF文檔中寫到:
Form 類有一個 hidden_tag 方法芬位, 它在一個隱藏的 DIV 標簽中渲染任何隱藏的字段
{{ form.<field_name>.label }}
,{{ form.<field_name>() }}
則是為了指明其需要在渲染時轉(zhuǎn)化為HTML元素 及其類別带到,下面編寫表單視圖來渲染模板
表單視圖
讓我們在app/routes.py
模塊中編寫這個視圖函數(shù):
from flask import render_template
from app import my_app
from app.forms import LoginForm
#代碼中間部分
@app.route('/login')
def login():
form = LoginForm()
return render_template('login.html', title='Sign In', form=form)#render_templatedi第一個參數(shù)傳入頁面昧碉,然后根據(jù)后面的參數(shù)渲染模板
然后把登錄的鏈接添加到導(dǎo)航欄,就是base.html
模板中:
<div>
Microblog:
<a href="/index">Home</a>
<a href="/login">Login</a>
</div>
接下來運行一下應(yīng)用試一試吧揽惹!本機運行結(jié)果:接收表單數(shù)據(jù)
當(dāng)我們在頁面中進行提交的時候被饿,會發(fā)現(xiàn)有錯誤,回顧之前我們只進行了顯示表單的工作搪搏,接下來我們通過Flask-WTF對提交的數(shù)據(jù)進行處理和驗證狭握。
from flask import render_template, flash, redirect
#關(guān)于GET、POST屬性有遺忘的話可以查看前面的介紹
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
flash('Login requested for user {}, remember_me={}'.format(
form.username.data, form.remember_me.data))
return redirect('/index')
return render_template('login.html', title='Sign In', form=form)
接下來讓我們看下form.validate_on_submit()
疯溺,當(dāng)我們進行提交時它主要完成了兩件事:
- 通過is_submitted()通過判斷HTTP方法來確認是否提交了表單
- 通過WTForms提供的validate()來驗證表單數(shù)據(jù)(使用我們在下面的表單類里給每個字段傳入的驗證函數(shù))
在stack overflow找到了自己可理解的解釋:
validate_on_submit() is a shortcut for is_submitted() and validate().
Generally speaking, it is used when a route can accept both GET and POST methods and you want to validate only on a POST request.
驗證通過后會返回True论颅,要是沒通過則會像之前一樣返回GET
請求的渲染頁面。
flash是為了向用戶反饋囱嫩,比如在用戶登錄成功后顯示消息恃疯,以使得應(yīng)用對用戶更加友好易用。
下面來看看redirect()
,可以觀察到其有一個URL參數(shù)墨闲,通過英文意思我們可以猜測今妄,其是在驗證通過后返回/index
頁面,函數(shù)的實際功能也是這樣鸳碧。
當(dāng)調(diào)用flash()
函數(shù)后盾鳞,F(xiàn)LASk會儲存這個消息,為了讓其顯示在頁面上瞻离,需要將消息渲染到基礎(chǔ)模板中腾仅,更新后的base.html
如下:
<html>
<head>
{% if title %}
<title>{{ title }} - microblog</title>
{% else %}
<title>microblog</title>
{% endif %}
</head>
<body>
<div>
Microblog:
<a href="/index">Home</a>
<a href="/login">Login</a>
</div>
<hr>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>
with
結(jié)構(gòu)把get_flashed_messages()
的結(jié)果賦值給變量messages
,前者返回flash()
注冊過的消息列表琐脏,接下來判斷并遍歷渲染消息列表攒砖,然而在運行應(yīng)用的過程中出現(xiàn)了一些問題缸兔,錯誤代碼中出現(xiàn):
__init__() takes from 1 to 2 positional arguments but 3 were given
從錯誤提示中發(fā)現(xiàn)是驗證器出現(xiàn)了問題,最后在stackoverflow找到了解決方案吹艇,在自己編寫過程form.py
中惰蜜,validators=[DataRequired()]
丟失了()
。若編寫正確受神,置空點擊Sign In
后應(yīng)當(dāng)刷新Sign In
頁面抛猖。
完善字段驗證
表單驗證是為了防止提交無效數(shù)據(jù),若接收到無效表單鼻听,則應(yīng)該重新顯示表單财著,讓用戶輸入合法數(shù)據(jù),在之前的設(shè)計中我們沒有給出足夠的錯誤反饋撑碴,現(xiàn)在讓我們完善這個功能撑教。(注:其實錯誤已經(jīng)生成,下面通過邏輯來渲染它們)
讓我們在login.html
中為username和password字段添加驗證描述性錯誤消息渲染邏輯:
{% extends "base.html" %}
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}<br>
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
相比之前醉拓,多加了一個for
循環(huán)來渲染錯誤信息伟姐,讓其用紅色字體顯示出來,運行結(jié)果:
登錄表單已經(jīng)差不多完善了亿卤,下面讓我們看看包含鏈接的更好方法愤兵。
生成鏈接
在之前我們在base.html
和routes.py
中包含了一些鏈接的例子:
<!--base.html中-->
<div>
Microblog:
<a href="/index">Home</a>
<a href="/login">Login</a>
</div>
<!--routes.py中-->
return redirect('/index')
flask模塊提供了url_for()
函數(shù)用于獲取函數(shù)的URL
,因此在項目中所有引用到URL字符串的地方排吴,都可以使用url_for(func, **kwargs)
來獲取函數(shù)的URL秆乳,這樣做的好處在于,可以使項目更容易維護钻哩。當(dāng)某函數(shù)URL發(fā)生變更時屹堰,只需修改一處地方即可,而無須修改每一處URL引用憋槐,相比硬編碼更加方便和易于維護双藕,讓我們使用url_for
來生成鏈接。
結(jié)果上述代碼可改為如下:
<!--base.html中-->
<div>
Microblog:
<a href="/index">Home</a>
<a href="/login">Login</a>
</div>
在routes.py
中的login
中也要進行修改阳仔,不過先得引入url_for模塊忧陪。
from flask import render_template, flash, redirect, url_for
#中間代碼部分
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# ...
return redirect(url_for('index'))#修改此句
# ...
在此表單差不多就完成了,(Ps:如果登陸了會重定向到歡迎界面),效果如下近范,謝謝您的觀看嘶摊。