【Flask】WTFormsのカスタムバリデータを理解しよう|Chapter4-5

一つ前のページではフォームのバリデーションについて学習しました。
今回は カスタムバリデータ について見ていきましょう。
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:エラーハンドリングとデバッグ編
Chapter7:アプリ開発編
フォームを作成する際に、入力内容の正しさをチェックする「バリデーション」はとても重要です。
WTFormsには DataRequired や Length など、よく使うバリデーションが標準で多数用意されています(組み込みバリデータ)。
しかし「特定の値を拒否したい」「文字列の一部に制限をかけたい」など、より柔軟なチェックが必要になる場面も多くあります。
そんなときに使えるのが「カスタムバリデータ」です。
この記事では、WTFormsで自作のバリデーション処理を定義する方法を学んでいきましょう。
- Flask開発を Stream Deck でボタン化しよう!
-
Flaskは非常に軽量かつシンプルなフレームワークですが、それゆえに定型作業が多く、開発は単調な作業の連続になりがちです。
それこそがFlaskのメリットであり、習得難易度が低い理由でもありますが、単調な作業は退屈で、ミスも起こりやすいでしょう。
そこで役に立つのが Stream Deck 。
このような定型手順が多い作業を “ボタン化” することで視覚化。
圧倒的に 効率的 かつ ストレスフリー な開発環境が簡単に手に入ります↓↓
あわせて読みたいFlask開発をStreamDeckでボタン化しようあわせて読みたいプログラマー向けStream Deckの選び方|初心者でも失敗しないモデル比較ガイド
カスタムバリデータとは?|独自バリデーションを作ろう

カスタムバリデータ とは、WTFormsの標準バリデーションでは実現できないような独自のチェックロジックを、自分で定義してフォームに組み込む機能です。
たとえば:
- 禁止された単語が含まれていないかをチェックしたい
- 特定の文字列で始まる入力を許可したい
- データベースの値と照らし合わせて重複を確認したい
といったニーズに対して、柔軟に対応することができます。
カスタムバリデータは、フォームクラスの中に特定の名前のメソッドを定義することで使えます。
そのメソッドは、対象フィールド名に応じて命名する必要があります。
カスタムバリデータの定義ルールと構文例
以下が、カスタムバリデータの基本的な構文です。
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クラスを継承したMyFormクラスの定義
username = StringField('ユーザー名', validators=[DataRequired('名前は入力必須です')]) # 組み込みバリデータ
def validate_username(self, field): # usernameフィールドにカスタムバリデータを設定
if field.data == 'admin': # フィールドのデータが「admin」であったなら
raise ValidationError('このユーザー名は使用できません。')- validate_フィールド名:チェック対象のフィールドに対応した関数名にする
- field:検証対象のフィールドインスタンス
- field.data:入力データを格納する属性(プロパティ)
- ValidationError:チェックに失敗したときにエラーとして出す例外クラス
カスタムバリデータの使用例
以下は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']) # このURLにGETまたはPOSTリクエストが来たときに実行
def index():
if request.method == 'POST': # もしリクエストがPOSTなら(フォームが送信されたら)
form = MyForm(request.form) # インスタンスを生成
# 検証処理(バリデータがこのときに実行される)
if form.validate(): # バリデーション実行。もし結果がTrueなら
return '登録成功!'
else: # もし結果がFalseなら
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()を呼び出すと、自動でバリデーションが走る
カスタムバリデータは標準バリデータでは対応しきれないニーズに応える強力な手段です。
次章ではテンプレートのマクロを使って、フォームの表示もさらに効率的にしていきましょう。
- サイト改善アンケート|1分だけ、ご意見をお聞かせください
-
本サイトでは、みなさまの学習をよりサポートできるサービスを目指しております。
そのため、ご利用者のみなさまの「プログラミングを学習する理由」などをアンケート形式でお伺いしています。ご協力いただけますと幸いです。
アンケート
練習問題: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を含めるのも必須です。






