python
46 lines · 7 steps
Multi-field validation in a Django ModelForm
A ReservationForm layers field-level and cross-field checks to reject bad bookings before they hit the database.
Explained by
highlit
1from django import forms
2from django.utils import timezone
3
4from .models import Reservation
5
6
7class ReservationForm(forms.ModelForm):
8 class Meta:
9 model = Reservation
10 fields = ["room", "check_in", "check_out", "guests", "promo_code"]
11
12 def clean_check_in(self):
13 check_in = self.cleaned_data["check_in"]
14 if check_in < timezone.localdate():
15 raise forms.ValidationError("Check-in cannot be in the past.")
16 return check_in
17
18 def clean(self):
19 cleaned_data = super().clean()
20 check_in = cleaned_data.get("check_in")
21 check_out = cleaned_data.get("check_out")
22 room = cleaned_data.get("room")
23 guests = cleaned_data.get("guests")
24
25 if check_in and check_out:
26 if check_out <= check_in:
27 self.add_error("check_out", "Check-out must be after check-in.")
28 elif (check_out - check_in).days > 30:
29 raise forms.ValidationError("Stays longer than 30 nights require a call to the front desk.")
30
31 if room and guests and guests > room.max_occupancy:
32 self.add_error(
33 "guests",
34 f"{room} sleeps at most {room.max_occupancy} guests.",
35 )
36
37 if room and check_in and check_out:
38 overlapping = Reservation.objects.filter(
39 room=room,
40 check_in__lt=check_out,
41 check_out__gt=check_in,
42 ).exclude(pk=self.instance.pk)
43 if overlapping.exists():
44 raise forms.ValidationError("This room is already booked for the selected dates.")
45
46 return cleaned_data
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Field-specific rules belong in clean_<field> methods, while rules spanning several fields belong in clean().
- 2Use add_error to attach a message to a specific field and ValidationError for form-wide errors.
- 3Excluding self.instance.pk keeps an overlap check from flagging the record against itself when editing.
Related explainers
python
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status from pydantic import BaseModel, EmailStr from sqlalchemy.orm import Session
Building a signup endpoint in FastAPI
dependency-injection
request-validation
background-tasks
Intermediate
8 steps
python
from django import template from django.urls import reverse, NoReverseMatch from django.utils.html import format_html
Active nav-link template tags in Django
template tags
url routing
active state
Intermediate
7 steps
python
from functools import wraps import asyncio from fastapi import APIRouter, FastAPI, Request
Per-route request timeouts in FastAPI
decorators
async
timeouts
Intermediate
6 steps
python
import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent.parent
How a Django settings module is wired
configuration
environment-variables
middleware
Intermediate
8 steps
python
def is_valid_card_number(number: str) -> bool: digits = [int(c) for c in number if c.isdigit()] if len(digits) < 13 or len(digits) > 19:
Validating card numbers with the Luhn check
checksum
validation
luhn-algorithm
Intermediate
6 steps
python
from django.db.models import Q from .models import Order
DISTINCT ON queries in Django
orm
querysets
postgresql
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/multi-field-validation-in-a-django-modelform-explained-python-86ac/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.