Подтвердить что ты не робот

Проверка пользовательских полей Django REST Framework

Я пытаюсь создать пользовательскую проверку для модели, чтобы проверить, что ее start_date находится перед ее end_date, и это почти невозможно.

Материал, который я пробовал:

  • встроенные проверки подлинности Django: для этого не проверяйте

  • писать мои собственные, например:

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

Этот бит кода, который я добавил в класс Serializer (а затем модель), но он, похоже, не вызван в любом месте.

Я также нашел этот > бит кода, который может быть полезен, но я не знаю, как интегрироваться в моем методе - кажется, что это сработает подтвердите один атрибут модели, но мне нужно проверить два атрибута.

Моя модель:

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')
4b9b3361

Ответ 1

Вы должны использовать широкую проверку объекта (validate()), так как validate_date никогда не будет вызываться, поскольку date не является полем в сериализаторе. Из документации:

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

Pre 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.")

Ответ 2

Ответ jgadelange работал до того, как django rest 3 вероятно. Если кто-то использует версию django rest framework 3 *, я думаю, что это было бы полезно для этого народа. следует продолжать процесс проверки на уровне модели, и единственным методом может быть метод очистки. Но объявление django rest framework говорит здесь, что если кто-то хочет проверить метод rest-call в методе modelclean, он/она должен переопределить метод проверки serializer и должен вызывать чистый метод для этого класса сериализатора следующим образом

(потому что 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.")

Ответ 3

Другой ответ здесь может быть полезен, если вы решите переопределить метод serialify validate().

Что касается ответа на порядок проверки Serializer в Django REST Framework, я должен сказать, что метод serializer.validate() вызывается в конце последовательности проверки. Тем не менее, валидаторы полей вызываются до этого, в serializer.to_internal_value(), поднимая ValidationError в конце.

Это означает, что пользовательские ошибки проверки не складываются по умолчанию.

На мой взгляд, самым чистым способом достижения желаемого поведения является использование проверки метода целевого поля в классе serializer:

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

В случае, если вам понадобится другое значение поля из модели, например start_date, вы можете получить их (но не подтвержденный, поскольку процесс не завершен):

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

Ответ 4

В случае, если кто-то борется с реализацией этого как основанного на классах валидатора в поле...

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, вы можете установить в поле end_date с помощью validators=[EndDateValidator('start_date')].