フォームとフィールドの検証¶
フォームのバリデーションは、データがクリーニング (clean) されるときに実行されます。このプロセスをカスタムしたい場合は、様々な箇所に変更を加えることができ、それぞれが違う目的を持っています。クリーニング方法のうち 3 タイプは、フォームのプロセス中に実行されます。これらは、通常フォーム上の is_valid()
メソッドを呼び出したときに実行されます。このほかにも、クリーニングとバリデーションのトリガーとなる処理 (直接 errors
属性にアクセスしたり、 full_clean()
を直接呼び出す) がありますが、通常必要とはなりません。
一般的に、あらゆるクリーニング方法は ValidationError
を投げる可能性があります。処理されるデータに問題がある場合、関連情報を ValidationError
コンストラクタに渡します。下記項目 に、ValidationError
を投げる際のベストプラクティスがあります。ValidationError
が投げられない場合、メソッドはクリーニングされた (標準化された) データを Python オブジェクトとして返すはずです。
Most validation can be done using validators - helpers that can be reused.
Validators are functions (or callables) that take a single argument and raise
ValidationError
on invalid input. Validators are run after the field's
to_python
and validate
methods have been called.
フォームのバリデーションは複数のステップに分割されます。カスタムやオーバーライドすることができます:
Field
のto_python()
メソッドはすべてのバリデーションの最初のステップとなります。これは、値をデータ型に正すことを強制し、それができない場合はValidationError
を投げます。このメソッドはウィジェットからそのままの値を受け取り、変換した値を返します。たとえば、FloatField
は Python のfloat
に変換されるか、もしくはValidationError
を投げます。Field
のvalidate()
メソッドは、フィールド特有のバリデーションを行います。これはバリデータとしては不適です。正しいデータ型を強制された値を取り、エラーの際はValidationError
を投げます。このメソッドは何も返さず、また値の変更も行わないはずです。バリデータに記述したくないバリデーションロジックを実行するためには、このメソッドをオーバーライドしてください。Field
のrun_validators()
メソッドはフィールドのバリデータをすべて実行し、すべてのエラーを単一のValidationError
に統合します。このメソッドをオーバーライドする必要はないはずです。Field
サブクラスのclean()
メソッドは、to_python()
、validate()
、run_validators()
を正しい順序で実行し、エラーを伝達する役目を負っています。もしこの過程のどこかでValidationError
が投げられた場合、バリデーションは停止し、そのエラーを投げます。このメソッドはクリーニングされたデータを返し、フォームのcleaned_data
ディクショナリに格納します。clean_<fieldname>()
メソッドは、フォームサブクラス上で呼び出されます --<fieldname>
がフォームのフィールド属性の名前と置き換えられます。このメソッドは、フィールドのタイプにかかわらず、その特定の属性に対するクリーニングを実行します。 このメソッドはパラメータを受け取りません。self.cleaned_data
内のフィールドの値を検索する必要があり、またこの時点ではフォーム上で元々送信された文字列ではなく Python オブジェクトであること覚えておいてください (これはcleaned_data
内にあります。上記に出てきた一般フィールドのclean()
メソッドがすでに一度データをクリーニングしているからです)。For example, if you wanted to validate that the contents of a
CharField
calledserialnumber
was unique,clean_serialnumber()
would be the right place to do this. You don't need a specific field (it's aCharField
), but you want a formfield-specific piece of validation and, possibly, cleaning/normalizing the data.このメソッドの戻り値は
cleaned_data
内の既存の値を置き換えるため、(たとえこのメソッドが変更しなかったとしても)cleaned_data
からのフィールドの値、ないし新しくクリーニングされた値になるはずです。フォームのサブクラスの
clean()
メソッドは、複数のフォームフィールドにまたがるバリデーションにも使用できます。この場所には、たとえば「フィールドA
が入力されたときフィールドB
は有効なメールアドレスのはず」といったチェックを記述できます。このメソッドは、必要な場合はまったく違うディクショナリを返すこともでき、その場合はcleaned_data
として使用されます。フィールドバリデーションのメソッドは
clean()
が呼ばれる際に実行されるので、フォームのerrors
属性にもアクセスできるようになります。これは各フィールドのクリーニングによって発生した例外をすべて含みます。Form.clean()
をオーバーライドして発生させた例外は、特定のフィールドに結びつかない点に注意してください。これらは特別な "フィールド" (__all__
と呼ばれます) に格納され、必要に応じてnon_field_errors()
メソッドを通じてアクセスできます。特定のフィールドにエラーを紐付けて格納したい場合は、add_error()
を呼び出す必要があります。ModelForm
サブクラスのclean()
メソッドをオーバーライドする際、特に考慮する点がまだあります (詳しくは ModelForm ドキュメント を参照してください)。
これらのメソッドは、一度に一つのフィールドに対し、先述した通りの順番で実行されます。フォーム内の各フィールドに対して (フォームの定義時に宣言された順序で)、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()
メソッドは、十分な機能を有する (code
名と params
ディクショナリを持つ) ValidationError
により大きな恩恵を受けます。
複数のエラーを起こす¶
クリーニングメソッド内で複数のエラーを検証し、すべてのエラーをフォーム送信者に知らせたい場合、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's form (and model) fields support use of utility functions and classes
known as validators. A validator is a callable object or function that takes a
value and returns nothing if the value is valid or raises a
ValidationError
if not. These can be passed to a
field's constructor, via the field's validators
argument, or defined on the
Field
class itself with the default_validators
attribute.
Validators can be used to validate values inside the field, let's have a look
at Django's SlugField
:
from django.core import validators
from django.forms import CharField
class SlugField(CharField):
default_validators = [validators.validate_slug]
As you can see, SlugField
is a CharField
with a customized validator
that validates that submitted text obeys to some character rules. This can also
be done on field definition so:
slug = forms.SlugField()
これは以下と同じです:
slug = forms.CharField(validators=[validators.validate_slug])
一般的なケース (例えば、E メールや正規表現に対する検証) は、Django が提供する既存のバリデータクラスを使って処理できます。例えば、validators.validate_slug
は RegexValidator
の第 1 引数をパターン ^[-a-zA-Z0-9_]+$
としたインスタンスです。バリデータを記述する のセクションを参照して、利用可能なバリデータのリストとバリデータの記述方法の例を確認できます。
フォームフィールドのデフォルトのクリーニング¶
まず、カンマで区切られたメールアドレスを含むかどうか検証するカスタムフォームフィールドを作成しましょう。クラスの全体像は以下のようになります:
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)
このフィールドを使うすべてのフォームでは、フィールドのデータを使えるようになる前に、これらのメソッドが実行されます。これはこのタイプのフィールドに特有であり、それはこの後の使われ方には関係ありません。
Let's create a ContactForm
to demonstrate how you'd use this field:
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
recipients = MultiEmailField()
cc_myself = forms.BooleanField(required=False)
Use MultiEmailField
like any other form field. When the is_valid()
method is called on the form, the MultiEmailField.clean()
method will be
run as part of the cleaning process and it will, in turn, call the custom
to_python()
and validate()
methods.
特定のフィールド属性をクリーニングする¶
上の例を引き続き使用して、ContactForm
の recipients
フィールドが常に``"fred@example.com"`` を含むようにしたいとします。これはフォームに特有のバリデーションなので、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 "fred@example.com" 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."
)
In this code, if the validation error is raised, the form will display an
error message at the top of the form (normally) describing the problem. Such
errors are non-field errors, which are displayed in the template with
{{ 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")
...
バリデーションエラーを通知するもう 1 つのアプローチは、エラーメッセージをフィールドの 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)
The second argument of add_error()
can be a string, or preferably an
instance of ValidationError
. See ValidationError を発生させる for more
details. Note that add_error()
automatically removes the field from
cleaned_data
.