validators.py

バリデーター

バリデーターは、異なる種類のフィールド間でバリデーションロジックを再利用するのに役立ちます。

Django ドキュメント

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_dateunique_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):
        ...