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

That bit of code I have added to the Serializer class (and then the model), but it does not seem to get called in either location.

I also found this bit of code that might be of use, but I don't know how to integrate in my method- it seems that it would work to validate one model attribute, but I need to check between two attributes.

My model:

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

顺便说一句,其他的验证都有用!

我的序列化程序:

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')
165494 次浏览

您应该使用对象范围的验证(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

正如 Michel Sabchuk 建议的那样,您可以将验证错误添加到 end_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({"end_date": "finish must occur after start"})
return data

另一种可能性是创建一个验证器,我根据 UniqueTogetherValidator的代码创建了一个验证器:

from rest_framework.utils.representation import smart_repr


class DateBeforeValidator:
"""
Validator for checking if a start date is before an end date field.
Implementation based on `UniqueTogetherValidator` of Django Rest Framework.
"""
message = _('{start_date_field} should be before {end_date_field}.')


def __init__(self, start_date_field="start_date", end_date_field="end_date", message=None):
self.start_date_field = start_date_field
self.end_date_field = end_date_field
self.message = message or self.message


def __call__(self, attrs):
if attrs[self.start_date_field] > attrs[self.end_date_field]:
message = self.message.format(
start_date_field=self.start_date_field,
end_date_field=self.end_date_field,
)
# Replace the following line with
#   raise serializers.ValidationError(
#       {self.end_date_field: message},
#       code='date_before',
#   )
# if you want to raise the error on the field level
raise serializers.ValidationError(message, code='date_before')


def __repr__(self):
return '<%s(start_date_field=%s, end_date_field=%s)>' % (
self.__class__.__name__,
smart_repr(self.start_date_field),
smart_repr(self.end_date_field)
)




class MySerializer(serializers.ModelSerializer):
class Meta:
# If your start/end date fields have another name give them as kwargs tot the
# validator:
#   DateBeforeValidator(
#       start_date_field="my_start_date",
#       end_date_field="my_end_date",
#   )
validators = [DateBeforeValidator()]

在 DRF 3.0之前,你也可以把它添加到模型的 clean 函数中,但是这在 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.")

Jgadelange 的回答可能在 django rest 3之前起作用了。如果有人使用 django rest 框架3 * 版本,我认为这会对他们有所帮助。一个应该保持验证过程在模型水平和清洁的方法可能是一个解决方案。但 django 休息框架公告说,如果有人想验证模型中休息调用的 给你。方法,则应重写序列化器验证方法,并需要通过以下方法调用此序列化器类中的 clean 方法

(因为 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.")

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

关于 Django REST 框架中序列化器验证的顺序上的答案,我必须说 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 the field's default value
start_date = self.initial_data.get('start_date')

如果任何人在实地实现基于类的验证器时遇到困难..。

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_dateend_date字段,那么可以使用 validators=[EndDateValidator('start_date')]end_date字段中进行设置。

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

首先要注意的是,如果我们像这样实现,当我们运行 run _ validator 时,只会出现在 validators 变量中设置的验证。因此,如果我们使用 valid_ method 验证某个字段,它将不会运行。

另外,我已经使它可继承,所以我们可以重新实现验证函数并重新使用代码。

校验器.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')

所以序列化程序应该是这样的: 序列化器 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]},
}

如果你更喜欢一个简单的解决方案,jgadelange 和受损有机的解决方案是相当有趣的,特别是如果你不打算重复使用验证器不止一次,但我建议一个改进: 我会使用对象级验证器,提出一个字段的验证错误的结果:

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

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

这一点很重要,可以确保不会与比较之前需要强制转换的不干净的开始日期进行比较(如果使用 self. initialdata 就会这样做)。