Skip to main content

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.validators
  • django.core.exceptions
  • django.utils.deconstruct
  • django.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 use gettext_lazy for internationalization.
  • flags: Optional regex flags. Here we use re.ASCII to 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 return True if the validation fails. In this case, if a % b != 0, the value is not a multiple, so we return True to trigger the error.
  • limit_value: This is the value passed during instantiation (e.g., MultipleOfValidator(5)).
  • message placeholders: You can use %(limit_value)s and %(show_value)s in your message string. BaseValidator automatically 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.py to see how EmailValidator or URLValidator handle more complex regex scenarios.
  • Learn how to use these same validators in Django Forms by adding them to the validators argument of form fields.