Django REST Framework自定义字段验证

Gab*_*abi 59 django 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.")
    
    Run Code Online (Sandbox Code Playgroud)

我已经添加到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')
Run Code Online (Sandbox Code Playgroud)

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'
        )
Run Code Online (Sandbox Code Playgroud)

我的看法:

class MyModelList(generics.ListCreateAPIView):
    permission_classes = (IsAdminUser,)
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    ordering = ('priority')
Run Code Online (Sandbox Code Playgroud)

jga*_*nge 70

您应该使用对象范围的验证(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
Run Code Online (Sandbox Code Playgroud)

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.")
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,从DRF 3.0开始,将不再按此说明调用模型的“清理”方法... http://www.django-rest-framework.org/topics/3.0-announcement/#differences-between模型序列化器验证和模型形式 (2认同)
  • @David M. - 该 URL 现在无效。这是正确的...... https://www.django-rest-framework.org/community/3.0-announcement/ (2认同)

Ami*_*mir 20

jgadelange的答案可能在django休息3之前有效.如果任何人使用django rest framework 3*版本,我认为这对那些人来说会有所帮助.一个应该在模型级别保持验证过程,清洁方法可能是一个解决方案.但是Django的REST框架公告称这里,如果有人想验证模型清洁机壳方法REST的电话,他/她应该重写串行validate方法和需要调用方法清洁通过以下方式形成这种序列化器类

(因为doc说:clean()方法不会被作为序列化器验证的一部分调用)

class MySerializer(serializers.ModelSerializer):

   def validate(self, attrs):
     instance = MyModel(**attrs)
     instance.clean()
     return attrs
Run Code Online (Sandbox Code Playgroud)

和模型

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.")
Run Code Online (Sandbox Code Playgroud)

  • 您的文档链接现在无效。你能更新一下吗?谢谢! (2认同)
  • 链接的文档说“在某些情况下,您确实需要在模型 .clean() 方法中保留验证逻辑,而不能将其分离到序列化器 .validate() 中。您可以通过显式实例化模型来做到这一点.validate() 方法中的实例。同样,如果可能的话,您确实应该考虑将验证逻辑与模型方法正确分离,但上述内容在某些向后兼容情况下或对于简单的迁移路径可能有用。” 所以它不是说“这样做”;而是说“这样做”。它说您可以在模型中执行此操作,但不要这样做,请改用序列化器。 (2认同)

Dam*_*nic 14

如果选择覆盖序列化程序的validate()方法,那么这里的另一个答案可能是有用的.

关于Django REST Framework中的序列化程序验证顺序的答案,我必须说serializer.validate()在验证序列结束时调用该方法.然而,现场的验证之前被调用,以serializer.to_internal_value(),养ValidationError在最后.

这意味着自定义验证错误不会与默认错误堆叠.

在我看来,实现所需行为的最简洁方法是在序列化程序类中使用目标字段方法验证:

def validate_end_date(self, value):
    # validation process...
    return value
Run Code Online (Sandbox Code Playgroud)

如果您需要模型中的其他字段值,例如start_date在这种情况下,您可以使用以下方法获取它们(但未经验证,因为流程未完成):

# `None` here can be replaced with the field's default value
start_date = self.initial_data.get('start_date')
Run Code Online (Sandbox Code Playgroud)


Gon*_*alo 7

我将扩展康拉德的答案。我喜欢它,因为它非常明确,而且当我们使用它们时,您正在调用其他字段的验证。所以它更安全,可能会是多余的(一些验证会被调用两次)

首先要注意的是,如果我们这样实现,当我们运行 run_validator 时,只会出现在 validators 变量中设置的验证。因此,如果我们使用 validate_ 方法验证一个字段,它将不会运行。

此外,我已将其设置为可继承,因此我们可以重新实现验证功能并重新使用代码。

验证器.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')
Run Code Online (Sandbox Code Playgroud)

所以序列化器将是这样的: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]},                       
         }                                                                
Run Code Online (Sandbox Code Playgroud)


小智 5

如果有人在现场努力将其实现为基于类的验证器......

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)
Run Code Online (Sandbox Code Playgroud)

假设您的序列化程序中有start_dateend_date字段,那么您可以end_date使用validators=[EndDateValidator('start_date')].


Mic*_*huk 5

如果您更喜欢更简单的解决方案,那么 jgadelange 和 Damaged Organic 的解决方案非常有趣,特别是如果您不打算多次重用验证器,但我建议进行改进:我会使用对象级验证器,提出一个 dict该字段的验证错误:

def validate(self, data):
    ...
    if data["start_date"] > data["end_date"]:
        raise serializers.ValidationError(
            {"end_date": "End date must be after start date."}
        )
    ...
Run Code Online (Sandbox Code Playgroud)

我正在利用ValidationError 类接受带有错误详细信息的对象。通过这种方式,我可以模拟字段级验证的相同行为,将错误消息与字段本身联系起来,同时我仍然可以比较每个单独验证后的日期。

这对于确保您不与在比较之前需要转换的不干净的开始日期进行比较很重要(就像您使用 self.initial_data 时所做的那样)。