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 FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
python
import random from typing import Iterator, List
How reservoir sampling picks k items
reservoir-sampling
streaming
randomness
Intermediate
5 steps
python
import secrets from django.contrib.auth import authenticate, login from django.core.cache import cache
Two-factor login with OTP in Django
two-factor-auth
one-time-passwords
caching
Intermediate
9 steps
python
import re from functools import total_ordering from typing import Optional
Parsing and comparing semantic versions
regex
operator-overloading
sorting
Intermediate
7 steps
python
from typing import Any, Sequence, Mapping def render_markdown_table(
Rendering an aligned Markdown table in Python
string formatting
data transformation
closures
Intermediate
8 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.