我正在尝试为模型创建自定义验证,以检查其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类(然后是模型)的那段代码,但似乎没有在任何一个位置调用它。
我还发现了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')
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')
答案 0 :(得分:52)
您应该使用对象范围的验证(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
Pre 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.")
答案 1 :(得分:18)
jgadelange的答案可能在django休息3之前有效。如果任何人使用django rest framework 3 *版本,我认为这对那些人来说会有所帮助。一个应该在模型级别保持验证过程,清洁方法可能是一个解决方案。但是django rest框架公告说here如果有人想在模型.clean方法中验证rest-call,他/她应该覆盖序列化器验证方法,并且需要通过以下方法调用此序列化器类的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.")
答案 2 :(得分:10)
如果选择覆盖序列化程序的validate()
方法,则此处的另一个答案可能会有用。
关于Order of Serializer Validation in Django REST Framework的答案,我必须说在验证序列结束时调用serializer.validate()
方法。但是,在serializer.to_internal_value()
之前调用字段的验证程序,最后提升ValidationError
。
这意味着自定义验证错误不会与默认错误叠加。
在我看来,实现所需行为的最简洁方法是在序列化程序类中使用target field method验证:
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
答案 3 :(得分:4)
如果有人在现场努力将其作为基于类的验证器来实现......
migrations.RunPython(drop_index_if_exists, create_index_if_not_exists)
假设您的序列化工具中有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
字段中设置。
答案 4 :(得分:4)
如果您更喜欢简单的解决方案,尤其是如果您不打算多次重复使用验证器,那么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时那样)。
答案 5 :(得分:0)
我将扩展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]},
}