python 46 lines · 7 steps

Building a deconstructible validator in Django

A reusable, migration-safe validator class checks phone numbers against the E.164 format and attaches to a model field.

Explained by highlit
1import re
2 
3from django.core.exceptions import ValidationError
4from django.core.validators import RegexValidator
5from django.db import models
6from django.utils.deconstruct import deconstructible
7from django.utils.translation import gettext_lazy as _
8 
9 
10@deconstructible
11class E164Validator:
12 message = _("Enter a valid phone number in E.164 format, e.g. +14155552671.")
13 code = "invalid_phone"
14 pattern = re.compile(r"^\+[1-9]\d{7,14}$")
15 
16 def __init__(self, message=None, code=None):
17 if message is not None:
18 self.message = message
19 if code is not None:
20 self.code = code
21 
22 def __call__(self, value):
23 normalized = re.sub(r"[\s\-().]", "", str(value))
24 if not self.pattern.match(normalized):
25 raise ValidationError(self.message, code=self.code, params={"value": value})
26 
27 def __eq__(self, other):
28 return (
29 isinstance(other, E164Validator)
30 and self.message == other.message
31 and self.code == other.code
32 )
33 
34 
35class Contact(models.Model):
36 name = models.CharField(max_length=120)
37 phone = models.CharField(
38 _("phone number"),
39 max_length=16,
40 validators=[E164Validator()],
41 help_text=_("International format, e.g. +14155552671."),
42 )
43 
44 def clean(self):
45 super().clean()
46 self.phone = re.sub(r"[\s\-().]", "", self.phone)
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Marking a validator @deconstructible lets Django serialize it into migrations by reconstructing the instance from its arguments.
  2. 2Defining __call__ turns a class instance into a callable validator that Django invokes like a plain function.
  3. 3Normalizing input before matching keeps the regex strict while tolerating formatting the user typed.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a deconstructible validator in Django — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code