45đź‘Ť
Firstly, you shouldn’t override full_clean
as you have done. From the django docs on full_clean:
Model.full_clean(exclude=None)
This method callsModel.clean_fields()
,Model.clean()
, andModel.validate_unique()
, in that order and raises aValidationError
that has amessage_dict
attribute containing errors from all three stages.
So the full_clean
method already calls clean
, but by overriding it, you’ve prevented it calling the other two methods.
Secondly, calling full_clean
in the save
method is a trade off. Note that full_clean
is already called when model forms are validated, e.g. in the Django admin. So if you call full_clean
in the save
method, then the method will run twice.
It’s not usually expected for the save method to raise a validation error, somebody might call save
and not catch the resulting error. However, I like that you call full_clean
rather than doing the check in the save method itself – this approach allows model forms to catch the problem first.
Finally, your clean
method would work, but you can actually handle your example case in the model field itself. Define your CharField
as
name = models.CharField(max_length=128)
The blank
option will default to False. If the field is blank, a ValidationError
will be raised when you run full_clean
. Putting default=None
in your CharField
doesn’t do any harm, but it is a bit confusing when you don’t actually allow None
as a value.
8đź‘Ť
After thinking about Alasdair’s answer and doing addtional reading, my sense now is that Django’s models weren’t designed so as to be validated on a model-only basis as I’m attempting to do. Such validation can be done, but at a cost, and it entails using validation methods in ways they weren’t intended for.
Instead, I now believe that any constraints other than those that can be entered directly into the model field declarations (e.g. “unique=True”) are supposed to be performed as a part of Form or ModelForm validation. If one wants to guard against entering invalid data into a project’s database via any other means (e.g. via the ORM while working within the Python interpreter), then the validation should take place within the database itself. Thus, validation could be implemented on three levels: 1) First, implement all constraints and triggers via DDL in the database; 2) Implement any constraints available to your model fields (e.g. “unique=True”); and 3) Implement all other constraints and validations that mirror your database-level constraints and triggers within your Forms and ModelForms. With this approach, any form validation errors can be re-displayed to the user. And if the programmer is interacting directly with the database via the ORM, he/she would see the database exceptions directly.
Thoughts anyone?
- [Django]-How to filter objects for count annotation in Django?
- [Django]-GeoDjango GEOSException error
- [Django]-Add Text on Image using PIL
7đź‘Ť
Capturing the pre-save signals on on my models ensured clean will be called automatically.
from django.db.models.signals import pre_save
def validate_model(sender, **kwargs):
if 'raw' in kwargs and not kwargs['raw']:
kwargs['instance'].full_clean()
pre_save.connect(validate_model, dispatch_uid='validate_models')
- [Django]-Mysql error : ERROR 1018 (HY000): Can't read dir of '.' (errno: 13)
- [Django]-No Module named django.core
- [Django]-Auto-create primary key used when not defining a primary key type warning in Django
0đź‘Ť
Thanks @Kevin Parker for your answer, quite helpful!
It is common to have models in your app outside of the ones you define, so here is a modified version you can use to scope this behavior only to your own models or a specific app/module as desired.
from django.db.models.signals import pre_save
import inspect
import sys
MODELS = [obj for name, obj in
inspect.getmembers(sys.modules[__name__], inspect.isclass)]
def validate_model(sender, instance, **kwargs):
if 'raw' in kwargs and not kwargs['raw']:
if type(instance) in MODELS:
instance.full_clean()
pre_save.connect(validate_model, dispatch_uid='validate_models')
This code will run against any models defined inside the module where it is executed but you can adapt this to scope more strictly or to be a set of modules / apps if desired.
- [Django]-Find Monday's date with Python
- [Django]-Django exception middleware: TypeError: object() takes no parameters
- [Django]-Modulus % in Django template