Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where should i do the django validations for objects and fields?

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)

like image 987
Robin van Leeuwen Avatar asked Oct 03 '15 11:10

Robin van Leeuwen


People also ask

How can you validate Django model fields?

to_python() method of the models. Field subclass (obviously for that to work you must write custom fields). Possible use cases: when it is absolutely neccessary to ensure, that an empty string doesn't get written into the database (blank=False keyword argument doesn't work here, it is for form validation only)

How do I validate two fields in Django?

They go into a special “field” (called all), which you can access via the non_field_errors() method if you need to. If you want to attach errors to a specific field in the form, you need to call add_error(). So from Django documentation you can use add_error() to do what you want to achieve.

Which method has a form instance which runs validation routines for all its fields?

The run_validators() method on a Field runs all of the field's validators and aggregates all the errors into a single ValidationError . You shouldn't need to override this method.

What can be used to validate all model fields if any field is to be exempted from validation provide it in the exclude parameter?

clean_fields() method documentation: This method will validate all fields on your model. The optional exclude argument lets you provide a list of field names to exclude from validation. It will raise a ValidationError if any fields fail validation.


2 Answers

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
like image 200
Ivan Avatar answered Oct 05 '22 15:10

Ivan


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
        ...
like image 38
Rahul Gupta Avatar answered Oct 05 '22 17:10

Rahul Gupta