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

Walkthrough

Space play step click any line
Three takeaways
  1. 1Field-specific rules belong in clean_<field> methods, while rules spanning several fields belong in clean().
  2. 2Use add_error to attach a message to a specific field and ValidationError for form-wide errors.
  3. 3Excluding self.instance.pk keeps an overlap check from flagging the record against itself when editing.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Multi-field validation in a Django ModelForm — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code