The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Django validates a submitted form when you call form.is_valid() (or access form.errors). That call runs field cleaning, converts accepted values to Python types, applies validators, executes cross-field checks in clean(), and— for a ModelForm—runs the relevant model validation. Read normalized values from cleaned_data only after validation succeeds.
The complete Django validation workflow
A form must be bound to input before it can validate. In a view, pass request.POST and, for uploads, request.FILES to the form. An unbound form such as ProfileForm() only renders fields; it has no submitted values to check.
- Construct the form with submitted data:
form = SignupForm(request.POST). - Call
form.is_valid(). This runs the cleaning pipeline and returns a Boolean. - If it returns
True, use normalized values inform.cleaned_data. - If it returns
False, render the bound form again. Django exposes field errors throughform.errorsand non-field errors throughform.non_field_errors().
Accessing form.errors also triggers validation. Do not read cleaned_data as if every key were present when the form is invalid: fields that fail cleaning are omitted, while valid fields remain available.
A minimal view
from django.shortcuts import render, redirect
from .forms import ContactForm
def contact(request):
if request.method == "POST":
form = ContactForm(request.POST, request.FILES)
if form.is_valid():
# cleaned_data contains Python values, not raw strings
send_message(form.cleaned_data)
return redirect("contact_done")
else:
form = ContactForm()
return render(request, "contact.html", {"form": form})
For a file field, omitting request.FILES means the upload cannot be validated or saved correctly. Keep the request method check so a GET displays an unbound form and a POST preserves entered values and errors.
#1 Best Overall
Field cleaning: required checks, conversion and validators
Every Django Field has a clean(value) method. It either returns a cleaned value or raises django.core.exceptions.ValidationError. Required fields reject None and empty input by default. Set required=False when empty input is valid.
Cleaning also normalizes values. For example, a valid DateField becomes a Python datetime.date, and an integer field becomes an integer. This is why business logic should use cleaned_data instead of parsing request.POST manually.
Reusable validators
Use a validator when the same rule can be attached to several fields or reused outside one form.
Free tools Windows power users keep installed
One-click scans. No signup required.
from django import forms
from django.core.validators import RegexValidator
username_validator = RegexValidator(
regex=r"^[a-z0-9_]+$",
message="Use lowercase letters, numbers and underscores only.",
)
class ProfileForm(forms.Form):
username = forms.CharField(
max_length=30,
validators=[username_validator],
)
birth_date = forms.DateField(required=False)
Validators should raise ValidationError, not return an error string. Keep field-specific rules close to the field declaration when possible.
The clean_<fieldname>() hook
Override clean_email(), clean_amount(), or another field hook when validation belongs to one field but needs form state or custom normalization.
Rank #2
from django import forms
from django.core.exceptions import ValidationError
class AccountForm(forms.Form):
email = forms.EmailField()
def clean_email(self):
email = self.cleaned_data["email"].strip().lower()
if email.endswith("@example.invalid"):
raise ValidationError("That email domain is not accepted.")
return email
Return the value you want stored in cleaned_data. If you raise an error, Django attaches it to that field.
Cross-field rules with clean()
Override the form’s clean() method for relationships involving multiple fields: matching passwords, date ranges, or fields that become mandatory only under a particular status. Field cleaning has already run, so inspect self.cleaned_data and, when useful, self.errors.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
from django import forms
from django.core.exceptions import ValidationError
class RegistrationForm(forms.Form):
password = forms.CharField(widget=forms.PasswordInput)
password_again = forms.CharField(widget=forms.PasswordInput)
start_date = forms.DateField()
end_date = forms.DateField(required=False)
def clean(self):
cleaned = super().clean()
password = cleaned.get("password")
password_again = cleaned.get("password_again")
start = cleaned.get("start_date")
end = cleaned.get("end_date")
if password and password_again and password != password_again:
self.add_error("password_again", "Passwords do not match.")
if start and end and end < start:
raise ValidationError("End date must be on or after start date.")
return cleaned
An error raised directly from clean() is a non-field error because it concerns the form as a whole. Use self.add_error("field", message) when the message should appear beside a particular field. Always return the cleaned dictionary returned by super().clean() (or your modified version).
ModelForm validation and uniqueness
A ModelForm combines form and model validation. Calling is_valid(), reading errors, or calling full_clean() starts the process. Django first runs the form’s field cleaning and clean(), then validates the model instance for fields represented on the form.
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ["title", "body", "publish_at"]
def clean(self):
cleaned = super().clean()
publish_at = cleaned.get("publish_at")
body = cleaned.get("body")
if publish_at and not body:
self.add_error("body", "A scheduled article needs body text.")
return cleaned
Include only fields users are allowed to edit. Omitted model fields are excluded from the ModelForm’s user-facing validation so a user can correct errors on fields present in the form.
Call super().clean() when overriding ModelForm.clean() if you want Django’s uniqueness checks for unique, unique_together, and unique_for_date, unique_for_month, or unique_for_year to remain enabled. Skipping the parent implementation can remove those checks.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Model validation, full_clean(), and save()
Model.full_clean() performs four stages in order:
clean_fields()validates individual model fields.clean()applies model-wide rules.validate_unique()checks uniqueness.validate_constraints()checks model constraints.
save() does not call full_clean() automatically. If application code creates a model instance directly and must handle validation errors before writing, call it explicitly:
from django.core.exceptions import ValidationError
article = Article(title="", body="Draft")
try:
article.full_clean()
except ValidationError as exc:
# exc.message_dict maps fields to lists of messages
handle_validation_errors(exc.message_dict)
else:
article.save()
Use full_clean() when you own the validation boundary—for example, service code importing data, background jobs, or instances assembled outside a ModelForm. A ModelForm already applies model checks for its included fields. Validation is not a substitute for database transactions or unique constraints: concurrent writes can still race, so enforce critical invariants at the database layer and handle the resulting integrity error.
Where each rule belongs
| Rule type | Best location | Error location |
|---|---|---|
| Required input, type conversion, one reusable predicate | Field options or validators=[...] |
That field |
| One field with custom normalization or context | clean_<fieldname>() |
That field |
| Relationship between form fields | Form clean() |
Non-field error or add_error() target |
| Rules that must hold for model instances everywhere | Model clean(), model fields, constraints |
Model ValidationError |
| Uniqueness and database integrity | Model validation plus database constraint | Form field/non-field error or database exception |
Common mistakes and fixes
Reading raw POST data
Symptom: dates, numbers, or booleans behave like strings. Fix: call is_valid() and use cleaned_data; field cleaning performs conversion.
Using cleaned_data before validation
Symptom: missing keys or stale values. Fix: access it only inside the successful branch, and use .get() inside cleaning methods because earlier field errors can remove keys.
Recommended Free Tools
Putting a cross-field rule in one field validator
Symptom: a validator cannot reliably see the other input. Fix: move the relationship check to clean().
Forgetting super().clean() in a ModelForm
Symptom: expected uniqueness errors disappear. Fix: call the parent method and modify the returned dictionary.
Expecting save() to validate
Symptom: a manually built instance reaches the database with invalid values. Fix: call instance.full_clean() before save(), while retaining database constraints for concurrency safety.
Missing uploaded files
Symptom: a file field is empty despite a multipart form. Fix: bind both request.POST and request.FILES, and use enctype="multipart/form-data" in HTML.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallTesting validation behavior
Test valid normalization, each invalid branch, error placement, and model uniqueness. Assert both the Boolean result and the resulting data or message.
Best Value
from django.test import TestCase
from .forms import RegistrationForm
class RegistrationFormTests(TestCase):
def test_dates_are_normalized(self):
form = RegistrationForm(data={
"password": "secret",
"password_again": "secret",
"start_date": "2026-09-29",
})
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data["start_date"].isoformat(), "2026-09-29")
def test_password_mismatch_is_field_error(self):
form = RegistrationForm(data={
"password": "one",
"password_again": "two",
"start_date": "2026-09-29",
})
self.assertFalse(form.is_valid())
self.assertIn("password_again", form.errors)
Pin expectations to the Django version used by your project. Validation details and available constraint behavior can vary between supported releases, so verify against that version’s documentation and test suite.
Performance, reliability and cost considerations
Form validation is normally inexpensive, but ModelForms can issue database queries for uniqueness checks. Avoid calling is_valid() repeatedly in one request; validation is cached, but a single explicit call makes control flow clearer. Keep expensive external checks out of field cleaning when possible, or cache them and provide a timeout and failure path. Validation should produce actionable errors, never expose secrets or internal exception details.
For bulk imports, validate rows in batches, report row-level errors, and wrap writes in transactions where partial updates would be harmful. Database constraints remain the final protection against race conditions even when every form passes validation.
Or skip the browser setup
If your Django project also needs reliable screenshots of rendered forms, ScreenshotNeo provides a single HTTP request instead of maintaining a browser automation stack. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
One-call example (see the ScreenshotNeo API documentation):
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Frequently Asked Questions
Does calling form.is_valid() save anything to the database?
No. It only runs validation and populates errors and cleaned data. A separate save() call is required.
Can I validate a form without a request object?
Yes. Pass a dictionary with Form(data=...) in application code or tests; use request.POST and request.FILES in a view.
Should validation be duplicated in JavaScript?
Client-side checks can improve feedback, but repeat every security and business rule on the server because browsers can bypass JavaScript.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

