Where should i do the django validations for objec

2019-05-05 03:20发布

问题:

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)

回答1:

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


回答2:

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
        ...