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
ValidationErrorobjects - 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=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 the
cacheoption 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 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_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.
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
|