バリデーター
バリデーターは、異なる種類のフィールド間でバリデーションロジックを再利用するのに役立ちます。
REST framework でバリデーションを扱うほとんどの場合、デフォルトのフィールドバリデーションに頼ったり、シリアライザーまたはフィールドクラスに明示的なバリデーションメソッドを記述したりするだけです。
ただし、場合によっては、バリデーションロジックを再利用可能なコンポーネントに配置して、コードベース全体で簡単に再利用できるようにしたい場合があります。これは、バリデーター関数とバリデータークラスを使用することで実現できます。
REST framework におけるバリデーション
Django REST framework シリアライザーのバリデーションは、Django の ModelForm
クラスでのバリデーションの動作とは少し異なります。
ModelForm
では、バリデーションはフォームで部分的に実行され、モデルインスタンスで部分的に実行されます。REST framework では、バリデーションはすべてシリアライザークラスで実行されます。これは、以下の理由で有利です。
- 関心の分離が適切に導入され、コードの動作がより明確になります。
- ショートカットの
ModelSerializer
クラスの使用と、明示的なSerializer
クラスの使用を簡単に切り替えることができます。ModelSerializer
に使用されているバリデーションの動作は簡単に複製できます。 - シリアライザーインスタンスの
repr
を出力すると、適用されるバリデーションルールが正確に表示されます。モデルインスタンスで呼び出される追加の隠れたバリデーションの動作はありません。
ModelSerializer
を使用している場合、これらはすべて自動的に処理されます。代わりに Serializer
クラスを使用する場合は、バリデーションルールを明示的に定義する必要があります。
例
REST framework が明示的なバリデーションをどのように使用するかの一例として、一意性制約のあるフィールドを持つ単純なモデルクラスを取り上げます。
class CustomerReportRecord(models.Model):
time_raised = models.DateTimeField(default=timezone.now, editable=False)
reference = models.CharField(unique=True, max_length=20)
description = models.TextField()
以下は、CustomerReportRecord
のインスタンスを作成または更新するために使用できる基本的な ModelSerializer
です。
class CustomerReportSerializer(serializers.ModelSerializer):
class Meta:
model = CustomerReportRecord
manage.py shell
を使用して Django シェルを開くと、次の操作を実行できます。
>>> from project.example.serializers import CustomerReportSerializer
>>> serializer = CustomerReportSerializer()
>>> print(repr(serializer))
CustomerReportSerializer():
id = IntegerField(label='ID', read_only=True)
time_raised = DateTimeField(read_only=True)
reference = CharField(max_length=20, validators=[<UniqueValidator(queryset=CustomerReportRecord.objects.all())>])
description = CharField(style={'type': 'textarea'})
ここで重要なのは reference
フィールドです。一意性制約がシリアライザーフィールドのバリデーターによって明示的に強制されていることがわかります。
このより明示的なスタイルのため、REST framework には、コア Django では利用できないいくつかのバリデータークラスが含まれています。これらのクラスについては、以下で詳しく説明します。REST framework のバリデーターは、Django の対応するバリデーターと同様に、__eq__
メソッドを実装しており、インスタンスの等価性を比較できます。
UniqueValidator
このバリデーターは、モデルフィールドに unique=True
制約を強制するために使用できます。単一の必須引数と、オプションの messages
引数を取ります。
queryset
必須 - これは、一意性を強制する必要があるクエリセットです。message
- バリデーションが失敗した場合に使用する必要があるエラーメッセージ。lookup
- バリデーション対象の値を持つ既存のインスタンスを検索するために使用されるルックアップ。デフォルトは'exact'
です。
このバリデーターは、次のようにシリアライザーフィールドに適用する必要があります。
from rest_framework.validators import UniqueValidator
slug = SlugField(
max_length=100,
validators=[UniqueValidator(queryset=BlogPost.objects.all())]
)
UniqueTogetherValidator
このバリデーターは、モデルインスタンスに unique_together
制約を強制するために使用できます。2 つの必須引数と、単一のオプションの messages
引数があります。
queryset
必須 - これは、一意性を強制する必要があるクエリセットです。fields
必須 - 一意のセットを構成する必要があるフィールド名のリストまたはタプル。これらは、シリアライザークラスのフィールドとして存在する必要があります。message
- バリデーションが失敗した場合に使用する必要があるエラーメッセージ。
バリデーターは、次のようにシリアライザークラスに適用する必要があります。
from rest_framework.validators import UniqueTogetherValidator
class ExampleSerializer(serializers.Serializer):
# ...
class Meta:
# ToDo items belong to a parent list, and have an ordering defined
# by the 'position' field. No two items in a given list may share
# the same position.
validators = [
UniqueTogetherValidator(
queryset=ToDoItem.objects.all(),
fields=['list', 'position']
)
]
注: UniqueTogetherValidator
クラスは、常に、適用されるすべてのフィールドが常に必須として扱われるという暗黙の制約を課します。default
値を持つフィールドは、ユーザー入力から省略された場合でも常に値を指定するため、例外です。
UniqueForDateValidator
UniqueForMonthValidator
UniqueForYearValidator
これらのバリデーターは、モデルインスタンスに unique_for_date
、unique_for_month
、および unique_for_year
制約を強制するために使用できます。次の引数を取ります。
queryset
必須 - これは、一意性を強制する必要があるクエリセットです。field
必須 - 指定された日付範囲内の一意性を検証するフィールド名。これは、シリアライザークラスのフィールドとして存在する必要があります。date_field
必須 - 一意性制約の日付範囲を決定するために使用されるフィールド名。これは、シリアライザークラスのフィールドとして存在する必要があります。message
- バリデーションが失敗した場合に使用する必要があるエラーメッセージ。
バリデーターは、次のようにシリアライザークラスに適用する必要があります。
from rest_framework.validators import UniqueForYearValidator
class ExampleSerializer(serializers.Serializer):
# ...
class Meta:
# Blog posts should have a slug that is unique for the current year.
validators = [
UniqueForYearValidator(
queryset=BlogPostItem.objects.all(),
field='slug',
date_field='published'
)
]
バリデーションに使用される日付フィールドは、シリアライザークラスに常に存在する必要があります。モデルクラスの default=...
に頼るだけではいけません。デフォルトに使用される値は、バリデーションが実行されるまで生成されないためです。
API をどのように動作させるかによって、これに使用したいスタイルがいくつかあります。ModelSerializer
を使用している場合は、REST framework が生成するデフォルトに頼ることがおそらくできますが、Serializer
を使用している場合、またはより明示的な制御が必要な場合は、以下に示すスタイルのいずれかを使用してください。
書き込み可能な日付フィールドで使用する。
日付フィールドを書き込み可能にする場合は、default
引数を設定するか、required=True
を設定して、常に入力データで使用できるようにする必要があることに注意してください。
published = serializers.DateTimeField(required=True)
読み取り専用の日付フィールドで使用する。
日付フィールドを表示したいが、ユーザーが編集できないようにする場合は、read_only=True
を設定し、さらに default=...
引数を設定します。
published = serializers.DateTimeField(read_only=True, default=timezone.now)
非表示の日付フィールドで使用する。
日付フィールドをユーザーから完全に非表示にしたい場合は、HiddenField
を使用します。このフィールドタイプはユーザー入力を受け入れませんが、代わりに、シリアライザーの validated_data
に常にデフォルト値を返します。
published = serializers.HiddenField(default=timezone.now)
注: UniqueFor<Range>Validator
クラスは、適用されるフィールドが常に必須として扱われるという暗黙の制約を課します。default
値を持つフィールドは、ユーザー入力から省略された場合でも常に値を指定するため、例外です。
注: HiddenField()
は、partial=True
シリアライザー(PATCH
リクエストを行う場合)には表示されません。この動作は将来変更される可能性があります。 github ディスカッションで最新情報を確認してください。
高度なフィールドのデフォルト値
シリアライザー内の複数のフィールドに適用されるバリデーターでは、API クライアントから提供されるべきではないが、バリデーターへの入力として使用可能であるフィールド入力が必要になる場合があります。この目的には、HiddenField
を使用します。このフィールドは validated_data
に存在しますが、シリアライザーの出力表現では使用されません。
注: read_only=True
フィールドを使用すると、書き込み可能なフィールドから除外されるため、default=…
引数は使用されません。 3.8 の発表を参照してください。
REST framework には、このコンテキストで役立つ可能性のあるいくつかのデフォルトが含まれています。
CurrentUserDefault
現在のユーザーを表すために使用できるデフォルトクラス。これを使用するには、シリアライザーをインスタンス化するときに、コンテキストディクショナリの一部として 'request' が提供されている必要があります。
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
CreateOnlyDefault
作成操作中にのみデフォルト引数を設定できるデフォルトクラス。更新中は、フィールドは省略されます。
作成操作中に使用する必要があるデフォルト値または呼び出し可能な引数である単一の引数を取ります。
created_at = serializers.DateTimeField(
default=serializers.CreateOnlyDefault(timezone.now)
)
バリデーターの制限
ModelSerializer
が生成するデフォルトのシリアライザークラスに頼るのではなく、バリデーションを明示的に処理する必要があるあいまいなケースがいくつかあります。
このような場合、シリアライザーの Meta.validators
属性に空のリストを指定することで、自動的に生成されたバリデーターを無効にすることができます。
オプションフィールド
デフォルトでは、「unique together」バリデーションは、すべてのフィールドが required=True
であることを強制します。場合によっては、フィールドの 1 つに明示的に required=False
を適用する必要がある場合があります。その場合、バリデーションの望ましい動作があいまいになります。
この場合、通常はシリアライザークラスからバリデーターを除外し、代わりに .validate()
メソッドまたはビューのいずれかでバリデーションロジックを明示的に記述する必要があります。
例:
class BillingRecordSerializer(serializers.ModelSerializer):
def validate(self, attrs):
# Apply custom validation either here, or in the view.
class Meta:
fields = ['client', 'date', 'amount']
extra_kwargs = {'client': {'required': False}}
validators = [] # Remove a default "unique together" constraint.
ネストされたシリアライザーの更新
既存のインスタンスに更新を適用する場合、一意性バリデーターは、一意性チェックから現在のインスタンスを除外します。現在のインスタンスは、シリアライザーの属性として存在し、シリアライザーをインスタンス化するときに instance=...
を使用して最初に渡されたため、一意性チェックのコンテキストで使用できます。
ネストされたシリアライザーでの更新操作の場合、インスタンスが使用できないため、この除外を適用する方法はありません。
ここでも、シリアライザークラスからバリデーターを明示的に削除し、バリデーション制約のコードを .validate()
メソッドまたはビューに明示的に記述することが望ましいでしょう。
複雑なケースのデバッグ
ModelSerializer
クラスが生成する動作が正確にわからない場合は、通常、manage.py shell
を実行して、シリアライザーのインスタンスを出力し、自動的に生成されるフィールドとバリデーターを検査することをお勧めします。
>>> serializer = MyComplexModelSerializer()
>>> print(serializer)
class MyComplexModelSerializer:
my_fields = ...
また、複雑なケースでは、デフォルトのModelSerializer
の動作に頼るよりも、シリアライザークラスを明示的に定義する方が良い場合が多いことに留意してください。これには少しコードが必要になりますが、結果の動作がより透明になります。
カスタムバリデーターの作成
Djangoの既存のバリデーターを使用したり、独自のカスタムバリデーターを作成したりできます。
関数ベース
バリデーターは、失敗時にserializers.ValidationError
を発生させる任意のcallable(呼び出し可能オブジェクト)にすることができます。
def even_number(value):
if value % 2 != 0:
raise serializers.ValidationError('This field must be an even number.')
フィールドレベルのバリデーション
Serializer
サブクラスに.validate_<field_name>
メソッドを追加することで、カスタムフィールドレベルのバリデーションを指定できます。これはSerializerドキュメントに記載されています。
クラスベース
クラスベースのバリデーターを作成するには、__call__
メソッドを使用します。クラスベースのバリデーターは、動作をパラメーター化して再利用できるため便利です。
class MultipleOf:
def __init__(self, base):
self.base = base
def __call__(self, value):
if value % self.base != 0:
message = 'This field must be a multiple of %d.' % self.base
raise serializers.ValidationError(message)
コンテキストへのアクセス
高度なケースでは、バリデーターが追加のコンテキストとして使用されているシリアライザーフィールドを渡されるようにしたい場合があります。これを行うには、バリデータークラスにrequires_context = True
属性を設定します。__call__
メソッドは、serializer_field
またはserializer
を追加の引数として呼び出されます。
class MultipleOf:
requires_context = True
def __call__(self, value, serializer_field):
...