Package: django-model-validation
django-model-validationdjango-model-validationsimplifies custom data validation for Django models.Quick NavigationFeaturesInstallationExampleFull UsageLicenseFeaturesSimple validator definitionValidators are automatically executed and errors accumulatedAdds a boolean property for validity checking that can optionally be cached in the database.Numerous helper functions for filtering by validityInstallationRequires Python version 3.9 or higher, Django version 3.2 or higher and pip.pipinstalldjango-model-validationAdd 'django_model_validation' to yourINSTALLED_APPSsetting.INSTALLED_APPS=[...'django_model_validation',]ExampleReplace the Django'sModelbase class byValidatingModeland annotate a validation method with the@validatordecorator.fromdjango.dbimportmodelsfromdjango_model_validation.modelsimportValidatingModel,validatorclassPerson(ValidatingModel):date_of_birth=models.DateField()date_of_death=models.DateField(blank=True,null=True)is_alive=models.BooleanField(default=True)@validatordefvalidate_biographical_data(self):ifself.date_of_deathisnotNoneandself.is_alive:return"A date of death should not be set if the person is alive."Thevalidate_biographical_datafunction is now automatically executed when attempting to save the model from a form.
In case of failure, it raises
aValidationErrorencapsulating the error message.A model may have multiple custom validators. They all get executed, and if any of them raise errors, these, along
with any other validation errors are combined into a singleValidationError.Full UsageWriting validatorsFor convenience, instead of raising aValidationError, custom validators may return any of the following:a string containing an error message (as in the example above)a dictionary that maps field names to lists of strings, orValidationErrorobjectsaValidationErrora list (or any iterable) containing any of the above types, even mixed, ora boolean indicating validation success.Validation is considered successful only if the method does not raise an exception and, either returns nothing or
returnsTrue.Returning iterables is particularly useful, as it allows validators to be written as
agenerator functions, simplifying the code when multiple validations are
performed in a single function.Examples:@validatordefvalidate_biographical_data(self):ifself.date_of_deathisnotNone:ifself.is_alive:yield"A date of death should not be set if the person is alive."ifself.date_of_death<self.date_of_birth:yieldValidationError("Date of death should not be before the date of birth.")ifself.date_of_death>date.today():yield{"date_of_death":"Date of death should not be in the future."}@validatordefvalidate_completeness(self):ifself.date_of_birth>date.today():return{"date_of_death":"If the person is not alive, please provide a date of death."}Triggering validations manuallyCustom validators are triggered during Django's standard validation process (i.e., as part of thefull_cleanmethod).
While this process is automatically invoked by model forms, it is not automatic when manually creating or updating an
object using, e.g.,save(). In this case, it's necessary to callfull_cleanmanually before saving.To manually execute only the custom validators, callrun_custom_validators()instead offull_clean().To manually execute a specific custom validator only, simply execute the decorated method. If the validation fails, the
decorator ensures the method raises aValidationErrorcontaining all returned errors (if any).Additionally, the decorator introduces some convenience functions to the method object:person=Person(...)person.validate_completeness.get_validation_error()# Returns the ValidationError instead of raising it.person.validate_completeness.is_valid()# Runs the validation and returns a bool indicating validation success.Boolean validity checking can be simplified by specifying theproperty_nameoption:@validator(property_name='is_data_complete')defvalidate_completeness(self):ifself.date_of_birth>date.today():return{"date_of_death":"If the person is not alive, please provide a date of death."}This sets up a model property that, when accessed, internally runs theis_validmethod of the validator, allowing easy
access to validity like this:ifperson.is_data_complete:...Enable validity cachingTo enable database caching for the validity property, set thecacheoption toTruein the@validatordecorator.
This creates a hidden boolean field on the model, storing the result of the validity check whenever the model is saved.@validator(cache=True,property_name='is_data_complete')defvalidate_completeness(self):...With this configuration, subsequent access to the property or theis_valid()method won't trigger the validation but
instead return the cached result.Caveats:If attributes are modified but the object is not saved, the validity cache won't update, potentially leading to an
outdated result.When adding a validator withcache=Trueor modifying thecacheoption of an existing one, a database migration is
necessary. This can be created automatically withmakemigrations.The cache field is initially empty. Refer to the "Cache Maintenance" section on how to automatically populate it upon
creation.It is advisable to set thecacheoption together with theproperty_nameoption. When specified, the property name
serves as the field name. If no property name is set, a field name is generated automatically, but accessing the cache
is then only possible using theis_valid()method.Analyse data validityThe validity cache can be used to filter querysets by validity according to specific or all validators. Since the
property name of a cached validator is a model field, it can simply be used to filter querysets. For added convenience,
several utility functions are available through the validator method when it's accessed through the model class.# Obtain filtered querysetsPerson.validate_completeness.get_valid_objects()Person.validate_completeness.get_invalid_objects()# Retrieve a Q object for use in queryset filteringPerson.validate_completeness.get_is_valid_condition()# Check if all objects are validPerson.validate_completeness.is_all_valid()Furthermore, the model class offers additional tools for analysing data validity across all cached validators
simultaneously.# Custom pre-filtered managersPerson.valid_objects.all()Person.invalid_objects.all()# Retrieve a Q object for use in queryset filteringPerson.get_custom_validity_condition()# Checks if all custom validation passes for all objectsPerson.check_custom_validators_globally()Cache maintenanceWhile the library handles caching automatically in most scenarios, here are instances where manual examination and
manipulation of the cache are helpful. The following provides a brief overview of available functions.person=Person(...)# For a specific validatorperson.validate_completeness.update_cache()person.validate_completeness.clear_cache()is_cached=person.validate_completeness.is_cached()cached_result=person.validate_completeness.get_cache()# For all custom validators at onceperson.update_validator_caches()person.clear_validator_caches()person.are_validation_results_cached()# For a specific validator and all objects in the database at oncePerson.validate_completeness.update_cache()Person.validate_completeness.clear_cache()Person.validate_completeness.is_all_cached()Person.validate_completeness.get_is_cached_condition()# For all custom validators and all objects in the database at oncePerson.update_validator_caches_globally()Person.clear_validator_caches_globally()Person.are_validation_results_cached_globally()Person.get_are_validation_results_cached_condition()Additionally, the cache of a specific validator (or all validators) can be updated within a migration. This proves
especially useful for initial population of a cache of a validator that is added to an existing model.To do this, simply append theUpdateModelValidatorCacheto the end of the operations list that adds the validation
cache field, listing the validators for which the cache should be updated.operations = [
    ...,
    UpdateModelValidatorCache('MyModel', MyModel.validate_something, MyModel.validate_something_else)
]Caution: This may call migrations to fail when validator code undergoes changes at a later stage. For instance,
introducing new fields and modifying an existing validator to accommodate them will result in migration failures, as the
validator code cannot be executed on previous model versions. If faced with such issues, consider removing
obsoleteUpdateModelValidatorCacheoperations from old migrations. Alternatively, consider manually populating the
cache usingRunPython.Disable automatic validationTo accept invalid data by default, set theautooption toFalse. This is particularly useful for cached validators
used to track data quality.@validator(auto=False)defvalidate_completeness(self):...Full configurationAll keyword arguments of the@validatordecorator:auto(bool): IfTrue, the validator will automatically run as part of the model'sfull_cleanmethod.Defaults to True.cache(bool): IfTrue, aModelValidatorCacheFieldwill be implicitly added to the model which will be used to
store the validation result.Defaults to False.auto_use_cache(bool): IfTrue, calling theis_valid()method or the validity property will automatically use
the cached validation result if available.Defaults to True.auto_update_cache(bool): IfTrue, the validator will automatically be executed and the cache updated
accordingly when the model is saved using thesave()method.Defaults to True.property_name(str, optional): Custom name for the boolean validity property associated with this validator.
If the cache is enabled, this will be used as the name of the cache field, otherwise a name will be
constructed. If the cache is disabled, this will be used as the name of a property of the model that acts
as a proxy to theis_valid()method of this validator.property_verbose_name(str, optional): If set, this will be used asverbose_name` for the cache field as well
as for generic error messages.Licensedjango-model-validationis distributed under the terms of the MIT License.