Flaskは、pythonの軽量なwebフレームワークとして有名です。
そんなFlaskで、formの機能を実装する際によく使用されるのが、「WTForms」です。
このWTFormsを使えば、便利なバリデーション機能がを使えます。
そのバリデーション機能の紹介と、その機能を使ったサンプルアプリの実装をしていきます。
目次
- WTFormsの説明
- validate_on_submitメソッドの使い方
- サンプルアプリの実装
- つまったところ
- おまけ
対象
- FlaskFormを使っているがバリデーションのかけ方がわからない人
- flaskでアプリケーションを作っている人
- pythonでwebサービスを作ろうとしている人
環境
- python 3.6.0
- Flask 1.0.2
WTFormsの説明
まず、WTFormsを簡潔に説明します。
WTFormsの公式ページの文頭には、このように説明があります。
When you have to work with form data submitted by a browser view, code quickly becomes very hard to read. There are libraries out there designed to make this process easier to manage.
つまり、フォームを作りたいときに、それが複雑になっても、簡単に管理できるようにしてくれます。
ざっくり実装
フォームの部分だけを実装すると、下記のように書けます。
後ほど(サンプルアプリの実装で)、ブラウザで動かせるように、テンプレートなどを合わせて、実装します。
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField
class BlogForm(FlaskForm):
blog_title = StringField("タイトル")
description = TextAreaField('本文')
tags = StringField("タグ")
validate_on_submitメソッドの使い方
ここが、この記事のメインです。
バリデーション!!!
いざ、実際のアプリのフォームを実装しようと思い、バリデーションを考えると、意外と複雑さを求められることがあると思います。
(簡単なバリデーションなら、こちらを参照ください)
単純な「文字数制限」「必須項目」などだけでなく、特定の文字(「/」「.」「印象の悪い文字」など)を含めたくないとか、日付の制限とか、既存のデータとの重複などなど...場面によって、処理内容が変わります。
そんなときに使うのが、validate_on_submitメソッドです。
やることは2つ。
-
- FlaskFormを継承して、フォームの内容を定義したクラスに(先程、ざっくり実装したクラス)メソッドを追加
-
- フォームがPOSTで送られてきたら、そのフォームに**validate_on_submit()**を実行
守らなければいけないルールは2つ。
- 上記の1を実装する際に、メソッド名は「validate_」+「フィールド名」で定義する
- バリデーション失敗時は、raiseでwtformsのValidationErrorを起こす
ざっくり実装
では、先程書いたフォームクラスにバリデーション処理を追記しましょう。
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, ValidationError
class BlogForm(FlaskForm):
blog_title = StringField("タイトル")
description = TextAreaField("本文")
tags = StringField("タグ (カンマ区切りで入力してください。)")
def validate_blog_title(self, blog_title):
"""バリデーション内容:
- 未入力は禁止
- 文字数が10文字以上は禁止
- 「/」を含むことは禁止
"""
if blog_title.data == "":
raise ValidationError("タイトルを入力してください")
if len(blog_title.data) > 10:
raise ValidationError("タイトルは10文字以下にしてください。")
if "/" in blog_title.data:
raise ValidationError("タイトルに「/」は含められません。")
def validate_tags(self, tags):
"""バリデーション内容:
- 4つ以上は禁止
"""
tag_list = tags.data.strip().split(",")
if len(tag_list) > 3:
raise ValidationError("タグは3つ以下にしてください。")
wtformsの中身を観察
(飛ばしてもいい内容)
validate_on_submitメソッドをたどっていくと、wtformsのform.pyに下記のように書かれています。
ここで、新しく実装されたバリデーションに関するメソッドを見つけてまとめて、呼んでくれています。
def validate(self):
"""
Validates the form by calling `validate` on each field, passing any
extra `Form.validate_<fieldname>` validators to the field validator.
"""
extra = {}
for name in self._fields:
inline = getattr(self.__class__, 'validate_%s' % name, None)
if inline is not None:
extra[name] = [inline]
return super(Form, self).validate(extra)
サンプルアプリの実装
では、ここまでの内容を含めて、ブラウザ上で動かせるようにテンプレートや、コントローラー部分も含めて実装します。
ディレクトリ構成
├── app.py
├── blog_form.py
└── templates
└── index.html
フォームクラスを実装
まずは、フォームクラスを作ろう。
先程の内容にいくつか追記します。
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, ValidationError
class BlogForm(FlaskForm):
blog_title = StringField("タイトル")
description = TextAreaField("本文")
tags = StringField("タグ (カンマ区切りで入力してください。)")
def validate_blog_title(self, blog_title):
"""バリデーション内容:
- 未入力は禁止
- 文字数が10文字以上は禁止
- 「/」を含むことは禁止
"""
if blog_title.data == "":
raise ValidationError("タイトルを入力してください")
if len(blog_title.data) > 10:
raise ValidationError("タイトルは10文字以下にしてください。")
if "/" in blog_title.data:
raise ValidationError("タイトルに「/」は含められません。")
def validate_description(self, description):
"""バリデーション内容:
- 未入力は禁止
- 文字数が10文字未満は禁止
"""
if description.data == "":
raise ValidationError("本文を入力してください。")
if len(description.data) < 10:
raise ValidationError("本文は10文字以上にしてください。")
def validate_tags(self, tags):
"""バリデーション内容:
- 4つ以上は禁止
"""
tag_list = tags.data.strip().split(",")
if len(tag_list) > 3:
raise ValidationError("タグは3つ以下にしてください。")
コントローラーを実装
コントローラー(flaskではviewsって呼ぶこともある)を書いていきます。
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), "../"))
from flask import Flask, request
from flask import render_template
from blog_form import BlogForm
app = Flask(__name__)
app.config["SECRET_KEY"] = "sample1201"
@app.route("/", methods=["GET", "POST"])
def index():
form = BlogForm()
if request.method == "GET":
message = "フォームを送ってみよう!!"
return render_template("index.html", form=form, message=message)
if request.method == "POST":
if form.validate_on_submit():
message = "バリデーションを通ったよ\(^o^)/"
return render_template("index.html", form=form, message=message)
message = "バリデーションに失敗したよ(T_T)"
return render_template("index.html", form=form, message=message)
if __name__ == '__main__':
app.run(debug=True)
ビューを実装
htmlも書きます。(cssファイルを作るのが面倒だったので、直接書きました。)
FlaskFormを使うと、htmlはかなりスッキリ書けます。
バリデーションに失敗した際のエラーメッセージが、表示されるように
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>サンプル</title>
</head>
<body>
<h2>{{ message }}</h2>
<form method="POST" action="/">
{{ form.hidden_tag() }}
<div style="margin: 10px;">
{% for error in form.blog_title.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
{{ form.blog_title.label }}:
{{ form.blog_title(placeholder="バリデーションを実装") }}
</div>
<div style="margin: 10px;">
{% for error in form.description.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
{{ form.description.label }}:
{{ form.description(placeholder="validate_on_submit()を使えば、お手軽だよー") }}
</div>
<div style="margin: 10px;">
{% for error in form.tags.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
{{ form.tags.label }}:
{{ form.tags(placeholder="python,flask") }}
</div>
<div style="margin: 10px;">
<input type="submit" value="送信">
</div>
</form>
</body>
</html>
これであとは、app.pyを動かすだけ!
$ python app.py
つまったところ
あれ、バリデーションしてくれない
原因:
メソッド名が適切ではなかった。
上にも書きましたが、バリデーション内容を書くメソッド名は、「validate_」+「フィールド名」である必要があります。
全てのバリデーションをクリアしたはずなのに見えないバリデーションにはじかれる
原因:
form内に**{{ form.hidden_tag() }}**の記述を忘れていた。
なんで、これがないと怒られるのかまでは、調べていない。
おまけ
最後までお付き合いいただき、ありがとうございました。
少しでもお役に立てたのであれば、嬉しいです。
Renttleというサービスを開発中です。ぜひ、使ってみて、レビューをください。