Sharing validation with the admin under DRF 3

28 views
Skip to first unread message

Ben Warren

unread,
Jul 2, 2020, 6:00:59 PM7/2/20
to Django REST framework
I have a question about writing reusable validation logic shared between the admin and DRF. If I have this setup: 

class Foo(models.Model):
 
from = models.IntegerField()
  to
= models.IntegerField()
 
def clean(self): if self.from > self.to:
   
raise ValidationError("`From` must be before or equal to `to`")

class FooSerializer(serializers.ModelSerializer):
 
class Meta:
    model
= Foo

Then starting with DRF3 the clean() validation will not be called by the serializer. The docs mention that I could use this mixin: 

def validate(self, attrs):
  model
= self.Meta.model
  instance
= Model(attrs)
  instance
.clean()


but that it is dispreferred, and suggests "you really should look at properly separating the validation logic out of the model method if possible". How would I factor logic like the above so that it could be used in the admin and in DRF? What if the logic gets more complex, like "field B can be populated only if field A is > 7"?

Would I do something like this?  The reach into __dict__ feels weird.

def ordervalidator(dct):
   
if self.from > self.to:
     
raise ValidationError # Using Django ValidationError, because the model admin needs it?

class Foo(models.Model):
 
from = models.IntegerField()
  to
= models.IntegerField()
  validators
= [ordervalidator]
 
def clean(self):
   
for validator in self.validators:
      validator
(self.__dict__)    

class FooSerializer(serializers.ModelSerializer): 
  
class Meta: 
    model 
= Foo 
    validators = [ordervalidator]

Brent O'Connor

unread,
Jul 6, 2020, 11:17:19 AM7/6/20
to Django REST framework
I think all you need to do is add the following to your model.

class Foo(models.Model):
 
from = models.IntegerField()
  to
= models.IntegerField()

 
def clean(self): if self.from > self.to:
     
raise ValidationError("`From` must be before or equal to `to`")

  def save(self, **kwargs):
     
self.full_clean()
     
super().save(**kwargs)


Carl Nobile

unread,
Jul 6, 2020, 1:43:12 PM7/6/20
to django-res...@googlegroups.com
What @Brent said is basically correct, however, it will fail if you have model mixins that change fields. That's because the field will not be changed until after the full_clean() method is called.
So how to fix this? It's not too difficult. I create a mixin that just does the full_clean() as in the following:

class ValidateOnSaveMixin:
    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)


In your class you would do this:

class Foo(ValidateOnSaveMixin, models.Model):
    from = models.IntegerField()
    to 
= models.IntegerField()
    validators 
= [ordervalidator]
  
    def clean(self):
        
for validator in self.validators:
            validator
(self.__dict__)

Always be sure you put the ValidateOnSaveMixin class is just before the models.Model and all other mixins before ValidateOnSaveMixin. Getting the MRO correct is important here.

~Carl

--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-fram...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-rest-framework/d4d5e79b-6aa5-4afe-8b45-4ada71ef5060o%40googlegroups.com.


--
-------------------------------------------------------------------------------
Carl J. Nobile (Software Engineer)
carl....@gmail.com
-------------------------------------------------------------------------------
Reply all
Reply to author
Forward
0 new messages