I'm creating a django application which uses both the Django Rest Framework and the plain django-views as entrypoint for users.
I want to do validation both independant fields of my models, and on objects on a whole. For example:
Field: is the entered licence-plate a correct one based on a regex function. No relation to other fields.
Object: Is the entered zipcode valid for the given country. Relates to zipcode and country in the model.
For the DRF-API i use ModelSerializers which automatically call all the validators i have placed in my Model, for example:
class MyModel(models.Model):
licence_plate = CharField(max_length=20, validators=[LicencePlateValidator])
Since the validator is given in the model, the API POSTS (because i use a ModelSerializer), as well as the objects created in the django admin backend are validated.
But when i want to introduce object level validation i need to do that in the serializer's validate()-method, which means objects are only validated in the API.
I'll have to override the model's save method too, to validate the objects created in the Django admin page.
Question: This seems a bit messy to me, is there a single point where i can put the object-level validators so that they are run at the API and in the admin-page, like i did with the field-level validation (I only have to put them in my model-declaration and everything is handled)
For model-level validation, there is the Model.clean
method.
It is called if you are using ModelForm
(which is used by default in admin
), so this solves django views and admin parts.
On the other hand, DRF does not call models' clean
automatically, so you will have to do it yourself in Serializer.validate
(as the doc suggests). You can do it via a serializer mixin:
class ValidateModelMixin(object)
def validate(self, attrs):
attrs = super().validate(attrs)
obj = self.Meta.model(**attrs)
obj.clean()
return attrs
class SomeModelSerializer(ValidateModelMixin, serializers.ModelSerializer):
#...
class Meta:
model = SomeModel
or write a validator:
class DelegateToModelValidator(object):
def set_context(self, serializer):
self.model = serializer.Meta.model
def __call__(self, attrs):
obj = self.model(**attrs)
obj.clean()
class SomeModelSerializer(serializers.ModelSerializer):
#...
class Meta:
model = SomeModel
validators = (
DelegateToModelValidator(),
)
Caveats:
- an extra instantiation of your models just to call
clean
- you will still have to add the mixin/validator to your serializers
You can create a separate function validate_zipcode_with_country(zipcode, country)
which will take 2 arguments zipcode
and country
.
Then, we will call this method in the serializer's validate()
and in our model's clean()
.
from django.core.exceptions import ValidationError
def validate_zipcode_with_country(zipcode, country):
# check zipcode is valid for the given country
if not valid_zipcode:
raise ValidationError("Zipcode is not valid for this country.")
Then in your serializers.py
, you need to call this function in your validate()
function.
class MySerializer(serializers.ModelSerializer):
def validate(self, attrs):
zipcode = attrs.get('zipcode')
country = attrs.get('country')
validate_zipcode_with_country(zipcode, country) # call the function
...
Similarly, you need to override the model's clean()
and call this function.
class MyModel(models.Model):
def clean(self):
validate_zipcode_with_country(self.zipcode, self.country) # call this function
...