astrognocci
astrognocci

Reputation: 1085

Django rest framework & model validation

Getting started using django-rest-framework, and I'm having some trouble regarding validation.

I have a basic model, and i've applied validators to a copple of its fields (A regular MaxLengthValidator and a custom RegexValidator, ending up with something like this:

class ZipCodeValidator(RegexValidator):
    regex = '^([0-9]{5})$'
    message = u'Invalid ZipCode.'


class User(AbstractUser, BaseUser):
    """
    Custom user model
    """

    # ... other fields ...
    zipcode = models.CharField(
        max_length=5, blank=True, validators=[ZipCodeValidator()]
    )
    description = models.TextField(
        null=True, blank=True, max_length=1000, validators=[MaxLengthValidator(1000)]
    )

I then created a ModelSerializer mapped to this model, with a few additional fields and methods. This is all served by a very simple `RetrieveUpdateAPIView.

I'm noticing that the validators are not called (i can enter anything in the zipcode field, or exceed 1000 characters for the description).

Quick and dirty solution has been to override the two fields at the serializer level and assigne them the validator there:

class UserSerializer(serializers.ModelSerializer):
    zipcode = serializers.WritableField(
        source='zipcode', required=False, validators=[ZipCodeValidator()]
    )
    description = serializers.WritableField(
        source='description', required=False, validators=[MaxLengthValidator(1000)]
    )

This works fine, but i don't like it much. I'd rather have this validation occur at the model level to be safer (i wouldn't having mind custom or additional validation on the serializer, but those rules will need to be enforced in all cases). Since serializers work a lot like django forms, i expected them to call the model's clean & cie method before saving them, but a quick look at the source seems to indicate it does not.

This is a bit annoying, it forces me to duplicate much of the fields code if I want to ensure the validation always happens, and i'd rather keep this as DRY as possible.

I might be missing something, but is there a nice and clean way to ensure those validators will be run by the serializer before updating the model ?

EDIT: Doubled checked the source, turns out that the instance's full_clean method is indeed called by the view before saving it to the db, which in turns ends up running the model's validator. Still lost as to why those don't seem to run, tho.

Upvotes: 27

Views: 10335

Answers (2)

Timothé Delion
Timothé Delion

Reputation: 1460

Actually, I think the first solution you proposed @astrognocci, even if it seems quite verbous, is a good solution for Django REST Framekork v3.0 +,

Indeed, .full_clean() method is not called anymore in ModelSerializer validation process, as explained in this post

So writing custom class validators - that can be used in Model and ModelSerializer - seems a relevant option for DRY concerns, and consistence.

Upvotes: 2

Norman8054
Norman8054

Reputation: 824

This works for me:

class ZipCodeValidator(RegexValidator):
    regex = r'^[0-9]{5}$'
    message = 'Invalid ZipCode.'


class MyModel(models.Model):
    zipcode = models.CharField(max_length=5, blank=True, validators=[ZipCodeValidator()])


class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel


>>> s1 = MyModelSerializer(data={'zipcode': '34234'})
>>> s1.is_valid()
True
>>> s2 = MyModelSerializer(data={'zipcode': 'f3434'})
>>> s2.is_valid()
False
>>> s2.errors
{'zipcode': [u'Invalid ZipCode.']}

Upvotes: 3

Related Questions