Django REST Framework 自定义字段验证
Django REST Framework custom fields validation
我正在尝试为模型创建自定义验证,以检查其 start_date
是否在其 end_date
之前,事实证明这几乎是不可能的。
我尝试过的东西:
内置 Django 验证器:none 检查这个
写我自己的,像这样:
def validate_date(self):
if self.start_date < self.end_date:
raise serializers.ValidationError("End date must be after start date.")
我已经添加到序列化器 class(然后是模型)的那段代码,但它似乎没有在任何一个位置被调用。
我还发现了 this 一些可能有用的代码,但我不知道如何将其集成到我的方法中 - 似乎可以验证一个模型属性,但我需要检查两个属性。
我的模特:
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')
您应该使用对象范围的验证 (validate()
),因为 validate_date
永远不会被调用,因为 date
不是序列化程序上的字段。 From the documentation:
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 之前,您还可以将它添加到模型的清理功能中,但在 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 framework 3* 版本,我认为这会对那个人有所帮助。应该将验证过程保持在模型级别,而清洁方法可能是一种解决方案。但是 django rest framework 公告说 here 如果有人想在模型 .clean 方法中验证 rest-call,he/she 应该重写序列化器验证方法并且需要从这个序列化器调用 clean 方法 class 通过以下方式
(因为文档说: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()
方法的情况,这里的另一个答案可能会有用。
关于 Order of Serializer Validation in Django REST Framework, I must say that serializer.validate()
method is called at the end of the validation sequence. However, field's validators are called before that, in serializer.to_internal_value()
的回答,最后提出 ValidationError
。
这意味着 自定义验证错误不会与默认错误叠加。
在我看来,实现所需行为的最简洁方法是在序列化程序中使用 target field method 验证 class:
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')
万一有人在现场将此作为基于 class 的验证器实施时遇到困难...
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')]
.
我将扩展 Konrad 的答案。我喜欢它,因为它非常明确,而且当我们使用它们时,您也在调用其他字段的验证。所以它更安全,可能会是多余的(有些验证会被调用两次)
首先要注意的是,如果我们这样实现,当我们 运行 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]},
}
如果您更喜欢更简单的解决方案,jgadelange 和 Damaged Organic 的解决方案非常有趣,特别是如果您不打算多次重用验证器,但我建议改进:我会使用对象级验证器, 使用字段的验证错误引发字典:
def validate(self, data):
...
if data["start_date"] > data["end_date"]:
raise serializers.ValidationError(
{"end_date": "End date must be after start date."}
)
...
我正在利用 ValidationError class accepts an object with the error details。这样,我可以模拟字段级验证的相同行为,将错误消息与字段本身联系起来,同时我仍然可以在每个单独验证后比较日期。
这很重要,可确保您不会与比较前需要投射的不干净的开始日期进行比较(如果您使用 self.initial_data,就会这样做)。
我正在尝试为模型创建自定义验证,以检查其 start_date
是否在其 end_date
之前,事实证明这几乎是不可能的。
我尝试过的东西:
内置 Django 验证器:none 检查这个
写我自己的,像这样:
def validate_date(self): if self.start_date < self.end_date: raise serializers.ValidationError("End date must be after start date.")
我已经添加到序列化器 class(然后是模型)的那段代码,但它似乎没有在任何一个位置被调用。
我还发现了 this 一些可能有用的代码,但我不知道如何将其集成到我的方法中 - 似乎可以验证一个模型属性,但我需要检查两个属性。
我的模特:
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')
您应该使用对象范围的验证 (validate()
),因为 validate_date
永远不会被调用,因为 date
不是序列化程序上的字段。 From the documentation:
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 之前,您还可以将它添加到模型的清理功能中,但在 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 framework 3* 版本,我认为这会对那个人有所帮助。应该将验证过程保持在模型级别,而清洁方法可能是一种解决方案。但是 django rest framework 公告说 here 如果有人想在模型 .clean 方法中验证 rest-call,he/she 应该重写序列化器验证方法并且需要从这个序列化器调用 clean 方法 class 通过以下方式
(因为文档说: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()
方法的情况,这里的另一个答案可能会有用。
关于 Order of Serializer Validation in Django REST Framework, I must say that serializer.validate()
method is called at the end of the validation sequence. However, field's validators are called before that, in serializer.to_internal_value()
的回答,最后提出 ValidationError
。
这意味着 自定义验证错误不会与默认错误叠加。
在我看来,实现所需行为的最简洁方法是在序列化程序中使用 target field method 验证 class:
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')
万一有人在现场将此作为基于 class 的验证器实施时遇到困难...
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')]
.
我将扩展 Konrad 的答案。我喜欢它,因为它非常明确,而且当我们使用它们时,您也在调用其他字段的验证。所以它更安全,可能会是多余的(有些验证会被调用两次)
首先要注意的是,如果我们这样实现,当我们 运行 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]},
}
如果您更喜欢更简单的解决方案,jgadelange 和 Damaged Organic 的解决方案非常有趣,特别是如果您不打算多次重用验证器,但我建议改进:我会使用对象级验证器, 使用字段的验证错误引发字典:
def validate(self, data):
...
if data["start_date"] > data["end_date"]:
raise serializers.ValidationError(
{"end_date": "End date must be after start date."}
)
...
我正在利用 ValidationError class accepts an object with the error details。这样,我可以模拟字段级验证的相同行为,将错误消息与字段本身联系起来,同时我仍然可以在每个单独验证后比较日期。
这很重要,可确保您不会与比较前需要投射的不干净的开始日期进行比较(如果您使用 self.initial_data,就会这样做)。