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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Marking a validator @deconstructible lets Django serialize it into migrations by reconstructing the instance from its arguments.
- 2Defining __call__ turns a class instance into a callable validator that Django invokes like a plain function.
- 3Normalizing input before matching keeps the regex strict while tolerating formatting the user typed.
Related explainers
typescript
type TokenType = "keyword" | "string" | "comment" | "number" | "text"; interface Token { type: TokenType;
How a regex tokenizer highlights code
tokenizer
regex
lexing
Intermediate
10 steps
java
public final class LogRedactor { private static final Pattern SECRET = Pattern.compile( "(?i)(password|token|api[_-]?key|secret|authorization)\\s*[=:]\\s*\\S+");
Streaming log redaction in Java
regex
streaming-io
try-with-resources
Intermediate
9 steps
go
package handlers import ( "net/http"
Custom validators and binding in Gin
validation
struct-tags
error-handling
Intermediate
8 steps
python
import secrets from fastapi import Depends, FastAPI, HTTPException, Security, status from fastapi.security import APIKeyHeader
API key authentication as a FastAPI dependency
authentication
dependency-injection
api-keys
Intermediate
8 steps
php
class OrderReceiptController extends Controller { public function store(Request $request, Order $order) {
Handling receipt uploads in a Laravel controller
file-upload
validation
authorization
Intermediate
5 steps
javascript
function parseHexColor(hex) { const cleaned = hex.trim().replace(/^#/, ''); const expand = (short) =>
Parsing hex colors into RGBA channels
parsing
bitwise
regex
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-deconstructible-validator-in-django-explained-python-0ad1/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.