[Django]-Using Validators in Django not working properly

4👍

I’m sure there are many ways to handle this, but I finally decided to adopt a common practice in all my Django projects:

when a Model requires validation, I override clean() to collect all validation logic in a single place and provide appropriate error messages.

In clean(), you can access all model fields, and do not need to return anything; just raise ValidationErrors as required:

from django.db import models
from django.core.exceptions import ValidationError


class MyModel(models.Model):

    def clean(self):
         
        if (...something is wrong in "self.field1" ...) {
            raise ValidationError({'field1': "Please check field1"})
        }
        if (...something is wrong in "self.field2" ...) {
            raise ValidationError({'field2': "Please check field2"})
        }

        if (... something is globally wrong in the model ...) {
            raise ValidationError('Error message here')
        }

The admin already takes advantages from this, calling clean() from ModelAdmin.save_model(),
and showing any error in the change view; when a field is addressed by the ValidationError,
the corresponding widget will be emphasized in the form.

To run the very same validation when saving a model programmatically, just override save() as follows:

class MyModel(models.Model):

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

Proof:

file models.py

from django.db import models


class Model1(models.Model):

    def clean(self):
        print("Inside Model1.clean()")

    def save(self, *args, **kwargs):
        print('Enter Model1.save() ...')
        super().save(*args, **kwargs)
        print('Leave Model1.save() ...')
        return

class Model2(models.Model):

    def clean(self):
        print("Inside Model2.clean()")

    def save(self, *args, **kwargs):
        print('Enter Model2.save() ...')
        self.full_clean()
        super().save(*args, **kwargs)
        print('Leave Model2.save() ...')
        return

file test.py

from django.test import TestCase
from project.models import Model1
from project.models import Model2

class SillyTestCase(TestCase):

    def test_save_model1(self):
        model1 = Model1()
        model1.save()

    def test_save_model2(self):
        model2 = Model2()
        model2.save()

Result:

❯ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
Enter Model1.save() ...
Leave Model1.save() ...
.Enter Model2.save() ...
Inside Model2.clean()
Leave Model2.save() ...
.
----------------------------------------------------------------------
Ran 2 tests in 0.002s

OK
Destroying test database for alias 'default'...

0👍

Validators run only when you use ModelForm. If you directly call comment.save(), validator won’t run. link to docs

So either you need to validate the field using ModelForm or you can add a pre_save signal and run the validation there (you’ll need to manually call the method, or use full_clean to run the validations).
Something like:

from django.db.models.signals import pre_save

def validate_model(sender, instance, **kwargs):
    instance.full_clean()

pre_save.connect(validate_model, dispatch_uid='validate_models')

Leave a comment