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')
- [Django]-Django: reverse parametrized url in JavaScript
- [Django]-Django – How to pass a User object to a form in views.py
- [Django]-How do I delete a collection in Django Rest Api?
- [Django]-Pass a variable ( a flag ) from urls.py to views.py in django