Djangoのフォームのバリデーションを詳しく見てみる
- 作成日: 2021-07-28
- 更新日: 2023-12-26
- カテゴリ: Django
Djangoのフォームのバリデーションを詳しく見てみる
Djangoのフォームはいつバリデーションしているのかと言うと、is_valid()
メソッドが呼ばれたときです。
バリデーションはフォーム、それからフィールドで行われます。
それらの処理はオーバーライド(上書き)することが可能です。
また、Djangoが用意しているバリデーターを使うことで簡単にバリデーションを実装することも可能です。
この記事ではDjangoのフォームのバリデーションについて詳しく見ていきます。
具体的には↓を見ていきます。
- Djangoのバリデーションの仕組み
- ValidationErrorについて
- カスタムフィールドによるバリデーション
- clean_[field]によるバリデーション
- cleanによるバリデーション
- validatorsによるバリデーション
関連記事
Djangoのフォーム(forms.Form)の使い方【Python】
Djangoのformの今どきな作り方【Python, モデルフォーム】
Django入門: フォームを作る ~ 簡単な一行掲示板アプリを作る その12【Windows10】
参考
フォームとフィールドの検証 | Django ドキュメント | Django
Djangoのバリデーションの仕組み
Djangoのフォームのバリデーションはフォームのis_valid()
が実行されたときに行われます。
if not form.is_valid():
...
is_valid()
が実行されるとフォームの持つclean()
メソッドが走り、また定義されたclean_[field]
メソッドが実行されます。
そしてフィールドのclean()
メソッドが実行され、フォームの隅から隅までバリデーションを実行されていきます。
各バリデーションでは検証に失敗した場合、django.forms.ValidationError
例外を投げる決まりになっています。
つまり、何か自分でバリデーションを追加する場合、検証に失敗したらValidationError
を発生させればいいわけです。
ValidationErrorについて
ValidationError
クラスは、バリデーションに失敗したときに投げられる例外です。
これは↓のように使います。
raise forms.ValidationError('名前の検証に失敗しました。')
↑の例ではフォームに名前のフィールドがある前提で、その検証に失敗したことを書いています。
ValidationError
に渡されるメッセージは、フォームのバリデーションに失敗したときに、フォームに表示されるエラーメッセージになります。
つまりここにアプリの内部情報を書いたりしてはいけません。
良いお作法
公式ドキュメントでは先ほどのValidationError
の書き方は推奨されていません。
Goodなのは↓のような書き方です。
raise forms.ValidationError(_('名前の検証に失敗しました。'), code='invalid name')
_()
のようにエラーメッセージをgettext
で囲み、さらにcode
を書きます。
これが公式で推奨されているValidationError
の書き方です。
この記事では簡便さのために推奨されていない方の書き方を使います。
予めご了承ください。
カスタムフィールドによるバリデーション
Djangoのフォームのバリデーションはフィールド単位で行われます。
独自のバリデーションをフィールドに追加したい場合は、Djangoのフィールドを継承したカスタムフィールドを作成します。
たとえば複数の整数のIDがカンマ区切りで入力されるフィールドを考えます。
このようなフィールドを定義したい場合は↓のように定義します。
from django import forms
from django.core.validators import validate_integer
...
class MultiIDField(forms.Field):
def to_python(self, value):
# valueをパースしてIDのリストに変換する
if not value:
return []
return value.split(',')
def validate(self, value):
# valueをバリデーションする
super().validate(value)
for id_ in value:
validate_integer(id_)
...
↑のMultiIDField
はforms.Field
を継承したカスタムフィールドです。
Djangoのフィールドのバリデーションでは、フィールドのclean()
メソッドが呼ばれます。
clean()
メソッドは内部でto_python()
とvalidate()
を順番に呼び出します。
to_python()
はフィールドに入力されたデータをPythonのオブジェクトに変換する役割を持っています。
validate()
はto_python()
で変換されたデータを検証する役割があります。
これらのメソッドを上書きすることで、独自のバリデーションを定義することが可能です。
↑の例ではto_python()
でフィールドの値(value
)のカンマをパースしてリストにしています。
そしてvalidate()
では親のvalidate()
を呼び出した後に、リスト(value
)の中身を検証しています。
検証にはDjangoが用意しているバリデーターの1つであるvalidate_integer()
を使っています。
これは値が整数かどうか検証するバリデート関数で、検証に失敗した場合は内部でValidationError
を発生させます。
このカスタムフィールドは既存のフィールドと同じようにフォームで使うことが出来ます。
class PostForm(forms.Form):
name = forms.CharField(label='名前', max_length=32)
content = forms.CharField(label='内容', widget=forms.Textarea)
ids = MultiIDField(label='IDリスト(カンマ区切り)')
...
clean_[field]によるバリデーション
フォームのメソッドにclean_
で始まるメソッドを定義します。
お尻にはフィールド名が付きます。
たとえばname
フィールドであればclean_name
になります。
このclean_name
というメソッドは、バリデーションでフォームのname
フィールドを検証するメソッドになります。
このメソッドをフォームに定義することで、name
フィールドの具体的なバリデーションを実装できます。
class PostForm(forms.Form):
name = forms.CharField(label='名前', max_length=32)
...
def clean_name(self):
# nameをバリデーションする
name = self.cleaned_data['name']
if len(name) < 3:
raise forms.ValidationError('名前が短すぎます。')
return name
↑の例ではclean_name()
でname
の長さを検証しています。
name
の長さが3
より下であれば、ValidationError
を発生させています。
name
はcleaned_data
から取り出します。
そしてメソッドの最後ではname
をreturn
します。
cleanによるバリデーション
複数のフィールドの絡み合った検証をしたい場合があります。
たとえばあるフィールドの値は、あるフィールドの値によって判断が分かれるという具合です。
そう言った場合はフォームのメソッドclean()
を上書きします。
class PostForm(forms.Form):
name = forms.CharField(label='名前', max_length=32)
content = forms.CharField(label='内容', widget=forms.Textarea)
ids = MultiIDField(label='IDリスト(カンマ区切り)')
...
def clean(self):
cleaned_data = super().clean()
name = cleaned_data.get('name')
content = cleaned_data.get('content')
ids = cleaned_data.get('ids')
if name is None or content is None or ids is None:
raise forms.ValidationError('フィールドが入力されていません。')
clean()
で一番大事なのは、super().clean()
とやって親のclean()
を呼び出すのを忘れないようにすることです。
親のclean()
はcleaned_data
を返します。
cleaned_data
からフィールドの値を取り出し、その値を複合的に検証します。
↑の例では、単純にフィールドの値がNone
だったらValidationError
を発生させています。
validatorsによるバリデーション
Djangoは複数のバリデーターを標準で用意しています。
それらはfrom django.core.validators
に保存されています。
↓はバリデータークラスとバリデーター関数の一覧です。
- RegexValidator
- URLValidator
- EmailValidator
- MaxValueValidator
- MinValueValidator
- MinLengthValidator
- MaxLengthValidator
- DecimalValidator
- FileExtensionValidator
- ProhibitNullCharactersValidator
- validate_integer
- validate_email
- validate_slug
- validate_unicode_slug
- validate_ipv4_address
- validate_ipv6_address
- validate_ipv46_address
- validate_comma_separated_integer_list
- validate_image_file_extension
↑のバリデーターを見ると、MinLengthValidator
を使えばname
フィールドの長さを検証できそうです。
バリデーターを指定するには↓のようにフィールドのキーワード引数validators
にリストでバリデーターを指定します。
class PostForm(forms.Form):
name = forms.CharField(
label='名前',
max_length=32,
validators=[MinLengthValidator(limit_value=3)]
)
...
↑のようにname
フィールドを改造し、clean_name()
を削除した場合でも、バリデーションは機能します。
また、バリデーターにはvalidate_comma_separated_integer_list
というそのものずばりなバリデーターもあります。
これはids
のバリデーションに使えそうです。
ですので↓のように改造します。
from django import forms
from django.core.validators import validate_comma_separated_integer_list
class MultiIDField(forms.Field):
pass
class PostForm(forms.Form):
...
ids = MultiIDField(
label='IDリスト(カンマ区切り)',
validators=[validate_comma_separated_integer_list]
)
↑のようにすると、フォームの入力ではカンマで区切られた整数でない値はエラーになります。
おわりに
今回はDjangoのフォームのバリデーションについて見てみました。
Djangoでは柔軟にバリデーションを改造することが出来ます。
🦝 < バリデーションで堅牢なアプリを作ろう
🐭 < カッチカチやぞ