見出し画像

[python]アプリケーション開発入門   WTFフォーム作成

Webフォームはアプリケーションにとって重要です。ユーザーが入力したデータをアプリケーションが受け取れるようになります。
投稿やお問い合わせなどフォームを利用することで、開発できる機能の幅がグッと広がります。

フォームはHTMLで以下のようなコードが必要になります。

<form method='POST'>
    <input name>
    <button type='submit'>送信する</button>
</form>

サーバー側では、以下の処理が必要になります。

Flask-WTFを利用することで、以下のことが楽にできるます。

・フォームのHTMLコードの生成
・入力されたデータのバリデーション

インストール

>  pip install flask-wtf

シークレットキー

WTF-Formにはシークレットキーの設定が必要です。
シークレットキーはユニークな文字列にしてください。
ウェブフォームにはCSRF(クロスサイトリクエストフォージェリ)攻撃のリスクがあります。

これを防ぐために、シークレットキーからユニークな文字列を生成し、外部からのフォームリクエストを除外します。

app = Flask(__name__)
app.config['SECRET_KEY'] = 'my_long_secret_key'
CSRF(クロスサイトリクエストフォージェリ)
悪意のある外部サイトのリンクをクリックすると、ユーザーが意図しないリスエストを送信させる。SNSサイトなどのログイン状態が狙われ、本人がリクエストしたとサービスに勘違いさせる。

フォームクラス

FlaskFormを継承してフォームクラスを作成します
フォームのフィールドはフォームクラスのクラスメンバーとして定義します。フィールドは入力タイプに対応したものが、wtformsからインポートできます。
ユーザー情報を入力するUserFormを定義します。

# form.py
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, RadioField, SubmitField
from wtforms.validators import DataRequired, NumberRange

class UserForm(FlaskForm):
   name = StringField('名前', validators=[DataRequired(message='名前を入力してください')])
   age = IntegerField('年齢', validators=[DataRequired(), NumberRange(min=10, message='10才未満はご利用できません(現在)')])
   sex = RadioField('性別', choices=[('0', '男性'), ('1', '女性')], default='0')
   submit = SubmitField('送信')


# GETの場合の、form.data
{'name': None, 'age': None, 'sex': '0', 'submit': False, 'csrf_token': None}

# POSTの場合の、form.data (ユーザーの入力データ)
{'name': 'john', 'age': 11, 'sex': '0', 'submit': True, 'csrf_token': 'IjBhMmE1ODYyMjdjNzlmMjAxNmFhMDVjMjk3NTgyNmY4NTYwZjNkNGQi.YPmYGA.p8O6Ntc1Y0IAJkmfA8JwaNckKig'}

Flask-Bootstrapのインストール

フォームを生成させる前に、フロントエンドライブラリ BootstrapをFlaskで使えるように、Flask-Bootstrapをインストールします。
Bootstrapを使用すると見た目を簡単に整えることができます。

> pip install flask-bootstraps

Flask-Bootstrapの初期化

Flask-Bootstrapを使用するには初期が必要です。Flaskアプリケーションインスタンス生成時に、Bootstrapも初期化するばOKです。

from flask_bootstrap import Bootstrap

app = Flask(__name__)
bootstrap = Bootstrap(app)

フォームのレンダリング

UserFormのレンダリングするテンプレートファイルです。
formはUserFormのインスタンスです。

{% extends "layout.html" %}

{% block title %}ユーザー情報入力{% endblock %}

{% block page_content %}
   <div class="container">
       <div class="page-header">ユーザー情報を入力してください</div>
       <form method="POST">
           {{ form.csrf_token }}

           <table class="table">
               <tr>
                   <th scope="col">{{ form.name.label }}</th>
                   <td>{{ form.name() }}</td>
               </tr>
               <tr>
                   <th scope="col">{{ form.age.label }}</th>
                   <td>{{ form.age() }}</td>
               </tr>
               <tr>
                   <th scope="col">{{ form.sex.label }}</th>
                   <td>{{ form.sex() }}</td>
               </tr>
               {% for _ , v in form.errors.items() %}
                   <div class="bg-danger">{{ ", ".join(v) }}</div>
               {% endfor %}
           </table>

           {{ form.submit() }}
       </form>
   </div>
{% endblock %}

{% block styles %}
   {{ super() }}
   <style>
       #sex {
           list-style: none;
           display: flex;
       }

       #sex li {
           width: 120px;
           text-align: center;
           text-decoration: none;
       }
   </style>
{% endblock %}

csrfを防ぐcsrf_tokenを埋め込むので必須です。

csrf_tokenの代わりに、hiddenFieldをまとめてレンダリングするhidden_tag()でも良いです。

{{ form.フィールドメンバ() }}で、フィールドのhtmlコードを埋め込みます。
print()すると、どのような出力がされるかわかります。

print(form.name())
=> <input id="name" name="name" required type="text" value="">
クラスメンバーをコールしていて変に感じますが、これは親クラスのFieldが特殊メソッド__call__を実装しているからです。
__call__メソッドを実装するとインスタンスをメソッドのようにコールすることができます。


画像1

ユーザー情報入力フォーム画面

Flask-Bootstrapのwtf.quick_form(form)を使うと、より簡単にBootstrapスタイルでフォームがレンダリングされます。

{% extends "layout.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}ユーザー情報入力{% endblock %}

{% block page_content %}
   <div class="container">
       <div class="page-header">ユーザー情報を入力してください</div>

       {{ wtf.quick_form(form) }}
   </div>
{% endblock %}

画像2


フォーム処理するビュー関数

ユーザー情報のフォームを受け取るAPIを追加します。

from flask import Flask, render_template, request
from form import UserForm

# application インスタンスの生成
app = Flask(__name__)

# secret keyの設定
app.config['SECRET_KEY'] = 'my_long_secret_key'

# 追加API
@app.route('/name', methods=['GET', 'POST'])
def name():
   form = UserForm()

   if request.method == 'GET':
       return render_template('user.html', form=form)

   if request.method == 'POST':
       if form.validate_on_submit():
           return render_template('user-complete.html',
                                  name=form.name.data,
                                  age=form.age.data,
                                  sex='男性' if form.sex.data == '0' else '女性')

   return render_template('user.html', form=form)

HTTPリクエストがGETの場合、formをテンプレートに渡しています。
HTTPリクエストがPOSTの場合、formには入力値が入っていて、その後の処理をする前に、値を検証する必要があります。
validate_on_submit()はフォームにセットされた値をFormFieldに設定したをvalidatorに従って検証します。
検証に失敗するとFlaseが返り、form.errorsにエラーがセットされます。

検証に成功したら、フォーム値を登録成功画面に渡します。

{% extends "layout.html" %}
{% block title %}ユーザー情報入力{% endblock %}
{% block page_content %}
   <div class="container">
       <h1 class="page-header">こんにちは、{{ name }}さん!</h1>
       <div class="bg-success">下記の内容で登録しました</div>
       <table class="table">
           <tr>
               <th scope="col">名前</th>
               <td>{{ name }}</td>
           </tr>
           <tr>
               <th scope="col">年齢</th>
               <td>{{ age }}</td>
           </tr>
           <tr>
               <th scope="col">性別</th>
               <td>{{ sex }}</td>
           </tr>
       </table>
   </div>
{% endblock %}

画像3

検証成功後の画面

画像4

検証失敗の画面

まとめ

・FlaskでFormを作成するにはFlaskWTFFormを使う
・WTFormを使うにはCSRF対策としてシークレットキーを設定する
・FlaskFormフォームを継承してフォームクラスを作る
・フォームインスタンスからフィールドのHTMLコードを生成する
・POSTされたフォーム値の検証には、validate_on_submit()を使う

参考

CSRF(クロスサイトリクエストフォージェリ)の意味とその対策
FlaskWTForm
Flask Web Development: Developing Web Applications with Python (English Edition)

この記事が気に入ったらサポートをしてみませんか?