Flask入門|カスタムバリデータを理解しよう【チャプター4-05】

一つ前のページではフォームのバリデーションについて学習しました。
今回は カスタムバリデータ について見ていきましょう。
Chapter1:Flask入門編
Chapter2:Jinja2入門編
Chapter3:フィルター編
Chapter4:フォーム編
・Chapter4-1:HTTPとは何か
・Chapter4-2:フォームの基本を理解しよう
・Chapter4-3:WTFormsを理解しよう
・Chapter4-4:フォームのバリデーションを理解しよう
・Chapter4-5:カスタムバリデータを理解しよう ◁今回はここ
・Chapter4-6:テンプレートマクロを理解しよう
・Chapter4-7:Flask-WTFを理解しよう
Chapter5:データベース編
Chapter6:Flaskの便利機能編
Chapter7:アプリ開発編
フォームを作成する際に、入力内容の正しさをチェックする「バリデーション」はとても重要です。
WTFormsには DataRequired
や Length
など、よく使うバリデーションが標準で多数用意されていますが、「特定の値を拒否したい」「文字列の一部に制限をかけたい」など、より柔軟なチェックが必要になる場面も多くあります。
そんなときに使えるのが「カスタムバリデータ」です。
この記事では、WTFormsで自作のバリデーション処理を定義する方法を学んでいきましょう。
本記事は 有料記事(100円)ですが、現在は期間限定で 無料公開中 です。
独自バリデーションを作成する「カスタムバリデータ」とは?
カスタムバリデータ とは、WTFormsの標準バリデーションでは実現できないような独自のチェックロジックを、自分で定義してフォームに組み込む機能です。
たとえば:
- 禁止された単語が含まれていないかをチェックしたい
- 特定の文字列で始まる入力を許可したい
- データベースの値と照らし合わせて重複を確認したい
といったニーズに対して、柔軟に対応することができます。
カスタムバリデータは、フォームクラスの中に特定の名前のメソッドを定義することで使えます。
そのメソッドは、対象フィールド名に応じて命名する必要があります。
validate_フィールド名の定義ルールと構文例
以下が、カスタムバリデータの基本的な構文です。
def validate_フィールド名(self, フィールド): if チェック条件: raise ValidationError("エラーメッセージ")
validate_フィールド名
→ 対象フィールドの直前にvalidate_
を付けることで、そのフィールドに対するバリデーションとして認識されます。フィールド
(引数)
→ 実際のフォームの入力値が格納されているフィールドオブジェクトです。ValidationError
→ 条件を満たさない場合にこの例外を発生させ、エラーメッセージとして表示する。
例えば、ユーザー名に「admin」という名前を使わせたくない場合は以下のように使用します。
from wtforms import Form, StringField from wtforms.validators import DataRequired, ValidationError # 必要な関数をインポート class MyForm(Form): # Formクラスを継承したMFformクラスの定義 username = StringField('ユーザー名', validators=[DataRequired('名前は入力必須です')]) # 組み込みバリデータ def validate_username(self, field): # usernameフィールドにカスタムバリデータを設定 if field.data == 'admin': # フィールドのデータが「admin」であったなら raise ValidationError('このユーザー名は使用できません。')
- validate_フィールド名:チェック対象のフィールドに対応した関数名にする
- field:検証対象のフィールドインスタンス(値は
field.data
で取得) - ValidationError:チェックに失敗したときにエラーとして出す例外クラス
Flaskアプリでカスタムバリデータを活用する例
以下はFlaskアプリ内でカスタムバリデータを使ってフォームを検証する例です。
コピーして保存・実行し、ユーザー名に「admin」や「Admin」、その他任意の名前を入力して挙動を確認しましょう。
from flask import Flask, request from wtforms import Form, StringField from wtforms.validators import DataRequired, ValidationError app = Flask(__name__) # フォーム定義 class MyForm(Form): username = StringField('ユーザー名', [DataRequired()]) # カスタムバリデータの定義 def validate_username(self, field): if field.data.lower() == 'admin': # 小文字変換したフィールドのデータが「admin」であったなら raise ValidationError('このユーザー名は使用できません。') @app.route('/', methods=['GET', 'POST']) def index(): if request.method == 'POST': # フォームにPOSTデータを渡す form = MyForm(request.form) # 検証処理(バリデータがこのときに実行される) if form.validate(): return '登録成功!' else: # エラー内容を表示 return f'エラー: {form.errors}' # 初期表示(GET) return ''' <form method="post"> ユーザー名: <input type="text" name="username"> <input type="submit" value="送信"> </form> ''' # 実行 if __name__ == '__main__': app.run(debug=True)
MyForm
クラスにはvalidate_username
という関数があり、これはusername
フィールドに対応しています。form.validate()
を呼び出すことで、全バリデータ(標準+カスタム)が実行されます。ValidationError
が発生した場合は、自動的にform.errors
にメッセージが格納されます。
カスタムバリデータを使う際の注意点まとめ
- 関数名は「validate_フィールド名」と正確に書かなければ機能しません。
- バリデーション失敗時は
ValidationError
を raise する必要があります。 - 同じフォームに複数のカスタムバリデータを追加することも可能です。
カスタムバリデータの活用で柔軟な入力チェックを実現
この章では「カスタムバリデータ」を実装する方法を学びました。
- 独自ルールを関数で定義できる
validate_フィールド名(self, field)
の形式で作るValidationError
を発生させることでチェック結果を反映form.validate()
を呼び出すと、自動でバリデーションが走る
カスタムバリデータは標準バリデータでは対応しきれないニーズに応える強力な手段です。
次章ではテンプレートのマクロを使って、フォームの表示もさらに効率的にしていきましょう。
練習問題:18歳未満をブロックするカスタムバリデーションを実装しよう
WTFormsでは InputRequired
や NumberRange
のような標準のバリデーションを使うことができますが、それだけでは対応できない独自のチェックが必要になります。
今回は、「18歳未満のユーザーは登録できない」という独自のルールを実現するために、カスタムバリデータを使ってフォームクラスを完成させてください。
以下のコードの空欄部分に入るロジックを記述してください。
この問題の要件
以下の要件に従ってコードを完成させてください。
Form
クラスを継承したAgeForm
というフォームクラスを定義IntegerField
を使用し、age
という名前の年齢入力フィールドを作成age
フィールドには以下の2つのバリデータを設定すること:InputRequired()
:入力必須NumberRange(min=0)
:マイナスの数値を許可しないvalidate_age(self, field)
というカスタムバリデータを定義- 入力された値が18未満の場合、
ValidationError
を発生させること - エラーメッセージは「18歳未満の方は登録できません。」
ただし、以下のコードをベースとし、中間部分を埋めて作成すること。
from flask import Flask, request from wtforms import Form, IntegerField from wtforms.validators import InputRequired, NumberRange, ValidationError app = Flask(__name__) # --- ここから穴埋め部分 --- # WTFormsを使ってフォームクラスを作成 class AgeForm(Form): # カスタムバリデータの定義 def validate_age(self, field): @app.route('/', methods=['GET', 'POST']) def index(): # --- ここまでが穴埋め部分 --- # フォームの初期表示(GET) return ''' <form method="post"> 年齢を入力してください:<br> <input type="number" name="age"><br> <input type="submit" value="送信"> </form> ''' if __name__ == '__main__': app.run(debug=True)
この問題を解くヒント
1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。
- ヒント:コードの構成を見る
-
正解のコードは上から順に以下のような構成となっています。
1:WTForms の
Form
クラスを継承したフォームクラス(AgeForm
)を定義2:クラスの中に、
age
という名前の整数用フィールドを作成
→ ラベルは「年齢」、バリデーションとして以下の2つを設定
- 入力が空でないかを確認する(入力必須)
- 入力された値が 0 以上であることを確認する(マイナス禁止)3:
validate_フィールド名
というルールに従って、validate_age
という関数を定義する
→ この関数は自動的にage
フィールドに対する検証として使われる4:入力された年齢(
field.data
)が18歳未満かどうかをチェック
→ 条件に合わない場合は、ValidationError
を使って「18歳未満の方は登録できません。」というメッセージを返す5:作成したフォームクラスを Flask アプリ内の
index
関数で使う
→ 入力された値が問題なければ「登録が完了しました!」と表示し、問題があれば赤文字でエラーを表示するようにする
このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。
正解コード
例えば、以下のようなプログラムが考えられます。
- 正解コード
-
# WTFormsを使ってフォームクラスを作成 class AgeForm(Form): # ユーザーに年齢を入力させるフィールド # InputRequired: 入力必須 # NumberRange: 0歳以上の数値に限定(マイナス値の拒否) age = IntegerField('年齢', [InputRequired(), NumberRange(min=0)]) # 【今回学習した内容】カスタムバリデータを使って独自のルールを追加する # この関数名は validate_フィールド名 という形式でなければならない def validate_age(self, field): # 入力された年齢が18未満であればエラーを出す if field.data < 18: # ValidationError を発生させることでフォームにエラーが表示される raise ValidationError('18歳未満の方は登録できません。') @app.route('/', methods=['GET', 'POST']) def index(): form = AgeForm(request.form) # POSTされたフォームデータを渡す if request.method == 'POST': if form.validate(): # バリデーションを実行(カスタムバリデータも含む) return '登録が完了しました!' else: # エラーがあれば、そのメッセージをHTMLとして表示する errors = ''.join([f'<p style="color:red;">{msg}</p>' for msg in form.age.errors]) return f''' {errors} <a href="/">戻る</a> '''
正解コードの詳細解説
正解コードをブロックごとに分割して解説します。
- 正解コードの詳細解説
-
フォームクラスの作成
class AgeForm(Form):
- これは フォームの設計図 を作る部分です。
Form
は WTForms で使う「フォームの基本クラス」。AgeForm
は、年齢を入力させるフォームの名前です。
年齢フィールドの定義
age = IntegerField('年齢', [InputRequired(), NumberRange(min=0)])
IntegerField
は「整数」を入力させるフィールド。'年齢'
はラベルとして表示される文字列。- バリデーション(検証ルール)として以下を設定しています:
NumberRange(min=0)
:マイナスの年齢(例:-3)は許可しないInputRequired()
:空欄で送信されたらエラーにする(入力必須)
カスタムバリデータ関数の定義
def validate_age(self, field):
validate_フィールド名
という名前で関数を定義すると、そのフィールド専用の検証処理が追加できます。- ここでは
age
というフィールドに対して使うので、関数名はvalidate_age
。
年齢が18歳未満の場合にエラーを出す
if field.data < 18: raise ValidationError('18歳未満の方は登録できません。')
field.data
は、実際にユーザーが入力した年齢。- それが18より小さければ
ValidationError
を発生させる。 ValidationError
に渡した文字列は、そのままエラーメッセージとして画面に表示されます。
Flaskのルーティング設定
@app.route('/', methods=['GET', 'POST']) def index():
- Flaskアプリのトップページ(
/
)にアクセスが来たとき、この関数が実行される。 methods=['GET', 'POST']
によって、表示と送信の両方に対応。
フォームオブジェクトを生成
form = AgeForm(request.form)
- ブラウザから送られてきたフォームのデータ(
request.form
)を使って、AgeForm
を作ります。 - これにより、
age
の値やバリデーションが使えるようになります。
POST送信時にバリデーションを実行
if request.method == 'POST': if form.validate(): return '登録が完了しました!'
- フォームが送信されたら、
form.validate()
を呼びます。 - このとき、
InputRequired
やNumberRange
だけでなく、自分で書いたvalidate_age()
も実行されます。 - エラーがなければ「登録が完了しました!」と表示。
エラーがあった場合の処理
else: errors = ''.join([f'<p style="color:red;">{msg}</p>' for msg in form.age.errors]) return f''' {errors} <a href="/">戻る</a> '''
form.age.errors
には、年齢フィールドのエラーメッセージが入っています。- それを1つずつ取り出して、赤文字で表示しています。
- 「戻る」リンクで再入力できるようにしています。
FAQ|Flaskのカスタムバリデータ活用でよくある質問
- Q1. カスタムバリデータは1つのフォーム内に複数定義できますか?
-
はい、可能です。バリデートしたいフィールドごとに
validate_フィールド名()
という関数を追加すれば、複数の独自チェックを同時に機能させることができます。
- Q2. カスタムバリデータの中で他のフィールドの値を参照できますか?
-
はい、参照可能です。
self.他のフィールド名.data
と記述することで、別のフィールドの値を取得し、複合的な条件でバリデーションを行うことができます。
- Q3. カスタムバリデータが動作しないのはなぜですか?
-
関数名が正しくないケースが多いです。
validate_
の後に正確なフィールド名を付ける必要があります。また、関数の引数にself
とfield
を含めるのも必須です。
質問用コンタクトフォーム
この記事を書くにあたりAIを活用しています。
人間の目による確認も行っていますが、もし間違い等ありましたらご指摘頂けると大変助かります。