ValidationError
Exception raised when validation fails.
from django.core.exceptions import ValidationError
raise ValidationError('Invalid value')
Constructor
ValidationError(
message,
code=None,
params=None
)
Error message or list of error messages.
Error code for programmatic identification.
Dictionary of parameters for message interpolation.
raise ValidationError(
'Value %(value)s is not valid',
code='invalid',
params={'value': user_input}
)
Built-in Validators
Validators are callables that raise ValidationError if validation fails.
RegexValidator
Validates that input matches a regular expression.
from django.core.validators import RegexValidator
RegexValidator(
regex,
message='Enter a valid value.',
code='invalid',
inverse_match=False,
flags=0
)
Regular expression string or compiled pattern.
If True, validation fails if pattern matches.
Regular expression flags (e.g., re.IGNORECASE).
phone_validator = RegexValidator(
regex=r'^\+?1?\d{9,15}$',
message='Enter a valid phone number'
)
phone = forms.CharField(validators=[phone_validator])
EmailValidator
Validates email addresses.
from django.core.validators import EmailValidator
EmailValidator(
message='Enter a valid email address.',
code='invalid',
allowlist=None
)
List of domains to allow (bypasses normal validation for these domains).
from django.core.validators import validate_email
email = forms.CharField(validators=[validate_email])
URLValidator
Validates URLs.
from django.core.validators import URLValidator
URLValidator(
schemes=['http', 'https', 'ftp', 'ftps'],
message='Enter a valid URL.',
code='invalid'
)
schemes
list
default:"['http', 'https', 'ftp', 'ftps']"
List of allowed URL schemes.
url_validator = URLValidator(schemes=['http', 'https'])
website = forms.CharField(validators=[url_validator])
MaxValueValidator
Validates that value is less than or equal to a maximum.
from django.core.validators import MaxValueValidator
MaxValueValidator(
limit_value,
message='Ensure this value is less than or equal to %(limit_value)s.'
)
age = forms.IntegerField(
validators=[MaxValueValidator(120)]
)
MinValueValidator
Validates that value is greater than or equal to a minimum.
from django.core.validators import MinValueValidator
MinValueValidator(
limit_value,
message='Ensure this value is greater than or equal to %(limit_value)s.'
)
price = forms.DecimalField(
validators=[MinValueValidator(0)]
)
MaxLengthValidator
Validates that string length is at most a maximum.
from django.core.validators import MaxLengthValidator
MaxLengthValidator(
limit_value,
message='Ensure this value has at most %(limit_value)d characters.'
)
MinLengthValidator
Validates that string length is at least a minimum.
from django.core.validators import MinLengthValidator
MinLengthValidator(
limit_value,
message='Ensure this value has at least %(limit_value)d characters.'
)
StepValueValidator
Validates that value is a multiple of a step size.
from django.core.validators import StepValueValidator
StepValueValidator(
limit_value,
message='Ensure this value is a multiple of step size %(limit_value)s.',
offset=None
)
Starting offset for step validation.
# Only accept multiples of 5, starting from 0
quantity = forms.IntegerField(
validators=[StepValueValidator(5)]
)
# Accept multiples of 10, starting from 5 (5, 15, 25, ...)
offset_quantity = forms.IntegerField(
validators=[StepValueValidator(10, offset=5)]
)
DecimalValidator
Validates decimal precision.
from django.core.validators import DecimalValidator
DecimalValidator(
max_digits,
decimal_places
)
Maximum total number of digits.
Maximum number of decimal places.
FileExtensionValidator
Validates file extensions.
from django.core.validators import FileExtensionValidator
FileExtensionValidator(
allowed_extensions,
message='File extension "%(extension)s" is not allowed.',
code='invalid_extension'
)
List of allowed file extensions (without dots).
attachment = forms.FileField(
validators=[
FileExtensionValidator(
allowed_extensions=['pdf', 'doc', 'docx']
)
]
)
IP Address Validators
from django.core.validators import (
validate_ipv4_address,
validate_ipv6_address,
validate_ipv46_address,
)
ip = forms.CharField(validators=[validate_ipv4_address])
Slug Validators
from django.core.validators import validate_slug, validate_unicode_slug
slug = forms.CharField(validators=[validate_slug])
unicode_slug = forms.CharField(validators=[validate_unicode_slug])
ProhibitNullCharactersValidator
Validates that string doesn’t contain null characters.
from django.core.validators import ProhibitNullCharactersValidator
text = forms.CharField(
validators=[ProhibitNullCharactersValidator()]
)
Custom Validators
Create custom validators as functions or classes.
Function Validator
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
'%(value)s is not an even number',
code='not_even',
params={'value': value}
)
even_number = forms.IntegerField(validators=[validate_even])
Class Validator
from django.utils.deconstruct import deconstructible
@deconstructible
class MultipleOfValidator:
message = 'Value must be a multiple of %(multiple)s'
code = 'not_multiple'
def __init__(self, multiple, message=None):
self.multiple = multiple
if message:
self.message = message
def __call__(self, value):
if value % self.multiple != 0:
raise ValidationError(
self.message,
code=self.code,
params={'multiple': self.multiple, 'value': value}
)
def __eq__(self, other):
return (
isinstance(other, self.__class__) and
self.multiple == other.multiple
)
quantity = forms.IntegerField(
validators=[MultipleOfValidator(5)]
)
Field Clean Methods
clean_fieldname()
Define field-specific validation by adding clean_<fieldname>() methods to your form.
class SignupForm(forms.Form):
username = forms.CharField(max_length=30)
email = forms.EmailField()
def clean_username(self):
username = self.cleaned_data['username']
if User.objects.filter(username=username).exists():
raise ValidationError(
'Username "%(username)s" is already taken',
code='duplicate',
params={'username': username}
)
return username
def clean_email(self):
email = self.cleaned_data['email']
if email.endswith('@tempmail.com'):
raise ValidationError(
'Temporary email addresses are not allowed',
code='temp_email'
)
return email
Accessing Other Fields
In clean_<fieldname>(), only the current field and previously cleaned fields are available:
class RegistrationForm(forms.Form):
first_name = forms.CharField()
last_name = forms.CharField()
full_name = forms.CharField(required=False)
def clean_full_name(self):
# first_name and last_name have already been cleaned
full_name = self.cleaned_data.get('full_name')
if not full_name:
first = self.cleaned_data.get('first_name', '')
last = self.cleaned_data.get('last_name', '')
full_name = f"{first} {last}".strip()
return full_name
Use clean() for cross-field validation.
class EventForm(forms.Form):
start_date = forms.DateField()
end_date = forms.DateField()
def clean(self):
cleaned_data = super().clean()
start = cleaned_data.get('start_date')
end = cleaned_data.get('end_date')
if start and end and start > end:
raise ValidationError(
'End date must be after start date',
code='invalid_date_range'
)
return cleaned_data
Adding Field-Specific Errors in clean()
Use add_error() to attach errors to specific fields:
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get('password')
confirm = cleaned_data.get('confirm_password')
if password != confirm:
self.add_error('confirm_password', 'Passwords do not match')
return cleaned_data
Non-Field Errors
Raise errors not tied to a specific field:
def clean(self):
cleaned_data = super().clean()
# Validation logic...
if some_condition:
raise ValidationError(
'General form error not tied to a field',
code='general_error'
)
return cleaned_data
Access in template:
{% if form.non_field_errors %}
<div class="errors">
{{ form.non_field_errors }}
</div>
{% endif %}
Validation Lifecycle
Validation occurs in this order when is_valid() is called:
- Field.to_python() - Convert raw value to Python type
- Field.validate() - Basic field validation
- Field.run_validators() - Run all attached validators
- Form.clean_fieldname() - Field-specific form cleaning
- Form.clean() - Form-wide validation
- Form._post_clean() - Post-validation hook (ModelForm uses this)
# Validation flow example
form = MyForm(request.POST)
if form.is_valid():
# All validation has passed
data = form.cleaned_data
else:
# form.errors contains all validation errors
errors = form.errors
Error Messages
Default Error Messages
Fields have default error messages for common validation failures:
field = forms.CharField()
# default_error_messages = {
# 'required': 'This field is required.',
# }
Custom Error Messages
Override error messages when defining fields:
email = forms.EmailField(
error_messages={
'required': 'Please provide an email address',
'invalid': 'Enter a valid email address',
}
)
Error Message Codes
Common error codes by field type:
CharField:
required - Field is required but empty
max_length - Value exceeds max_length
min_length - Value shorter than min_length
EmailField:
invalid - Invalid email format
IntegerField:
invalid - Not a valid integer
max_value - Exceeds maximum value
min_value - Below minimum value
ChoiceField:
invalid_choice - Not a valid choice
FileField:
missing - No file submitted
empty - File is empty
max_length - Filename too long
Customize error messages in ModelForm Meta:
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content']
error_messages = {
'title': {
'required': 'Article must have a title',
'max_length': 'Title is too long',
},
}
Displaying Errors
Field Errors
<form method="post">
{% csrf_token %}
{% for field in form %}
<div>
{{ field.label_tag }}
{{ field }}
{% if field.errors %}
<div class="errors">
{{ field.errors }}
</div>
{% endif %}
</div>
{% endfor %}
</form>
All Errors
{% if form.errors %}
<div class="form-errors">
{{ form.errors }}
</div>
{% endif %}
Error Dictionary
if not form.is_valid():
for field, errors in form.errors.items():
for error in errors:
print(f"{field}: {error}")
Error Data
Access structured error data:
error_dict = form.errors.as_data()
# Returns dict of field names to lists of ValidationError objects
for field, errors in error_dict.items():
for error in errors:
print(f"Field: {field}")
print(f"Code: {error.code}")
print(f"Message: {error.message}")
JSON Errors
import json
error_json = form.errors.as_json()
data = json.loads(error_json)