# Flask實作_ext_03_Flask-WTF_表單建立
###### tags: `flask` `flask_ext` `python` `wtform`
## 說明
:::danger
官方文件:
* [Flask-wtf](https://flask-wtf.readthedocs.io/en/stable/)
:::
表單類元件在網頁設計中是不可或缺的一個元素,雖然可以透過`request.form`來取得前端的表單,但不免俗的還是要自己硬刻一堆重覆性質的Html。
`Flask-wtf`除了可以減化這類工作之外,它還預設有避免`CSRF`攻擊的功能,這對資訊安全上有絕對的幫助。
## 安裝
```python=
pip install flask-wtf
```
透過`pip`安裝`flask-wtf`會自動將相依套件`WTForms`下載安裝,實作上必需要設置Flask參數`SECRET_KEY`,否則會報錯,如下設置:
```python
from flask import Flask
app = Flask(__name__)
app.config('SECRET_KEY') = 'your key values'
```
## 範例
`Flask-wtf`是一個類別函數,一切都從繼承`FlaskForm`開始,再各別從`wtforms`去引入需求的欄位類別以及驗證。
建立一個新的Python文件,檔案名稱為`view_form.py`,相關作業說明如註解,如下:
```python=
# 引入flask_wtf
from flask_wtf import FlaskForm
# 各別引入需求欄位類別
from wtforms import StringField, SubmitField
from wtforms.fields.html5 import EmailField
# 引入驗證
from wtforms.validators import DataRequired, Email
# 從繼承FlaskForm開始
class UserForm(FlaskForm):
username = StringField('UserName', validators=[DataRequired(message='Not Null')])
email = EmailField('Email', validators=[DataRequired(message='Not Null')])
submit = SubmitField('Submit')
```
第11行:建置一個字串欄位,並利用`validators`來設置欄位的驗證,驗證項目為`DataRequired`,代表必填,最後是當驗證失敗的時候就出現驗證失敗的訊息`message`
現在讓我們加入一個啟動Flask的Python文件,命名為`flask_start.py`,如下:
```python=
from flask import Flask, render_template
# 引入form類別
from view_form import UserForm
app = Flask(__name__)
@app.route('/user', methods=['GET', 'POST'])
def user():
form = UserForm()
# flask_wtf類中提供判斷是否表單提交過來的method,不需要自行利用request.method來做判斷
if form.validate_on_submit():
return 'Success Submit'
# 如果不是提交過來的表單,就是GET,這時候就回傳user.html網頁
return render_template('user.html', form=form)
if __name__ == '__main__':
app.debug = True
app.run()
```
第14行:回傳渲染網頁`user.html`,並傳遞了一個表單物件參數`form`,這讓我們可以在Html文件中可以利用`jinja2`的語法來操作它
部份註解寫在程式碼上,其中`form.validate_on_submit()`是由`flask_wtf`提供的method,可以直接確認`POST`與欄位驗證以及最重要的`CSRF`。
最後在`templates`內加入Html文件,命名為`user.html`
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="POST" action="{{ url_for('user') }}" >
{{ form.hidden_tag() }}
<p>{{ form.username.label }} {{ form.username }}
{% if form.username.errors %}
{% for error in form.username.errors %}
{{ error }}
{% endfor %}
{% endif %}
</p>
<p>{{ form.email.label }} {{ form.email }}</p>
<p>{{ form.submit.label }} {{ form.submit }}</p>
</form>
</body>
</html>
```
第9行:這是必填的一個項目,沒有填會出現異常
第10行:`{{ form.username.label }}{{ form.username }}`,`form`是由後端拋到前端來的一個參數,`username`是我們在Form中所設置的屬性名稱,而`label`是該屬性的第一個參數。
一切就緒,這時候執行專案會遇到錯誤訊息如下:

這是因為`flask_wtf`預設需要設置密碼,也是為了避免一開始所說的`CSRF`攻擊。
在`flask_start.py`上,加上`app.config['SECRET_KEY']`的參數設置,如下:
```python=
...上略
if __name__ == '__main__':
app.debug = True
app.config['SECRET_KEY']='your key'
app.run()
```
重新執行專案,成功的渲染版面,如下圖:

查看原始檔,轉譯的非常完美,如下圖:

故意讓email格式輸入錯誤,如下圖:

這錯誤訊息是因為欄位本身設置為email欄位,這是`html5`預設的卡控機制。
故意讓username是空值,異常訊息如下圖:

透過`field.errors`來取得錯誤訊息之後呈現!
## 總結
利用一個簡單的範例來認識`flask-wtf`,透過參數的傳遞將物件由後端拋到前端,再搭配`jinja2`語法來操作,簡化的程度大到令人感到不可思議,原來開發一個表單可以這麼快速,但這還不足以驚豔,因為下一話我們會搭配`flask-bootstrap`來更快速的開發表單,下面有其它的部份,是簡單的列出`WTForms`支援的欄位清單以及驗證函數,更詳細的當然是請您參閱官方文件說明。
**Flask-WTF_表單建立_進階:**[Flask實作_ext_04_Flask-WTF_表單建立_進階](https://hackmd.io/s/r1pPM7ZXz)
## 其它
### WTForms支援欄位清單
|類型|說明|
|-----|-----|
|StringField|字串欄位|
|TextAreaField|多行字串欄位|
|PasswordField|密碼欄位(出現\*)|
|HiddenField|隱藏欄位|
|DateField|日期欄位datetime.date|
|DateTimeField|日期時間欄位datetime.datetime|
|IntegerField|整數欄位|
|DecimalField|精度數值欄位decimal.Decimal|
|FloatField|浮點數欄位|
|BooleanField|布林欄位|
|RadioField|單選radio|
|SelectField|下拉表單|
|SelectMultipleField|下拉表單(可多選)|
|FileField|文件上傳欄位|
|SubmitField|提交表單按鈕|
|FormField|Form中有Form|
|FieldList|?|
### WTForms驗證函數
|函數|說明|
|-----|-----|
|Email|驗證電子郵件欄位|
|EqualTo|比較兩欄位數值是否相同|
|IPAddress|驗證ip位址|
|Length|驗證字串長度|
|NumberRange|驗證數值區間|
|Optional|?|
|DataRequired|必填欄位|
|Regexp|以正則式驗證|
|URL|驗證網址|
|AnyOf|驗證輸入值在列表中|
|NoneOf|驗證輸入值不在列表中|