Reputation: 1085
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
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
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