標籤:

Flask表單疑問,這個name是怎麼傳進來的?

最近在看《FlaskWeb開發:基於Python的Web應用開發實戰》

第4.4節。示例4-4代碼,展示怎麼生成表單和接收表單數據

@app.route("/", methods=["GET", "POST"])
def index():
name = None
form = NameForm()
if form.validate_on_submit():
name = form.name.data
form.name.data = ""
return render_template("index.html", form=form, name=name)

補上NameForm()的定義

from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required

class NameForm(Form):
name = StringField("What is your name?", validators=[Required()])
submit = SubmitField("Submit")

書上說,用戶提交表單後,伺服器收到POST請求,validdate_on_submit()返回True,name被賦值。我不明白name是怎麼被賦值的,

name = form.name.data #這個屬性不為空嗎?

form不是剛剛被創建的一個臨時變數嗎?誰給他name.data值了?可是網頁上真的顯示了提交的用戶名。這一切在背後是怎麼發生的?

我有一點猜測:難道是伺服器在POST請求分發給視圖函數時,把用戶提交的表單數據放到了應用的上下文,而form對象被創建時,在其初始化函數中,會自己去應用的上下文尋找表單數據,表單數據一定有一個規定的名稱,以便於被找到。而上下文中的一些變數可能是以字典的形式存儲的。

以上僅是我的猜測。請告訴我原理。

還有,《FlaskWeb開發:基於Python的Web應用開發實戰》真的適合web初學者嗎?感覺好多新概念不理解,一些是web開發本身的概念,一些是flask引入的概念。而作者在提到這些概念的時候都,好像都假設讀者了解這些概念。有一些介紹也是點到即止。


瀉藥!

沒看form有關的源碼,但是應該是這樣的∶

首先,你得理解像flask這種MVC(或者說MTC)的基本運行機制。

- 對於flask的view,你得知道wsgi協議(如果不清楚,請自行Google之)。

更底層(邏輯上的底層)的HTTP utils(flask用的是werkzeug)將client端的HTTP requests等進行parse,並且將其構建為wsgi的environment(包含了request及其他信息)。

wsgi server在process請求的過程是:根據wsgi協議構建environ,將其傳入flask app instance(這個即為flask框架實現的wsgi app),flask app instance用這個environ和自己的``start_response`` method(這個也是wsgi協議規約)完成請求處理並response。

flask有一個線程級(或greenlet)的request對象。在真正process response之前,將environ丟到這個request對象里,之後這個request跟著你的那個線程就成為了默契的炮友~~直至response完成或線程完蛋。

views裡面的那一坨坨的route的作用是啥捏?這個就是url routing了,就是我在你的站點上點了一個特定的url,flask要如何滿足你。你得說出你想要的東西吧。

那麼views是什麼時候用到呢?廢話,當然還是在接收並理解請求之後到響應之前。

好了,flask app instance在處理你的需求的時候從request對象里拿到了你請求的url,這個就相當於一把key,然後app instance根據這個key去views里找到了對應的鎖(你想要的處理邏輯,就是views里的route下面的function),這個鎖啊,她一旦被打開了,滿足你需求的時刻也就快了 ~ ~

P.S. 至於如何匹配到這個鎖的呢,這個就是url mapping了,就是一堆正則匹配(別小看這個,如果你用過flask的blueprint,你會明白寫這麼個mapping也是件爽hi了的事情咯)

- model controller呢?

你的需求其實就是要flask給你response,response其實呢就是數據(不管是RESTFUL API還是template形式,都是你擼出的data)。

數據這坨翔實怎麼來的呢?就是你從資料庫(廣義,包括sql nosql db,內存級緩存,磁碟文件等等等等)拿到的,至於data的來源嘛,可能是你自己插的,或者是從別的地方哭著要來的。

model就是干這活的,只不過它比較抽象,將資料庫操作轉成了對Python對象的操作(這個就是大名鼎鼎的ORM,其實並沒有什麼卵用,ORM寫多了你連查詢優化都忘了,如果你有比較高的performance需求也有很多時間,就自己擼高效檢索方案吧,我沒少被這玩意兒坑%&>_&<%)。

controller這玩意兒你其實可以不要,不過為了做邏輯分離,讓你擼業務擼的漂亮點兒還是用它吧。

OK,說了這麼多,不知道有沒有人能看懂,如果沒看懂,讓我思考會兒吧 ~ ~

========================================================================

簡而言之呢,你用flask作application的時候,MVC各司其職堆好你產生data的邏輯即可,其它你一概不用管。

靠,終於可以回到你的問題上了:

先看下Form這玩意兒:

from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required

class NameForm(Form):
name = StringField("What is your name?", validators=[Required()])
submit = SubmitField("Submit")

這就是一個類而已,和model裡面的那些玩意兒沒本質區別,也都是一個映射關係。model是將資料庫啥的和Python對象映射;Form是將HTML表單(form)和這個從flask.ext.wtf.Form及其子類映射。

再來看看這玩意兒:

1 @app.route("/", methods=["GET", "POST"])
2 def index():
3 name = None
4 form = NameForm()
5 if form.validate_on_submit():
6 name = form.name.data
7 form.name.data = ""
8 return render_template("index.html", form=form, name=name)

首先來分析一下執行過程:

哎呀,這個只是個route嘛,它哪有執行過程。

執行是由app instance做的嘛,好吧,其實它是被app instance做的。

在展開之前看一下route後面那個 methods=["GET", "POST"]。這玩意的意思是說,當我遇到有與``index``(即上面route下面的那個index)匹配的的請求時,我可以用GET或者POST方法,其他的像PUT、DELETE等HTTP verb我不讓你丫玩兒。

當你把你的這個flask應用deploy到web server或者在本地run test server的時候,你在瀏覽器里輸入 http://xxxxxx.ooooo.xxoo/index 的時候,你向server發射的是``GET``炮彈,flask instance接受了你的彈,然後route到了index這個函數,好吧,激動人心的時刻到了:

#3. name = None 這個就是個賦值

#4. form = NameForm() 好吧,這個就是NameForm類的實例化,拿到一個form object。

#5. if form.validate_on_submit(): 呵呵,這個嘛,意思是問一下#2中實例化的form object,SB,你是在被人(1)submit(提交)並且(2)提交滿足我的要求么? 好吧,前面說過了,我是在GET,而沒有在submit(submit在HTML中為POST),好吧,我連(1)都不滿足,所以也就沒有#6、#7什麼事兒了。

#8 好的,app instance終於快要向你回饋了。給我把前面的from object和name變數一起帶到index.html這個文件里吧。

哈哈,終於有jinja2的活兒了,@題主, 你丫也把index.html發上來呀 = =

index.html里你寫了個表單,裡面有個叫做``name``的field和一個``submit``按鈕,這些field和你的``NameForm``對應,好了,對應填充的活兒和就交給jinja2吧,它會給你一個最終將在用戶瀏覽器顯示的index.html。

view的使命完成了,它成功的生成了數據。

app instance 拿到了view的數據,也該向client端發最終響應了。OK,server完成了這次request的response,用戶拿到最終想要的東西了。

瀏覽器或者UA乾的活兒我就不展開了,我的手已經抽筋了,

現在在用戶的瀏覽器上,看到了index.html,上面有一個表單,那個表單上面有個文本框(有``What is your name?``的placeholder)和一個Submit提交按鈕。

好了,用戶在那個文本框中寫了一個名字,比如``John``,然後按了那個``Submit``按鈕,這次提交迴向server發射一個``POST``請求,這個請求裡面會帶上文本框的名字(哈哈,就是name)和文本框name裡面的值(yeah,就是``John``)。然後呢,到flask instance中,request對象(如果不知道這是什麼東西,看前文)里就會有所有這些有關請求的數據了。

辣么,辣么這跟from有毛線關係呢????

其實吧,form中的數據是flask從request(request是從environ構建的)中拿到並映射進form屬性中的(映射過程發生在你實例化form的時候),這個過程我也不展開了。好吧,不知道有沒有解釋你

name = form.name.data

為什麼會有數據的疑問。

我實在不想在打字了,好累噢!!!!!

好吧,總結起來就是,HTTP utils將用戶的raw request解析成符合wsgi規範的environ,然後flask instance將environ丟到線程級的request object里,在你實例化flask.ext.wtf.Form及其子類時,會從request object里解析並映射數據到form中。

關於讀書的話,如果你覺得你實在看不明白,那可能是有關web底層一點的知識稍微欠缺,可以補補這塊的知識,對於框架的處理機制,最好的辦法是 看!!!源!!!!碼!!!!!

看不懂就補基礎,如此而已。

上面過程都是我信手打出來的,也好久沒用flask了,寫的時候也沒查資料,如有bug或建議可以告訴我,以便我修改。

最後希望能幫到你/你們。


我當初也被搞糊塗了,那本 flask web development里用到的是flask_wtf模塊,而有的教程里用到的卻是WTForms模塊。
這是WTForms的表單驗證路由函數
@app.route("/register", methods=["GET", "POST"])
def register():
form = RegistrationForm(request.form)
if request.method == "POST" and form.validate():
pass
對比二者你會發現,二者有三個區別
1.RegistrationForm實例化時用到了request.form,而你的例子里沒有用到。
2.你的例子里沒有驗證表單是POST還是GET方法,上面這個例子里用了request.method == "POST"
3.一個是form.validate(),一個是form.validate_on_submit()。
可見flask_wtf模塊里的Form 類對WTForms里的Form類進行了封裝。
把request.form 和request.method == "POST"封裝進去了。對比一下代碼應該很容易明白。


Flask的request是全局變數(準確點說是線程局部變數,在線程內可以看做是全局變數),我沒有看Flask的form相關的代碼,所有這裡猜測一下,應該是NameForm這個類直接使用到了request.from這個POST表單數據的字典來驗證,又因為request在線程內是全局的,所有NameForm可以不通過參數就可用獲取到表單數據


可以先看看 @wang Harvey 和 @朱添一 的回答,再去看一下Flask-WTF中定義Form類的源碼,在Form類的注釋里,flask-wtf/form.py at bd12c97adad9f72b2872b2074c9067c8cd85ff35 · lepture/flask-wtf · GitHub 這兩行應該能夠清楚解釋題主的問題了。在此引用一下:

If ``formdata`` is not specified, this will use :attr:`flask.request.form` and
:attr:`flask.request.files`. Explicitly pass ``formdata=None`` to prevent this.

另外,可以看一下 formdata 在 `Form` 構造函數里被賦值的過程:

if formdata is _Auto:
if self.is_submitted():
formdata = request.form
if request.files:
formdata = formdata.copy()
formdata.update(request.files)
elif request.get_json():
formdata = werkzeug.datastructures.MultiDict(request.get_json())
else:
formdata = None


== 雖然@wang harvey 的答案非常棒,但我覺得完全沒解決樓主的問題啊…………不行,@不上最高票答主了……

樓主不知道 name 是怎麼出來的?那麼 name 是定義出來的。python是強類型,直接定義了 name 來存儲 form.name.data 的數據。

那麼 form.name.data 是怎麼出來的?form.name是你在 form 裡面定義的一個數據,它的 name(參見[文檔]Forms — WTForms 2.0.3dev documentation )就是 name(你自己定義的) 。

那麼我們把 form.name.data 傳給 name ,有什麼用呢?我們看它是重定向到 index.html 裡面的,那麼我們打開 index.html(為了這個我還專門打開好長時間都沒用的文件 checkout 了一把),我們可以看到

&Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!&

好的,那麼我們就完全明白了,是要在 index.html 判斷 name 的值,然後決定是輸出 name 還是輸出 stranger 的


試圖簡化解釋一下這個問題:

1、 當路由觸發 index 視圖函數。

form = NameForm() 這條語句創建一個form 實例對象,也就是說表單創建了。

2、如果是第一次訪問含有這個表單的頁面 也就是GET請求

if form.validate_on_submit() 這個判斷為假,下面的語句塊不執行。

3、如果是POST請求

這個時候,表單已經存在。

if form.validate_on_submit() 這個判斷為真,下面的語句塊要執行。

name = form.name.data 這條語句在POST的時候,取得form表單屬性值。

4、無論POST、還是GET

return render_template("index.html", form=form, name=name)

這條語句都要被執行。

如何取得form表單的值,請看flask.request。

class flask.requestAPI — Flask Documentation (0.10)

To access incoming request data, you can use the global request
object. Flask parses incoming request data for you and gives you
access to it through that global object. Internally Flask makes
sure that you always get the correct data for the active thread if you
are in a multithreaded environment.

This is a proxy. See Notes On Proxies for more information.

The request object is an instance of a Request
subclass and provides all of the attributes Werkzeug defines. This
just shows a quick overview of the most important ones.


FlaskForm是對WTForm里Form的包裝,它會自動從request.form里讀取表單的值

validate_on_submit是對WTForm里is_submit和validate的包裝,它會檢測是否是表單提交和表單是否有效

第一次get頁面,生成空的表單類的實例,validate_on_submit自然通不過,於是直接調到render頁面,然後點擊表單提交後相當於post頁面,表單有了內容,表單類的實例自然從request.form里讀取了name的值

Flask-WTForm是對WTForm的一層封裝,練建議直接看看WTForm的crash course會更清楚明白一些,WTform是基礎,它只是沒有默認提供CSRF防範以及其他一些高級一點的功能而已,但是對理解表格類在Flask里的使用很有幫助

另外其實可以自己寫HTML表格的代碼,然後從Flask里獲取表單數據,並進一步寫數據的檢驗,甚至寫CSRF Protection,就會明白這些表格類的作用,其實無非就是做一個封裝,簡化了代碼,更好地重用。但裡面的原理才是最重要的


@小鬧鐘 的答案已經很清楚了。不過下面我結合源碼簡單回答一下這個問題

flask_wtf中中的Form是繼承於wtforms中的Form的。相比較原始的Form,增加了從request自動傳入數據和一些加密的功能。本問題就是針對 Form對象怎麼自動載入了數據 提出的

在FlaskForm中有這麼一個wrap_formdata方法(其實是Meta類的方法,要更具體的需要看wtforms的實現,這裡就不走遠了)

def wrap_formdata(self, form, formdata):
if formdata is _Auto:
if _is_submitted():
if request.files:
return CombinedMultiDict((
request.files, request.form
))
elif request.form:
return request.form
elif request.get_json():
return ImmutableMultiDict(request.get_json())

return None

return formdata

可以看到返回的數據就是從request中的得到的數據。

所以說flask_wtf中的Form就這樣在初始化的時候就得到了request中的表單數據。

事實上假如你直接使用wtform中的Form的話,是這樣使用的:

# NameForm繼承與wtforms的Form,而不是flask_wtf中的Form
def index():
form = NameForm(request.form) # 這裡你需要顯式對NameForm傳入表單數據
...

最後記住一點,編程是沒有黑魔法的。好奇底下一層的實現就自己看源碼唄。


我也是零基礎看了點python的類、方法的介紹就上這本書。吐槽一下這本書的毛病,就是變數/函數命名規則太「普通」了,以至於會讓初學者暈頭轉向。所以我就把書里的代碼中的變數/函數名都改了一波,這樣一來,整個程序的邏輯就變得稍微清晰一點了。

類:

class NameForm(FlaskForm):
name_index_input = StringField("填寫你的ID:", validators=[Required()])
submit_index = SubmitField("提交")

視圖函數:

@app.route("/", methods=["GET", "POST"])
def index():
name_app = None #初始化用戶名變數
form_app = NameForm() #建立一個NameForm/FlaskForm的實例
if form_app.validate_on_submit():
name_app = form_app.name_index_input.data #將輸入的值置入用戶名變數
form_app.name_index_input.data = "" #清空頁面上的輸入欄
return render_template("index.html", form_index=form_app,
name_index_display=name_app) #將用戶名的值傳入頁面

然後index.html頁面:

{% block page_content %}
&
&你好,{% if name_index_display %}{{ name_index_display }}
{% else %}陌生人{% endif %}!&
& {{ wtf.quick_form(form_index) }}
{% endblock %}

如果name_app不為None,則顯示name_index_display此變數的值(當然是等於name_app啦)。

所以這裡的「name」有3種定義:

1、視圖函數內的變數(name_app);

2、表單的輸入框(name_index_input);

3、頁面的輸出值(name_index_display);

卻被作者用同一個名稱命名,對初學者真是滿滿的惡意……


之前遇到這個問題,一直不懂,今天又遇到了這個問題,就實驗了一下:

在頁面的name上輸入了「123」,在代碼中,驗證這個表單之前,數據就已經存在裡面了,所以應該是wtf預先就將數據放進去了,從wsgi的定義中,webapp有一個environ字典參數,包括header和data。所以就是這樣了。

新手,有錯誤請指正


以下是我的個人理解,首先第一次請求的時候,是get請求,渲染了一個空的表格頁面,當你填完了東西之後,點擊提交就已經是第二次請求,post請求,這時候就把表格的內容發送給伺服器,再進行賦值。


題主,我跟你用的同一本書。我是自學程序,看了基本的python語法就強擼這本書了。最開始真的html都不會。。。。。。然後到現在兩個月多了,不好意思才看到第六章第七章。。。


NameForm() 應該是繼承自 WTForm ,先了解一下這個東西。


推薦閱讀:

學習Flask需要什麼基礎?
關於學習Flask過程中Python虛擬環境的激活問題?
希望用flask作為中介讓python和js交互,大家有什麼比較好的實踐經驗沒?
《Flask Web開發》中程序的許可權為什麼用十六進位表示?
在python3下怎樣用flask-sqlalchemy對mysql資料庫操作?

TAG:Flask |