概要
Flask + WTFormsなWebアプリでvalidationを行う。
各種built-inのvalidation利用と、エラーメッセージの表示、カスタムvalidationの作成などを取り扱う。
バージョン情報
- Flask==1.1.1
- Flask-WTF==0.14.2
サンプルコード
今回のコードを動かす上でベースにしたサンプルコード。
フォルダ構成。
├── app.py └── templates └── hello.html
app.py
import flask import flask_wtf import wtforms import wtforms.fields.html5 as wtforms5 from wtforms import validators app = flask.Flask(__name__) app.secret_key = 'nanika_secret_key_iretoite' @app.route('/', methods=['GET', 'POST']) def hello_world(): form = HelloForm() form.validate_on_submit() return flask.render_template( 'hello.html', form=form) class HelloForm(flask_wtf.FlaskForm): ''' ログイン画面フォーム ''' mojiretsu = wtforms.StringField('文字列', validators=[ validators.DataRequired(message='必須です'), validators.Length(min=3, max=20, message='3文字以上20文字以内で入力')]) password1 = wtforms.PasswordField('パスワード1', validators=[ validators.DataRequired(message='必須です'), validators.EqualTo('password2', message='パスワード入力不一致')]) password2 = wtforms.PasswordField('パスワード2')
templates/hello.html
{# エラーメッセージ表示用のマクロ #} {% macro render_error(field) %} <ul class=errors> {% for error in field.errors %} <li class="error">{{ error }}</li> {% endfor %} </ul> {% endmacro %} <form method="post"> {{ form.csrf_token }} <p> {{ form.mojiretsu(placeholder="必須文字列", size=20, required=False) }} {{ render_error(form.mojiretsu) }} <p> {{ form.password1(size=20) }} {{ render_error(form.password1) }} <p> {{ form.password2(size=20) }} {{ render_error(form.password2) }} <button>submit</button> </form>
こんな感じのコードを元に、各種validationの動きを書き加えていきます。
built-inのvalidatorたち
WTFormsには下記のvalidatorが用意されている。
validator | description |
---|---|
DataRequired | 必須チェック |
InputRequired | 入力必須チェック |
EqualTo | 入力内容が同じかどうか(パスワード2つ入力させて双方が同じかチェックするとか) |
AnyOf | 指定された値のいずれかと一致するか |
NoneOf | AnyOfの逆で指定された値のいずれとも一致しない |
Optional | 空白を許容する(空白の場合は他のチェックをスキップ) |
Length | 文字列長チェック |
Regexp | 正規表現チェック |
NumberRange | 数値範囲チェック |
メールアドレスチェック | |
IPAddress | IPアドレス(v4,v6どっちもいけます) |
MacAddress | mac addressチェック |
URL | URLチェック |
UUID | UUIDチェック |
DataRequiredとInputRequired
DataRequiredとInputRequiredは必須チェックをします。
Form
class HelloForm(flask_wtf.FlaskForm): ''' ログイン画面フォーム ''' mojiretsu = wtforms.StringField('文字列', validators=[ validators.DataRequired(message='必須です')])
template
{{ form.mojiretsu.field }} {{ form.mojiretsu(placeholder="必須文字列", size=20, required=False) }} {{ render_error(form.mojiretsu) }}
こんな感じで記述すると、空で入力した際にmessageで指定した「必須です」という文字列が画面に表示されます。
template側で required=False をしているのは、DataRequired/InputRequiredが指定されると自動でhtml form側にrequiredが指定されてsubmitできなくなるからです。普段実装する時はこの指定は入れません。
両者の違いはDataRequiredは値がFalseの場合はエラーになるけど、InputRequiredは入力さえあればセーフになるあたり。
例えば下記のようにIntegerFieldでそれぞれフィールドを指定します。
class HelloForm(flask_wtf.FlaskForm): ''' ログイン画面フォーム ''' data_req = wtforms.IntegerField('DataRequiredの例', validators=[ validators.DataRequired(message='必須です')]) input_req = wtforms.IntegerField('InputRequiredの例', validators=[ validators.InputRequired(message='必須です')])
template
<p> {{ form.data_req.label }} {{ form.data_req(placeholder="必須文字列", size=20, required=False) }} {{ render_error(form.data_req) }} <p> {{ form.input_req.label }} {{ form.input_req(placeholder="必須文字列", size=20, required=False) }} {{ render_error(form.input_req) }}
このフォームに0を入れると、DataRequiredは0 == Falseなのでチェックに引っかかります。
対してInputeRequiredはエラーになりません。
EqualTo
2つのフィールドの値が一致しているかチェック。
例としてパスワードフィールドを2つ用意し、validators.EqualToをpassword1に設定する。
password1 = wtforms.PasswordField('パスワード1', validators=[ validators.DataRequired(message='必須です'), validators.EqualTo('password2', message='パスワード入力不一致')]) password2 = wtforms.PasswordField('パスワード2')
template
<p> {{ form.password1(size=20) }} {{ render_error(form.password1) }} <p> {{ form.password2(size=20) }} {{ render_error(form.password2) }}
これでそれぞれに違う値を入力すると「パスワード入力不一致」とメッセージが表示される。
AnyOf
AnyOfは指定した値のどれかに一致しているかをチェックする。
mojiretsu = wtforms.StringField('文字列', validators=[ validators.AnyOf(values=['A', 'B', 'C'], message='A〜Cのいずれかであること')])
これでvaluesに指定した以外の文字列が来るとエラーになります。
Optional
Optionalは空白であればvalidationを通過できます。
上で実行したAnyOfは空白を許容しませんでしたが、下記のように記述すれば空白も許可されます。
mojiretsu = wtforms.StringField('文字列', validators=[ validators.Optional(), validators.AnyOf(values=['A', 'B', 'C'], message='A〜Cのいずれかであること')])
Optionalの引数はデフォルトで strip_whitespace=True になっています。
strip_whitespace=Falseを指定すると空白文字もチェックで引っかかるようになります。
mojiretsu = wtforms.StringField('文字列', validators=[ validators.Optional(strip_whitespace=False), validators.AnyOf(values=['A', 'B', 'C'], message='A〜Cのいずれかであること')])
NoneOf
NoneOfは指定した値のいずれとも一致しない条件です。
mojiretsu = wtforms.StringField('文字列', validators=[ validators.NoneOf(values=['A', 'B', 'C'], message='A〜Cのいずれでもないこと')])
Length
文字列の長さをチェックします。
mojiretsu = wtforms.StringField('文字列', validators=[ validators.Length(min=4, max=8, message='4〜8文字で入力')])
Regexp
正規表現チェックをします。
下記は数値とアルファベット小文字のみを許可する例。
mojiretsu = wtforms.StringField('文字列', validators=[ validators.Regexp('^[0-9a-z]+$', message='ルール違反')])
NumberRange
数値の範囲チェックをします。
kazu = wtforms.FloatField('数値', validators=[ validators.NumberRange(min=1, max=9, message='1〜9の数値を入力してください')])
template
{{ form.kazu() }} {{ render_error(form.kazu) }}
これで1.0〜9.0の範囲を外れる数値が入力されるとエラーになります。
ところで上記のサンプルで数値以外を入力する、エラーメッセージが下記のようになりました。
- Not a valid float value
- 1〜9の数値を入力してください
Not a valid float valueはFloat Fieldのデフォルトのエラーメッセージです。
このFieldが持つデフォルトメッセージをカスタマイズする方法がわかりませんでした。
参考ページ
https://github.com/wtforms/wtforms/issues/39
https://stackoverflow.com/questions/34422803/number-validation-between-range-0-and-100
独自クラス作っちゃえば一応できますが。
class MyFloatField(wtforms.FloatField): def __init__(self, label=None, validators=None, message="Not a valid float value", **kwargs): super(MyFloatField, self).__init__(label, validators, **kwargs) self.message = message def process_formdata(self, valuelist): if valuelist: try: self.data = float(valuelist[0]) except ValueError: self.data = None raise ValueError(self.gettext(self.message)) class HelloForm(flask_wtf.FlaskForm): ''' ログイン画面フォーム ''' kazu = MyFloatField('数値', message="数値じゃない", validators=[ validators.NumberRange(min=1, max=9, message='1〜9の数値を入力してください')])
custom validation
独自にバリデーションを定義してみます。
下記はtrueを示してるっぽい文字列であれば通過するvalidationをdefで定義した例。
from wtforms.validators import ValidationError def is_true(form, field): text = field.data.lower() if not text in ['true', 'ok', 'yes']: raise ValidationError('OKじゃない') class HelloForm(Form): mojiretsu = wtforms.StringField('文字列', validators=[is_true])
続いてClassで定義する場合。
書き方は本家wtformsのvalidators.pyを参考。
https://github.com/wtforms/wtforms/blob/2.2.1/wtforms/validators.py#L39
例として指定されたチェックボックス(BooleanField)のフィールド名を複数指定して、「すべてチェックされている」 or 「すべてチェックされていない」場合は通る処理を書いてみる。
class CheckMultipleField(object): def __init__(self, fieldnames, message): self.fieldnames = fieldnames self.message = message def __call__(self, form, field): fieldnames = self.fieldnames + [field.name] true_count = sum([form[name].data for name in fieldnames]) if true_count != 0 and true_count != len(fieldnames): raise ValidationError(self.message) class HelloForm(flask_wtf.FlaskForm): ''' ログイン画面フォーム ''' f1 = wtforms.BooleanField('F1', validators=[ CheckMultipleField(['f1', 'f2', 'f3'], message='全部チェックか全部未チェックのみ許可')]) f2 = wtforms.BooleanField('F2') f3 = wtforms.BooleanField('F3')
template
<p> {{ form.f1.label }} {{ form.f1() }} {{ render_error(form.f1) }} <p> {{ form.f2.label }} {{ form.f2() }} {{ render_error(form.f2) }} <p> {{ form.f3.label }} {{ form.f3() }} {{ render_error(form.f3) }}
改定履歴
Author: Masato Watanabe, Date: 2020-02-29, 記事投稿