Help us understand the problem. What is going on with this article?

FlaskFormで簡単にバリデーションする方法

More than 1 year has passed since last update.

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.

つまり、フォームを作りたいときに、それが複雑になっても、簡単に管理できるようにしてくれます。

ざっくり実装

フォームの部分だけを実装すると、下記のように書けます。
後ほど(サンプルアプリの実装で)、ブラウザで動かせるように、テンプレートなどを合わせて、実装します。

blog_form.py
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つ。

  • 1. FlaskFormを継承して、フォームの内容を定義したクラスに(先程、ざっくり実装したクラス)メソッドを追加
  • 2. フォームがPOSTで送られてきたら、そのフォームにvalidate_on_submit()を実行

守らなければいけないルールは2つ。

  • 上記の1を実装する際に、メソッド名は「validate_」+「フィールド名」で定義する
  • バリデーション失敗時は、raiseでwtformsValidationErrorを起こす

ざっくり実装

では、先程書いたフォームクラスにバリデーション処理を追記しましょう。

blog_form.py
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に下記のように書かれています。
ここで、新しく実装されたバリデーションに関するメソッドを見つけてまとめて、呼んでくれています。

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

フォームクラスを実装

まずは、フォームクラスを作ろう。
先程の内容にいくつか追記します。

blog_form.py
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って呼ぶこともある)を書いていきます。

app.py
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はかなりスッキリ書けます。
バリデーションに失敗した際のエラーメッセージが、表示されるように

templates/index.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というサービスを開発中です。ぜひ、使ってみて、レビューをください。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away