A Django app that simplifies custom data validation for models
Project description
django-model-validation
django-model-validation
simplifies custom data validation for Django models.
Quick Navigation
Features
- Simple validator definition
- Validators are automatically executed and errors accumulated
- Adds a boolean property for validity checking that can optionally be cached in the database.
- Numerous helper functions for filtering by validity
Installation
Requires Python version 3.9 or higher, Django version 3.2 or higher and pip.
pip install django-model-validation
Add 'django_model_validation' to your INSTALLED_APPS
setting.
INSTALLED_APPS = [
...
'django_model_validation',
]
Example
Replace the Django's Model
base class by ValidatingModel
and annotate a validation method with the @validator
decorator.
from django.db import models
from django_model_validation.models import ValidatingModel, validator
class Person(ValidatingModel):
date_of_birth = models.DateField()
date_of_death = models.DateField(blank=True, null=True)
is_alive = models.BooleanField(default=True)
@validator
def validate_biographical_data(self):
if self.date_of_death is not None and self.is_alive:
return "A date of death should not be set if the person is alive."
The validate_biographical_data
function is now automatically executed when attempting to save the model from a form.
In case of failure, it raises
a ValidationError
encapsulating 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 single ValidationError
.
Full Usage
Writing validators
For convenience, instead of raising a ValidationError
, 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, or
ValidationError
objects - a
ValidationError
- a list (or any iterable) containing any of the above types, even mixed, or
- a boolean indicating validation success.
Validation is considered successful only if the method does not raise an exception and, either returns nothing or
returns True
.
Returning iterables is particularly useful, as it allows validators to be written as a generator functions, simplifying the code when multiple validations are performed in a single function.
Examples:
@validator
def validate_biographical_data(self):
if self.date_of_death is not None:
if self.is_alive:
yield "A date of death should not be set if the person is alive."
if self.date_of_death < self.date_of_birth:
yield ValidationError("Date of death should not be before the date of birth.")
if self.date_of_death > date.today():
yield {"date_of_death": "Date of death should not be in the future."}
@validator
def validate_completeness(self):
if self.date_of_birth > date.today():
return {"date_of_death": "If the person is not alive, please provide a date of death."}
Triggering validations manually
Custom validators are triggered during Django's standard validation process (i.e., as part of the full_clean
method).
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 call full_clean
manually before saving.
To manually execute only the custom validators, call run_custom_validators()
instead of full_clean()
.
To manually execute a specific custom validator only, simply execute the decorated method. If the validation fails, the
decorator ensures the method raises a ValidationError
containing 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 the property_name
option:
@validator(property_name='is_data_complete')
def validate_completeness(self):
if self.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 the is_valid
method of the validator, allowing easy
access to validity like this:
if person.is_data_complete:
...
Enable validity caching
To enable database caching for the validity property, set the cache
option to True
in the @validator
decorator.
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')
def validate_completeness(self):
...
With this configuration, subsequent access to the property or the is_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 with
cache=True
or modifying thecache
option 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 the
cache
option together with theproperty_name
option. 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 validity
The 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 querysets
Person.validate_completeness.get_valid_objects()
Person.validate_completeness.get_invalid_objects()
# Retrieve a Q object for use in queryset filtering
Person.validate_completeness.get_is_valid_condition()
# Check if all objects are valid
Person.validate_completeness.is_all_valid()
Furthermore, the model class offers additional tools for analysing data validity across all cached validators simultaneously.
# Custom pre-filtered managers
Person.valid_objects.all()
Person.invalid_objects.all()
# Retrieve a Q object for use in queryset filtering
Person.get_custom_validity_condition()
# Checks if all custom validation passes for all objects
Person.check_custom_validators_globally()
Cache maintenance
While 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 validator
person.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 once
person.update_validator_caches()
person.clear_validator_caches()
person.are_validation_results_cached()
# For a specific validator and all objects in the database at once
Person.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 once
Person.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 the UpdateModelValidatorCache
to 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
obsolete UpdateModelValidatorCache
operations from old migrations. Alternatively, consider manually populating the
cache using RunPython
.
Disable automatic validation
To accept invalid data by default, set the auto
option to False
. This is particularly useful for cached validators
used to track data quality.
@validator(auto=False)
def validate_completeness(self):
...
Full configuration
All keyword arguments of the @validator
decorator:
auto
(bool
): IfTrue
, the validator will automatically run as part of the model'sfull_clean
method. Defaults to True.cache
(bool
): IfTrue
, aModelValidatorCacheField
will 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 as
verbose_name` for the cache field as well as for generic error messages.
License
django-model-validation
is distributed under the terms of the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file django_model_validation-0.1.tar.gz
.
File metadata
- Download URL: django_model_validation-0.1.tar.gz
- Upload date:
- Size: 15.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.7.1 CPython/3.11.7 Linux/6.5.0-5-amd64
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3e86983b86f38f806a376e7c50706faea3d5de99e39f3bb750298f11b27a4c5a |
|
MD5 | 56ae50120d2b1be2e0605c8aa1022d96 |
|
BLAKE2b-256 | f00b095a8b6923830c53e2d2e7cfabda1f54e3312330fb583e93e7de87eb73fc |
File details
Details for the file django_model_validation-0.1-py3-none-any.whl
.
File metadata
- Download URL: django_model_validation-0.1-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.7.1 CPython/3.11.7 Linux/6.5.0-5-amd64
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0730a93b0923139de9e1ab9071f9ad52726cb75f11f2ff1bf707a67c10dcf0fd |
|
MD5 | a98fc3baf254cdd0089e95dc305d3027 |
|
BLAKE2b-256 | b42161d465aad3456d0701248e826ddd4e50e832e093342f205b37e093faeede |