ユーニックス総合研究所

  • home
  • archives
  • django-form-validation

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_)  

...  

↑のMultiIDFieldforms.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を発生させています。
namecleaned_dataから取り出します。
そしてメソッドの最後ではnamereturnします。

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では柔軟にバリデーションを改造することが出来ます。

🦝 < バリデーションで堅牢なアプリを作ろう

🐭 < カッチカチやぞ