Implementing Custom Validators
To implement custom validation logic in this project, you can use simple functions for one-off checks or subclass Django's validator classes to create reusable, configurable validation components.
Creating a Function-Based Validator
For simple logic that doesn't require configuration, define a function that takes a value and raises a ValidationError if the value is invalid.
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
# Usage in a Model or Form field
# even_field = models.IntegerField(validators=[validate_even])
Subclassing RegexValidator for Pattern Matching
When you need to validate strings against a specific pattern, subclass RegexValidator. This is the approach used for system-level validators like ASCIIUsernameValidator in django/contrib/auth/validators.py.
import re
from django.core import validators
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
@deconstructible
class ASCIIUsernameValidator(validators.RegexValidator):
regex = r"^[\w.@+-]+\Z"
message = _(
"Enter a valid username. This value may contain only unaccented lowercase a-z "
"and uppercase A-Z letters, numbers, and @/./+/-/_ characters."
)
flags = re.ASCII
Key Components:
regex: The regular expression pattern to match.message: The error message shown when validation fails.flags: Standardremodule flags (e.g.,re.IGNORECASE). Note that if flags are used,regexmust be a string.@deconstructible: This decorator is required for class-based validators used in Model fields to ensure they can be serialized for migrations.
Subclassing BaseValidator for Comparisons
For validators that compare a value against a limit (like MaxValueValidator or MinLengthValidator), subclass BaseValidator. You must implement the compare method and optionally the clean method.
from django.core.validators import BaseValidator
from django.utils.translation import gettext_lazy as _
class MaxValueValidator(BaseValidator):
message = _("Ensure this value is less than or equal to %(limit_value)s.")
code = "max_value"
def compare(self, a, b):
# Return True if the validation SHOULD FAIL
return a > b
class MaxLengthValidator(BaseValidator):
# ... message and code definitions ...
def compare(self, a, b):
return a > b
def clean(self, x):
# Pre-process the value before comparison (e.g., get length)
return len(x)
How BaseValidator Works:
clean(value): Transforms the input (e.g.,MaxLengthValidatorconverts a string to its length).limit_value: The threshold passed during initialization. Iflimit_valueis a callable, it is executed at validation time.compare(cleaned_value, limit_value): If this returnsTrue, aValidationErroris raised using the providedmessageandcode.
Using Specialized Built-in Validators
The codebase provides several specialized validators that can be used directly or customized via arguments:
File Extension Validation
Use FileExtensionValidator to restrict uploaded files to specific types.
from django.core.validators import FileExtensionValidator
# Usage in a model field
# document = models.FileField(validators=[FileExtensionValidator(allowed_extensions=['pdf', 'docx'])])
Step Value Validation
Use StepValueValidator to ensure numeric values follow a specific increment, optionally starting from an offset.
from django.core.validators import StepValueValidator
# Ensures value is a multiple of 5 (5, 10, 15...)
validator = StepValueValidator(limit_value=5)
# Ensures value is a multiple of 5 starting from 2 (2, 7, 12...)
validator_with_offset = StepValueValidator(limit_value=5, offset=2)
Decimal Validation
DecimalValidator allows fine-grained control over decimal precision.
from django.core.validators import DecimalValidator
# Validates up to 5 digits total, with 2 decimal places
validator = DecimalValidator(max_digits=5, decimal_places=2)
Troubleshooting and Best Practices
Migration Errors
If you use a custom class-based validator in a models.py file, you must decorate the class with @deconstructible. Without this, Django's migration framework will not be able to serialize the validator into a migration file, leading to ValueError: Cannot serialize errors.
Regex Performance
When using RegexValidator, the regex is compiled using _lazy_re_compile. If you provide a pre-compiled regex object to the constructor, ensure you do not also pass flags, as this will raise a TypeError.
Floating Point Precision
When implementing comparison logic for numbers, be aware that StepValueValidator uses math.isclose with a tolerance of 1e-9 to handle floating point inaccuracies. Follow this pattern for custom numeric validators to avoid unexpected failures with floats.
import math
# Example comparison from StepValueValidator
return not math.isclose(math.remainder(a - offset, b), 0, abs_tol=1e-9)