フォームとフィールドの検証—Djangoドキュメント

提供:Dev Guides
< DjangoDjango/docs/3.2.x/ref/forms/validation
移動先:案内検索

フォームとフィールドの検証

フォームの検証は、データがクリーンアップされるときに行われます。 このプロセスをカスタマイズしたい場合は、変更を加えるためのさまざまな場所があり、それぞれが異なる目的を果たします。 フォームの処理中には、3種類のクリーニング方法が実行されます。 これらは通常、フォームでis_valid()メソッドを呼び出すときに実行されます。 クリーニングと検証をトリガーできるものは他にもありますが(errors属性にアクセスするか、full_clean()を直接呼び出す)、通常は必要ありません。

一般に、処理中のデータに問題がある場合、どのクリーニング方法でもValidationErrorが発生し、関連情報がValidationErrorコンストラクターに渡されます。 ValidationErrorを上げるためのベストプラクティスについては、以下を参照してください。 ValidationErrorが発生しない場合、メソッドはクリーンアップされた(正規化された)データをPythonオブジェクトとして返す必要があります。

ほとんどの検証は、バリデーター(再利用可能なヘルパー)を使用して実行できます。 バリデーターは、単一の引数を取り、無効な入力でValidationErrorを発生させる関数(または呼び出し可能オブジェクト)です。 バリデーターは、フィールドのto_pythonおよびvalidateメソッドが呼び出された後に実行されます。

フォームの検証はいくつかのステップに分かれており、カスタマイズまたはオーバーライドできます。

  • Fieldto_python()メソッドは、すべての検証の最初のステップです。 値を正しいデータ型に強制し、それが不可能な場合はValidationErrorを上げます。 このメソッドは、ウィジェットから生の値を受け取り、変換された値を返します。 たとえば、FloatFieldはデータをPython floatに変換したり、ValidationErrorを発生させたりします。

  • Fieldvalidate()メソッドは、バリデーターに適さないフィールド固有の検証を処理します。 正しいデータ型に強制変換された値を取り、エラーが発生するとValidationErrorを発生させます。 このメソッドは何も返さないため、値を変更しないでください。 バリデーターに入れることができない、または入れたくない検証ロジックを処理するには、これをオーバーライドする必要があります。

  • Fieldrun_validators()メソッドは、フィールドのすべてのバリデーターを実行し、すべてのエラーを1つのValidationErrorに集約します。 このメソッドをオーバーライドする必要はありません。

  • Fieldサブクラスのclean()メソッドは、to_python()validate()、およびrun_validators()を正しい順序で実行し、それらのエラーを伝播する役割を果たします。 。 いずれかのメソッドでValidationErrorが発生すると、検証が停止し、そのエラーが発生します。 このメソッドはクリーンなデータを返し、それがフォームのcleaned_dataディクショナリに挿入されます。

  • clean_<fieldname>()メソッドは、フォームサブクラスで呼び出されます。ここで、<fieldname>はフォームフィールド属性の名前に置き換えられます。 このメソッドは、フィールドのタイプに関係なく、その特定の属性に固有のクリーニングを実行します。 このメソッドにはパラメーターが渡されません。 self.cleaned_dataでフィールドの値を検索し、フォームで送信された元の文字列ではなく、この時点でPythonオブジェクトになることを覚えておく必要があります(cleaned_dataになります)。上記の一般フィールドclean()メソッドは、すでにデータを1回クリーンアップしているためです)。

    たとえば、serialnumberというCharFieldのコンテンツが一意であることを検証する場合は、clean_serialnumber()が適切な場所です。 特定のフィールド(CharField)は必要ありませんが、フォームフィールド固有の検証と、場合によってはデータのクリーニング/正規化が必要です。

    このメソッドの戻り値は、cleaned_dataの既存の値を置き換えるため、cleaned_dataからのフィールドの値(このメソッドが変更しなかった場合でも)または新しくクリーンアップされた値である必要があります。

  • フォームサブクラスのclean()メソッドは、複数のフォームフィールドへのアクセスを必要とする検証を実行できます。 ここで、「フィールドAが指定されている場合、フィールドBには有効な電子メールアドレスが含まれている必要があります」などのチェックを入れることができます。 このメソッドは、必要に応じて完全に異なる辞書を返すことができ、cleaned_dataとして使用されます。

    clean()が呼び出されるまでにフィールド検証メソッドが実行されているため、個々のフィールドのクリーニングによって発生したすべてのエラーを含むフォームのerrors属性にもアクセスできます。

    Form.clean()オーバーライドによって発生したエラーは、特定のフィールドに関連付けられないことに注意してください。 それらは特別な「フィールド」(__all__と呼ばれる)に入り、必要に応じて non_field_errors()メソッドを介してアクセスできます。 フォームの特定のフィールドにエラーを添付する場合は、 add_error()を呼び出す必要があります。

    また、ModelFormサブクラスのclean()メソッドをオーバーライドする場合は、特別な考慮事項があることに注意してください。 (詳細については、 ModelFormドキュメントを参照してください)

これらのメソッドは、一度に1フィールドずつ、上記の順序で実行されます。 つまり、フォーム内の各フィールドに対して(フォーム定義で宣言されている順序で)、Field.clean()メソッド(またはそのオーバーライド)が実行され、次にclean_<fieldname>()が実行されます。 最後に、これら2つのメソッドがすべてのフィールドに対して実行されると、前のメソッドでエラーが発生したかどうかに関係なく、 Form.clean()メソッドまたはそのオーバーライドが実行されます。

これらの各方法の例を以下に示します。

前述のように、これらのメソッドはいずれもValidationErrorを発生させる可能性があります。 どのフィールドでも、Field.clean()メソッドがValidationErrorを発生させる場合、フィールド固有のクリーニングメソッドは呼び出されません。 ただし、残りのすべてのフィールドのクリーニング方法は引き続き実行されます。

ValidationErrorを上げる

エラーメッセージを柔軟で簡単に上書きできるようにするには、次のガイドラインを考慮してください。

  • コンストラクターに説明エラーcodeを提供します。

    # Good
    ValidationError(_('Invalid value'), code='invalid')
    
    # Bad
    ValidationError(_('Invalid value'))
  • メッセージに変数を強制しないでください。 プレースホルダーとコンストラクターのparams引数を使用します。

    # Good
    ValidationError(
        _('Invalid value: %(value)s'),
        params={'value': '42'},
    )
    
    # Bad
    ValidationError(_('Invalid value: %s') % value)
  • 位置フォーマットの代わりにマッピングキーを使用します。 これにより、メッセージを書き換えるときに、変数を任意の順序で配置したり、変数を完全に省略したりできます。

    # Good
    ValidationError(
        _('Invalid value: %(value)s'),
        params={'value': '42'},
    )
    
    # Bad
    ValidationError(
        _('Invalid value: %s'),
        params=('42',),
    )
  • メッセージをgettextでラップして、翻訳を有効にします。

    # Good
    ValidationError(_('Invalid value'))
    
    # Bad
    ValidationError('Invalid value')

すべてを一緒に入れて:

raise ValidationError(
    _('Invalid value: %(value)s'),
    code='invalid',
    params={'value': '42'},
)

再利用可能なフォーム、フォームフィールド、およびモデルフィールドを作成する場合は、これらのガイドラインに従うことが特に必要です。

推奨されていませんが、検証チェーンの最後にいる場合(つまり、 フォームclean()メソッド)であり、エラーメッセージを決してオーバーライドする必要がないことを知っているので、冗長性を低くすることもできます。

ValidationError(_('Invalid value: %s') % value)

Form.errors.as_data()および Form.errors.as_json()メソッドは、フル機能のValidationErrorcodeを使用)から大きな恩恵を受けます。名前とparams辞書)。

複数のエラーが発生する

クリーニング方法中に複数のエラーを検出し、それらすべてをフォーム送信者に通知したい場合は、エラーのリストをValidationErrorコンストラクターに渡すことができます。

上記のように、ValidationErrorインスタンスのリストをcodeおよびparamsとともに渡すことをお勧めしますが、文字列のリストも機能します。

# Good
raise ValidationError([
    ValidationError(_('Error 1'), code='error1'),
    ValidationError(_('Error 2'), code='error2'),
])

# Bad
raise ValidationError([
    _('Error 1'),
    _('Error 2'),
])

実際に検証を使用する

前のセクションでは、フォームの検証が一般的にどのように機能するかについて説明しました。 使用中の各機能を確認することで、物事を適切に配置する方が簡単な場合があるため、これまでの各機能を使用する一連の小さな例を次に示します。

バリデーターの使用

Djangoのフォーム(およびモデル)フィールドは、バリデーターと呼ばれるユーティリティ関数とクラスの使用をサポートします。 バリデーターは、値を受け取り、値が有効な場合は何も返さない、または有効でない場合は ValidationError を発生させる呼び出し可能なオブジェクトまたは関数です。 これらは、フィールドのvalidators引数を介してフィールドのコンストラクターに渡すか、default_validators属性を使用して Field クラス自体で定義できます。

バリデーターを使用してフィールド内の値を検証できます。DjangoのSlugFieldを見てみましょう。

from django.core import validators
from django.forms import CharField

class SlugField(CharField):
    default_validators = [validators.validate_slug]

ご覧のとおり、SlugFieldCharFieldであり、送信されたテキストがいくつかの文字ルールに従っていることを検証するカスタマイズされたバリデーターを備えています。 これは、フィールド定義でも実行できるため、次のようになります。

slug = forms.SlugField()

と同等です:

slug = forms.CharField(validators=[validators.validate_slug])

電子メールや正規表現に対する検証などの一般的なケースは、Djangoで利用可能な既存のバリデータークラスを使用して処理できます。 たとえば、validators.validate_slugは、最初の引数がパターン^[-a-zA-Z0-9_]+$で構成された RegexValidator のインスタンスです。 バリデーターの作成のセクションを参照して、すでに利用可能なもののリストと、バリデーターの作成方法の例を確認してください。


フォームフィールドのデフォルトのクリーニング

まず、入力がコンマ区切りの電子メールアドレスを含む文字列であることを検証するカスタムフォームフィールドを作成しましょう。 フルクラスは次のようになります。

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(',')

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super().validate(value)
        for email in value:
            validate_email(email)

このフィールドを使用するすべてのフォームでは、フィールドのデータを使用して他の処理を実行する前に、これらのメソッドが実行されます。 これは、その後の使用方法に関係なく、このタイプのフィールドに固有のクリーニングです。

ContactFormを作成して、このフィールドの使用方法を示しましょう。

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

他のフォームフィールドと同様にMultiEmailFieldを使用します。 フォームでis_valid()メソッドが呼び出されると、MultiEmailField.clean()メソッドがクリーニングプロセスの一部として実行され、カスタムto_python()および[ X166X] メソッド。


特定のフィールド属性のクリーニング

前の例から続けて、ContactFormで、recipientsフィールドに常にアドレス"[email protected]"が含まれていることを確認するとします。 これはフォームに固有の検証であるため、一般的なMultiEmailFieldクラスには入れたくありません。 代わりに、次のように、recipientsフィールドで動作するクリーニングメソッドを記述します。

from django import forms
from django.core.exceptions import ValidationError

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "[email protected]" not in data:
            raise ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

相互に依存するフィールドのクリーニングと検証

お問い合わせフォームに別の要件を追加するとします。cc_myselfフィールドがTrueの場合、subjectには"help"という単語が含まれている必要があります。 一度に複数のフィールドで検証を実行しているため、フォームの clean()メソッドはこれを行うのに適した場所です。 ここでは、フォームでclean()メソッドについて説明しているのに対し、以前はフィールドでclean()メソッドを記述していたことに注意してください。 物事を検証する場所を決定するときは、フィールドとフォームの違いを明確に保つことが重要です。 フィールドは単一のデータポイントであり、フォームはフィールドのコレクションです。

フォームのclean()メソッドが呼び出されるまでに、個々のフィールドクリーンメソッドがすべて実行されているため(前の2つのセクション)、self.cleaned_dataにはこれまでに残っているデータが入力されます。 。 したがって、検証するフィールドが最初の個別のフィールドチェックに耐えられなかった可能性があるという事実を考慮する必要もあります。

このステップでエラーを報告するには、2つの方法があります。 おそらく最も一般的な方法は、フォームの上部にエラーを表示することです。 このようなエラーを作成するには、clean()メソッドからValidationErrorを発生させることができます。 例えば:

from django import forms
from django.core.exceptions import ValidationError

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

このコードでは、検証エラーが発生した場合、フォームの上部に問題を説明するエラーメッセージが(通常は)表示されます。 このようなエラーはフィールド以外のエラーであり、テンプレート:Form.non field errorsとともにテンプレートに表示されます。

サンプルコードのsuper().clean()を呼び出すと、親クラスの検証ロジックが確実に維持されます。 フォームがclean()メソッドでcleaned_dataディクショナリを返さない別のフォームを継承する場合(これはオプションです)、結果にcleaned_dataを割り当てないでください。 super()を呼び出して、代わりにself.cleaned_dataを使用します。

def clean(self):
    super().clean()
    cc_myself = self.cleaned_data.get("cc_myself")
    ...

検証エラーを報告するための2番目のアプローチでは、フィールドの1つにエラーメッセージを割り当てる必要があります。 この場合、フォーム表示の「subject」行と「cc_myself」行の両方にエラーメッセージを割り当てましょう。 実際にこれを行うときは注意してください。フォームの出力が混乱する可能性があります。 ここでは何が可能かを示し、特定の状況で効果的に機能するものを見つけるのはあなたとあなたのデザイナーに任せています。 新しいコード(前のサンプルを置き換える)は次のようになります。

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error('cc_myself', msg)
            self.add_error('subject', msg)

add_error()の2番目の引数は、文字列、またはできればValidationErrorのインスタンスにすることができます。 詳細については、 Raising ValidationError を参照してください。 add_error()は、cleaned_dataからフィールドを自動的に削除することに注意してください。