1. 概述
查看wtforms代碼樹fields目錄的core.py,會發現在文件開頭有這樣的語句:
__all__ = ( 'BooleanField', 'DecimalField', 'DateField', 'DateTimeField', 'FieldList', 'FloatField', 'FormField', 'IntegerField', 'RadioField', 'SelectField', 'SelectMultipleField', 'StringField', )
這個表示當前文件在被Import的時候,能夠導入的所有方法。上面的這些,除了FieldList和FormField這兩個表單字段我們平時使用得比較少以外,
其他的我們或多或少都使用過了。而且通常情況下,上面的那些基本的字段已經完全符合我們要求了,除非特殊情況,否則,我們根本沒有機會使用到。
關於FormField的使用,估計大家在使用的時候就挺郁悶的。要么就是報錯,要么就是渲染出來的頁面出現格式問題,根本沒法使用。看官網或許幫助不大,
查找中文資料又很少有幫助的,所以就會很困惑。
不過,我最近遇到的一個問題發現用FormField非常的炫酷,而且特別特別有意思。
2. 遇到的問題
最近遇到日常工單系統,大概有13中常規的工單,每種都是不一樣的表單。表單創建之后還有對應List、Get以及處理。所以后面大概有30中不同的視圖。
如果像之前那種方式,每個表單對應一個HTML,那樣會導致頁面太多。
另外就是,主頁面空間有限,不能讓這些表單頁面堆積在一起。所以,還是希望這13種表單服用同一個頁面,根據用戶選擇的類型的不同來做不同的展示。
但是,怎樣把這13種表單融合在一個HTML文件中,並且不影響到每個表單每個字段的檢查。
這塊可能要多說下,大家知道我們在使用wtforms Form表單元類派生出自己的表單類的時候,可能對一些必須的字段定義些validator方法,這樣在
用戶提交表單的時候wtforms自身為我們做了對應的檢查。
很顯然,我們在做到這點肯定是使用某種隱藏繼續,在需要顯示的時候才顯示對應的頁面,其他情況下都是隱藏的。但是,這塊又有一個問題就是,
如果隱藏了,哪些隱藏了的定義了validator的表單字段會在表單提交之后直接報錯。
解決了這些問題,還有一個問題就是查看頁面詳情,用戶創建一個表單,期待的時候查看詳情也看到這塊的內容,而不希望看到其他的表單。所以,
這塊又需要對應13種不同的頁面來展示不同的表單類型的數據。這塊能不能用一種更優雅的方式解決?
概括下遇到的問題,大致如下:
- 需要展示的表單類型過多,而且后面可能會有新的需求加入,所以需要盡可能的簡潔以及可擴展的方式來呈現表單,盡可能把所有表單結構放在一個頁面中。
- 為了可擴展性,表單類需要繼承自wtforms Form類,但是如何確保隱藏的表單中必選字段對當前表單的影響。
- 給用戶展示對應創建的表單時,能否用一種通用的方式來展示這13種字段完全不太相同的表單結構,並且代碼盡可能具有良好的可擴展性。
3. 大致的解法
由於現在比較晚,所以就不給出詳細的解決方案,這塊會大致講下解決方法。不過,相信大家能夠從中找到靈感。
針對問題1:
使用wtforms 的FormField,對13種不同方格的表單,創建13種派生自wtforms Form基類的派生類。定義一個功能類,功能類中有13個字段都是FormField類型,
分別對應這13個表單結構。這樣就能夠做到對單個表單結構的精確控制,即使某個表單內容做了更改也不會影響到頁面的布局以及其他模塊。
頁面的顯示,需要重構flask admin系統默認模板admin/file/form.html的內容。具體,對於整個表單調用lib.render_form_fields(form),改為是
對每個表單結構調用lib.render_form_fields(form.XXX_field)。
這樣,能夠確保渲染出來的FormField字段不會出現格式有問題或者亂碼。
針對問題2:
重載派生類表單的validate方法,這塊有些技巧,具體可以看下我這塊使用的代碼:
def validate(self): monitor_type = self.monitor_type.data hidden_field_val = str(monitor_type.get('hidden_field', '')) fields = self._fields extra = {} success = True for name, field in iteritems(fields): if name == hidden_field_val: for _name, _field in iteritems(field._fields): if not _field.validate(field._fields): success = False return success
這塊有個技巧,就是在表單中使用隱藏表單來表征當前用戶選擇的表單類型。這樣我們在后端做驗證的時候,只
針對當前表單元素做驗證,而不是表單功能類的13中表單結構。這樣,隱藏的表單中的必選字段就不會影響到當前表單,因為我們只會檢查當前顯示表單的結構。
上述接粗的代碼就是取出隱藏的表單字段的值。不過有一點,由於隱藏表單字段的值是不能用戶輸入的,只能我們自己用jQuery代碼對它進行設置。
這樣,這塊的問題就解決了。
針對問題3:
這塊有個比較有意思的技巧,定義數據庫表結構的時候要盡可能的與表單結構無關,這樣的話可擴展性更好些。否則,一個是表結構字段太多,太龐大了,
另外就是可擴展性不太好。所以,我這塊是使用data字段直接表征整個表單所有數據,每次提交表單的時候,直接獲取表單的字典結構:
這塊使用了表單的data屬性,data屬性會直接返回一個字典結構。我們在存儲到數據庫中直接使用json.dumps(form.data),把表單值序列化成字符串並存到數據庫表結構data
字段中。用戶讀取的時候,在把對應字段json.loads回來,並展示給用戶就可以了。
如何給用戶展示的時候,不需要定義很多HTML頁面,而只使用一個,這塊的關鍵是巧用Form類,我們可以根據表單類型獲取到對應的表單派生類,並使用它來定義表單對象,
這塊可以使用預定義的map就可以了,而且可擴展性非常好。
定義好表單對象之后,從數據庫中load數據,把data字段的值json化之后傳給表單。然后,我們直接使用admin/file/form.html 模板渲染頁面問題就完全解決了。
4. 小結
當使用一種方法復雜到想讓你放棄的時候,一定要堅持,因為一定還有其他路要走。因為,你可能之前走錯了方向,換個角度思考,你會獲得很多意想不到的收獲。