Tutorial: Creating a Custom Validator
In this tutorial, you will learn how to create custom validation logic by extending the core validator classes. You will build a pattern-based validator for phone numbers and a comparison-based validator for numeric multiples.
Prerequisites
To follow this tutorial, you should have a Django project environment where you can define models and validators. Ensure you have access to the following modules:
django.core.validatorsdjango.core.exceptionsdjango.utils.deconstructdjango.utils.translation
Step 1: Creating a Pattern-Based Validator
The RegexValidator is the foundation for any validation that relies on regular expressions. While you can use it directly, subclassing it allows you to create reusable, named validators with specific error messages.
Create a PhoneNumberValidator by subclassing RegexValidator in a validators.py file:
import re
from django.core.validators import RegexValidator
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
@deconstructible
class PhoneNumberValidator(RegexValidator):
regex = r'^\+?1?\d{9,15}$'
message = _(
"Enter a valid phone number. This value must be between 9 and 15 digits "
"and may optionally start with '+'."
)
flags = re.ASCII
code = "invalid_phone_number"
How it works:
regex: Defines the pattern the input must match.message: The error message displayed when validation fails. We usegettext_lazyfor internationalization.flags: Optional regex flags. Here we usere.ASCIIto ensure we only match standard digits.@deconstructible: This decorator is required for any validator used in a model field. It allows Django's migration system to serialize the class.
Step 2: Creating a Comparison-Based Validator
For logic that involves comparing a value against a limit (like minimums, maximums, or specific mathematical properties), use BaseValidator.
We will create a MultipleOfValidator that ensures a number is divisible by a specific factor:
from django.core.validators import BaseValidator
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
@deconstructible
class MultipleOfValidator(BaseValidator):
message = _("Ensure this value is a multiple of %(limit_value)s (it is %(show_value)s).")
code = "not_a_multiple"
def compare(self, a, b):
return a % b != 0
How it works:
compare(a, b): You must override this method. It should returnTrueif the validation fails. In this case, ifa % b != 0, the value is not a multiple, so we returnTrueto trigger the error.limit_value: This is the value passed during instantiation (e.g.,MultipleOfValidator(5)).messageplaceholders: You can use%(limit_value)sand%(show_value)sin your message string.BaseValidatorautomatically populates these.
Step 3: Using the clean Method for Complex Data
If you need to transform the input before comparing it (like checking the length of a string), override the clean method. This is how MinLengthValidator is implemented in django/core/validators.py.
@deconstructible
class WordCountValidator(BaseValidator):
message = _("Ensure this text has at least %(limit_value)s words.")
def clean(self, x):
return len(x.split())
def compare(self, a, b):
return a < b
The clean method processes the input x (the text) and returns the value a (the word count) that is then passed to compare.
Step 4: Integrating Validators into Models
Once defined, you can apply these validators to your Django models by passing them in the validators list of a field.
from django.db import models
from .validators import PhoneNumberValidator, MultipleOfValidator
class UserProfile(models.Model):
phone = models.CharField(
max_length=16,
validators=[PhoneNumberValidator()]
)
bonus_points = models.IntegerField(
validators=[MultipleOfValidator(10)]
)
Step 5: Verifying the Result
You can verify your validators by calling them directly. They act as callables that raise a ValidationError if the input is invalid.
from django.core.exceptions import ValidationError
from .validators import MultipleOfValidator
validator = MultipleOfValidator(10)
# This will pass silently
validator(20)
# This will raise a ValidationError
try:
validator(15)
except ValidationError as e:
print(e.message) # "Ensure this value is a multiple of 10 (it is 15)."
Next Steps
- Explore
django/core/validators.pyto see howEmailValidatororURLValidatorhandle more complex regex scenarios. - Learn how to use these same validators in Django Forms by adding them to the
validatorsargument of form fields.