フォームとフィールドの検証
フォームの検証は、データがクリーンアップされるときに行われます。 このプロセスをカスタマイズしたい場合は、変更を加えるためのさまざまな場所があり、それぞれが異なる目的を果たします。 フォームの処理中には、3種類のクリーニング方法が実行されます。 これらは通常、フォームでis_valid()
メソッドを呼び出すときに実行されます。 クリーニングと検証をトリガーできるものは他にもありますが(errors
属性にアクセスするか、full_clean()
を直接呼び出す)、通常は必要ありません。
一般に、処理中のデータに問題がある場合、どのクリーニング方法でもValidationError
が発生し、関連情報がValidationError
コンストラクターに渡されます。 ValidationError
を上げるためのベストプラクティスについては、以下を参照してください。 ValidationError
が発生しない場合、メソッドはクリーンアップされた(正規化された)データをPythonオブジェクトとして返す必要があります。
ほとんどの検証は、バリデーター(再利用可能なヘルパー)を使用して実行できます。 バリデーターは、単一の引数を取り、無効な入力でValidationError
を発生させる関数(または呼び出し可能オブジェクト)です。 バリデーターは、フィールドのto_python
およびvalidate
メソッドが呼び出された後に実行されます。
フォームの検証はいくつかのステップに分かれており、カスタマイズまたはオーバーライドできます。
Field
のto_python()
メソッドは、すべての検証の最初のステップです。 値を正しいデータ型に強制し、それが不可能な場合はValidationError
を上げます。 このメソッドは、ウィジェットから生の値を受け取り、変換された値を返します。 たとえば、FloatField
はデータをPythonfloat
に変換したり、ValidationError
を発生させたりします。Field
のvalidate()
メソッドは、バリデーターに適さないフィールド固有の検証を処理します。 正しいデータ型に強制変換された値を取り、エラーが発生するとValidationError
を発生させます。 このメソッドは何も返さないため、値を変更しないでください。 バリデーターに入れることができない、または入れたくない検証ロジックを処理するには、これをオーバーライドする必要があります。Field
のrun_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()メソッドは、フル機能のValidationError
(code
を使用)から大きな恩恵を受けます。名前と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]
ご覧のとおり、SlugField
はCharField
であり、送信されたテキストがいくつかの文字ルールに従っていることを検証するカスタマイズされたバリデーターを備えています。 これは、フィールド定義でも実行できるため、次のようになります。
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
からフィールドを自動的に削除することに注意してください。