---
# System prepended metadata

title: Flask實作_ext_03_Flask-WTF_表單建立
tags: [flask, wtform, flask_ext, python]

---

# 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`是該屬性的第一個參數。

一切就緒，這時候執行專案會遇到錯誤訊息如下：
![](https://i.imgur.com/epheNZL.png)

這是因為`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()
```

重新執行專案，成功的渲染版面，如下圖：
![](https://i.imgur.com/Q28xf9f.png)

查看原始檔，轉譯的非常完美，如下圖：
![](https://i.imgur.com/c01aRbm.png)

故意讓email格式輸入錯誤，如下圖：
![](https://i.imgur.com/7KchZJs.png)
這錯誤訊息是因為欄位本身設置為email欄位，這是`html5`預設的卡控機制。

故意讓username是空值，異常訊息如下圖：
![](https://i.imgur.com/fnr0408.png)
透過`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|驗證輸入值不在列表中|
