web-dev-qa-db-ja.com

Django REST Frameworkカスタムフィールド検証

モデルのカスタム検証を作成して、そのstart_dateend_dateの前にあり、ほぼ不可能であることを確認しようとしています。

私が試したもの:

  • 組み込みDjangoバリデーター:これをチェックするものはありません

  • 次のように自分で書きます:

    def validate_date(self):
       if self.start_date < self.end_date:
            raise serializers.ValidationError("End date must be after start date.")
    

Serializerクラス(およびモデル)に追加したコードの一部ですが、どちらの場所でも呼び出されないようです。

また、 this が役に立つかもしれないコードを見つけましたが、私のメソッドに統合する方法がわかりません-1つのモデル属性を検証するのにうまくいくようですが、 2つの属性間。

私のモデル:

class MyModel(models.Model):

    created = models.DateTimeField(auto_now_add=True)
    relation_model = models.ForeignKey(RelationModel, related_name="mymodels")
    priority = models.IntegerField(
        validators = [validators.MinValueValidator(0), validators.MaxValueValidator(100)])
    start_date = models.DateField()
end_date = models.DateField()

    @property
    def is_active(self):
        today = datetime.date.today()
        return (today >= self.start_date) and (today <= self.end_date)

    def __unicode__(self):
        ...

    class Meta:
        unique_together = ('relation_model', 'priority', 'start_date', 'end_date')

Fyi、他のすべての検証は機能します!

私のシリアライザー:

class MyModelSerializer(serializers.ModelSerializer):

    relation_model = RelationModelSerializer
    is_active = serializers.Field(source='is_active')

    def validate_date(self):
        if self.start_date > self.end_date:
            raise serializers.ValidationError("End date must be after start date.")   

    class Meta:
        model = MyModel
        fields = (
            'id', 'relation_model', 'priority', 'start_date', 'end_date', 'is_active'
        )

私の見解:

class MyModelList(generics.ListCreateAPIView):
    permission_classes = (IsAdminUser,)
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    ordering = ('priority')
54
Gabi

dateはシリアライザのフィールドではないため、validate_dateは呼び出されないため、オブジェクト全体の検証(validate())を使用する必要があります。 ドキュメントから

class MySerializer(serializers.ModelSerializer):
    def validate(self, data):
        """
        Check that the start is before the stop.
        """
        if data['start_date'] > data['end_date']:
            raise serializers.ValidationError("finish must occur after start")
        return data

DRF 3.0より前では、モデルのクリーン関数に追加することもできましたが、これはDRF 3.0ではもう呼び出されません。

class MyModel(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()
    def clean(self):
        if self.end_date < self.start_date:
            raise ValidationError("End date must be after start date.")
62
jgadelange

jgadelangeの答えは、おそらくDjango rest 3より前に機能しました。 Djangoレストフレームワーク3 *バージョンを使用している人がいれば、これはその人にとって役立つと思います。検証プロセスをモデルレベルで維持する必要があり、クリーンなメソッドが1つのソリューションになる場合があります。しかし、Django残りのフレームワークの発表では here と言います。誰かがmodel .cleanメソッドで残りの呼び出しを検証したい場合、シリアライザのvalidateメソッドをオーバーライドし、次の方法でこのシリアライザクラスからメソッドを削除します

(docは言う:clean()メソッドはシリアライザー検証の一部として呼び出されないため)

class MySerializer(serializers.ModelSerializer):

   def validate(self, attrs):
     instance = MyModel(**attrs)
     instance.clean()
     return attrs

とモデル

class MyModel(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()

    def clean(self):
        if self.end_date < self.start_date:
            raise ValidationError("End date must be after start date.")
20
Amir

ここでの別の答えは、シリアライザーのvalidate()メソッドをオーバーライドすることを選択した場合の状況に関して役立つかもしれません。

Django REST Frameworkのシリアライザー検証の順序 に関する回答については、 serializer.validate() メソッドが検証シーケンス。ただし、その前にフィールドのバリデーターが呼び出されます serializer.to_internal_value() で、最後にValidationErrorを上げます。

これは、カスタム検証エラーがデフォルトのエラーと重ならないことを意味します。

私の意見では、望ましい動作を実現する最もクリーンな方法は、シリアライザクラスで ターゲットフィールドメソッド 検証を使用することです。

def validate_end_date(self, value):
    # validation process...
    return value

この場合のstart_dateなど、モデルから別のフィールド値が必要な場合は、次のようにして取得できます(プロセスは完了していないため、まだ検証されていません)。

# `None` here can be replaced with field's default value
start_date = 'start_date' in self.initial_data
    and self.initial_data['start_date'] or None
14
Damaged Organic

これをフィールドでクラスベースのバリデーターとして実装するのに苦労している場合...

from rest_framework.serializers import ValidationError

class EndDateValidator:
    def __init__(self, start_date_field):
        self.start_date_field = start_date_field

    def set_context(self, serializer_field):
        self.serializer_field = serializer_field

    def __call__(self, value):
        end_date = value
        serializer = self.serializer_field.parent
        raw_start_date = serializer.initial_data[self.start_date_field]

        try:
            start_date = serializer.fields[self.start_date_field].run_validation(raw_start_date)
        except ValidationError:
            return  # if start_date is incorrect we will omit validating range

        if start_date and end_date and end_date < start_date:
            raise ValidationError('{} cannot be less than {}'.format(self.serializer_field.field_name, self.start_date_field)

シリアライザーにstart_dateおよびend_dateフィールドがあると仮定すると、validators=[EndDateValidator('start_date')]end_dateフィールドに設定できます。

4
Konrad Perzyna

Konradの回答を拡大します。非常に明示的であり、他のフィールドを使用するときに他のフィールドの検証を呼び出しているため、私はそれが好きです。したがって、より安全で、おそらく冗長になります(一部の検証は2回呼び出されます)

最初に注意することは、このように実装すると、run_validatorを実行したときに、validators変数に設定された検証のみが表示されることです。したがって、たとえばvalidate_メソッドを使用してフィールドを検証した場合、そのフィールドは実行されません。

また、私はそれを継承可能にしたので、検証関数を再実装してコードを再利用できます。

validators.py

from rest_framework.serializers import ValidationError

class OtherFieldValidator:

    #### This part is the same for all validators ####

    def __init__(self, other_field):
        self.other_field = other_field # name of parameter

    def set_context(self, serializer_field):
        self.serializer_field = serializer_field # name of field where validator is defined

    def make_validation(self,field, other_field):
        pass

    def __call__(self, value):
        field = value
        serializer = self.serializer_field.parent # serializer of model
        raw_other_field = serializer.initial_data[self.other_field] # data del otro campo

        try:
            other_field = serializer.fields[self.other_field].run_validation(raw_other_field)
        except ValidationError:
            return # if date_start is incorrect we will omit validating range

    #### Here is the only part that changes ####

        self.make_validation(field,other_field)

class EndDateValidator(OtherFieldValidator):

    def make_validation(self,field, other_field):
        date_end = field
        date_start = other_field
        if date_start and date_end and date_end < date_start:
            raise ValidationError('date cannot be')

したがって、シリアライザーは次のようになります。serializers.py

# Other imports
from .validators import EndDateValidator

 def myfoo(value):                                                        
     raise ValidationError("start date error")                             

 class MyModelSerializer(serializers.ModelSerializer):                                        
     class Meta:                                                          
         model = MyModel                                                      
         fields = '__all__'                                                                                       
         extra_kwargs = {                                                 
             'date_end': {'validators': [EndDateValidator('date_start')]},
             'date_start': {'validators': [myfoo]},                       
         }                                                                
0
Gonzalo