186👍
You should use an object wide validation (validate()
), since validate_date
will never be called since date
is not a field on the serializer. 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
As suggested by Michel Sabchuk you can add the validation error to the end_date
field:
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
Another possibility is to create a validator. I created one based on the code for 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()]
Pre DRF 3.0 you could also add it to the clean function of a model, but this is not called anymore in 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.")
51👍
Another answer here might be useful, regarding the situation if one chooses to override serializer’s validate()
method.
Regarding answer on 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()
, raising ValidationError
at the end.
This means that custom validation errors do not stack with default ones.
In my opinion cleanest way to achieve desired behaviour is by using target field method validation in serializer class:
def validate_end_date(self, value):
# validation process...
return value
In case if you need another field value from the model, such as start_date
in this case, you can get them (yet unvalidated, as a process is not complete) with:
# `None` here can be replaced with the field's default value
start_date = self.initial_data.get('start_date')
- [Django]-How do you configure Django to send mail through Postfix?
- [Django]-Django count RawQuerySet
- [Django]-How to change site title, site header and index title in Django Admin?
31👍
jgadelange’s answer worked before django rest 3 probably. If any one using the django rest framework 3* version, I think this would be helpful for that folk. one should keep validation process in model level and clean method may be the one solution. But django rest framework announcement says here that, if someone wants to validate rest-call in model .clean method, he/she should override the serializer validate method and need to call the clean method form this serializer class by the following way
(because doc says : clean() method will not be called as part of serializer validation)
class MySerializer(serializers.ModelSerializer):
def validate(self, attrs):
instance = MyModel(**attrs)
instance.clean()
return attrs
and model
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.")
- [Django]-How to loop over form field choices and display associated model instance fields
- [Django]-How to access Enum types in Django templates
- [Django]-Django proxy model and ForeignKey
12👍
The jgadelange and Damaged Organic’s solutions are pretty interesting if you prefer a simpler solution, specially if you don’t plan to reuse the validator more than once, but I would suggest an improvement: I would use the object level validator, raising a dict with the field’s validation error:
def validate(self, data):
...
if data["start_date"] > data["end_date"]:
raise serializers.ValidationError(
{"end_date": "End date must be after start date."}
)
...
I’m taking advantage that the ValidationError class accepts an object with the error details. This way, I can emulate the same behavior of a field level validation, tying the error message with the field itself, while I can still compare the dates after each one was validated individually.
This is important to ensure that you are not comparing to an unclean start date that you would need to cast before comparison (as you would be doing if you were using self.initial_data).
- [Django]-Paginating the results of a Django forms POST request
- [Django]-Why is logged_out.html not overriding in django registration?
- [Django]-.filter() vs .get() for single object? (Django)
7👍
I will expand Konrad answer. I like it because its quite explicit, and also you are calling validation on other fields when we use them. So it is safer, probably will be redundant (some validations will be called twice)
First thing to note, is that if we implement like this, when we run run_validator, only validations set in validators variable will appear. So if we validate a field for example with the validate_ methods, it will not be run.
Also, I have make it inheritable, so we can reimplement the validation function and rehuse the code.
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')
So the serializer will be like this : 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]},
}
- [Django]-Define css class in django Forms
- [Django]-How to express a One-To-Many relationship in Django?
- [Django]-Naming convention for Django URL, templates, models and views
6👍
In case anyone struggling with implementing this as class-based validator on field…
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)
Assuming you have start_date
and end_date
fields in your serializer, you can then set in on end_date
field with validators=[EndDateValidator('start_date')]
.
- [Django]-How to check Django version
- [Django]-Speeding up Django Testing
- [Django]-Set Django IntegerField by choices=… name