Flaskでバリデーションを体験

# はじめに
本記事では、Flaskにおけるバリデーション機能の実装を体験します。
フォームに値を入力し、記述通りに処理が実行されるかを確認します。

# 前提
- Flaskオブジェクトを生成できる
- リクエストを受け取る方法を知っている

# バリデーションとは
バリデーションとは、**データの入力に制限を付加する機能**のことです。

例えば、サイトの会員登録フォームに入力するパスワードに英数字大文字の使用が必須であったとします。
このフォームに全て小文字のアルファベットでパスワードを入力をした場合、条件を満たしていないことを警告するメッセージが表示されて送信が中断するはずです。
このときに実行される、条件を満たしているかどうかの確認処理がバリデーションです。

# 作成アプリイメージ
作成アプリでは、文字列とテキストの入力フォームを用意し、それぞれに入力制限を実装していきます。
- バリデーションOK
![通過.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1237378/3d712c41-6b9d-97bb-6f16-2f07e4a1e417.gif)

- バリデーションNG
![不通過.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1237378/a8af52ef-d4fb-3c4e-3c1b-1fec7d43db5e.gif)

# 実践
## フォルダ構成
今回作成するアプリのフォルダ構成を確認します。
```terminal
.
├── app.py
├── form.py
└── templates
    └── index.html
```

- 「app.py」
    - ルーティングを記述します。
- 「form.py」
    - ブラウザに表示するフォーム、バリデーションの処理を記述します。
- 「index.html」
    - ブラウザに表示する内容を記述します。


## ライブラリインストール
以下コマンドでライブラリをインストールします。
```terminal
pip install Flask Flask-WTF WTForms
```
- **Flask-WTF**
    - Flaskアプリで、ブラウザのフォームとバリデーションを実装するための機能を提供するライブラリ
- **WTForms**
    - バリデーションの中で使用する部品を提供するライブラリ

各ライブラリの違いを一言で説明することは難しいのですが、本記事内ではこのような使い分けをします。

## ファイル作成
### form.py
以下を記述します。
```form.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, ValidationError

class Form(FlaskForm):
    string = StringField('文字列')
    text = TextAreaField('テキスト')
    
    def validate_string(self, string):
        '''
        - 未入力 禁止
        - 文字数9文字以上 禁止
        - 「-」 禁止
        '''
        if string.data == '':
            raise ValidationError('値を入力してください。')
        
        if len(string.data) > 8:
            raise ValidationError('値を8文字以内で入力してください。')
        
        if '-' in string.data:
            raise ValidationError('「-」の入力は禁止されています。')
    
    def validate_text(self, text):
        '''
        - 未入力 禁止
        - 文字数20文字未満 禁止
        '''
        if text.data == '':
            raise ValidationError('値を入力してください。')
        
        if len(text.data) < 20:
            raise ValidationError('値を20文字以上入力してください。')
```
#### 説明
```python
from flask_wtf import FlaskForm
```
- **FlaskForm**
    - Formクラスに継承するオブジェクトです。
    - FlaskFormオブジェクトを継承したクラスは、バリデーションの機能が有効になります。


```python
from wtforms import StringField, TextAreaField, ValidationError
```
- **StringField**, **TextAreaField**
    - バリデーションの機能を持つ入力領域を定義します。
    - 他にも、IntegerField, RadioFieldなどがあります。
- **ValidationError**
    - バリデーションNGのときに発生させる例外です。
    - 例外が発生することが予想される場合、raise文でValidationErrorを発生させることで、引数にしていした値をブラウザ画面に表示させます。
   
```python
class Form(FlaskForm):
    string = StringField('文字列')
    text = TextAreaField('テキスト')
```
フォームを構成する入力フィールドを定義します。
各Fieldオブジェクトの引数に指定した値は、ブラウザ画面に入力項目名として使用できます。

```python
def validate_string(self, string):
    if string.data == '':
        raise ValidationError('値を入力してください。')
```
入力フィールドstringのバリデーションです。
バリデーションの関数名は「**validate_***」である必要があります。

バリデーションは、「入力フィールド名 + data」に対しての条件を記述する形で設定します。

バリデーションでTrueだった場合、つまり、制限された入力をした場合、ValidationErrorを発生させます。
ValidationErrorの引数に指定した値は、ブラウザ画面で表示させることができます。

### app.py
以下を記述します。
```app.py
from flask import Flask, request, render_template
from form import Form

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'

@app.route('/', methods=['GET', 'POST'])
def index():
    form = Form()
    if request.method == 'GET':
        title = '値を送信してください'
        return render_template('index.html', form=form, title=title)
    
    if request.method == 'POST':
        if form.validate_on_submit():
            title = 'バリデーションを通過しました'
            return render_template('index.html', form=form, title=title)
        else:
            title = 'バリデーションを通過できませんでした'
            return render_template('index.html', form=form, title=title)
        
if __name__ == '__main__':
    app.run(debug=True)
```
#### 説明
```python
from form import Form
```
バリデーションを設定したFormクラスをインポートします。

```python
if request.method == 'POST':
    if form.validate_on_submit():
        title = 'バリデーションを通過しました'
        return render_template('index.html', form=form, title=title)
    else:
        title = 'バリデーションを通過できませんでした'
        return render_template('index.html', form=form, title=title)
```
- **validate_on_submit()**
    - バリデーションの確認をします。
    - 問題なくバリデーションを通過できれば、Trueを返します。

また、Formクラスをインスタンス化したformオブジェクトをindex.htmlに渡しています。

### index.html
以下を記述します。
```django:index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>バリデーションテスト</title>
</head>

<body>
    <h1>{{ title }}</h1>
    <form action="" method="POST">
        {{ form.hidden_tag() }}

        {{ form.string.label }}:
        <div>
            {{ form.string }}

            {% for error in form.string.errors %}
            <span style="color: red">{{ error }}</span>
            {% endfor %}
        </div>

        {{ form.text.label }}:
        <div>
            {{ form.text }}

            {% for error in form.text.errors %}
            <span style="color: red">{{ error }}</span>
            {% endfor %}
        </div>

        <input type="submit" value="送信">
    </form>
</body>

</html>
```
#### 説明
- **hidden_tag()**
    - FlaskFormを継承したFormクラスをブラウザ画面に表示します。
- **form.string.label**
    - 入力フィールドstringの項目名を表示します。
    - この値は、form.pyのクラス定義の中で、StringFieldの引数に指定した値です。
- **form.string**
    - stringの入力フィールドを表示します。

```python
{% for error in form.string.errors %}
<span style="color: red">{{ error }}</span>
{% endfor %}
```
form.pyで発生させたエラーの内容を表示します。

# 終わりに
意外と簡単にバリデーションを実装できました。
他の入力フィールドについても試してみたいです。