Premier League Predictor: Django & MySQL — Chapter 3, Exercise 2 ==================================================== TASK Explain, precisely, what max_num = 20 alone actually controls on SeasonTeamInline, and what validate_max = True adds that max_num by itself does not provide. SOLUTION max_num = 20, on its own, only controls the display side of the inline formset: how many total forms (existing rows plus blank rows for new additions) the admin page will render and offer for filling in. Once a Season already has 20 real SeasonTeam rows, max_num = 20 means no further blank row is shown to invite a 21st addition — from the admin's own screen, it looks fully capped. That display-only behavior is genuinely different from validation. Django's own formset machinery, by default, does not reject a submission just because it happens to contain more forms than max_num — that non-enforcing default is explicitly a long-standing Django behavior, not a bug. In practice, that means a 21st row could still be saved through means the admin's own rendered page wouldn't normally offer — a manually crafted request, a race condition between two people, or any path that bypasses the normal "no blank row shown" limit — with max_num alone doing nothing to stop it. validate_max = True adds the missing piece: it tells the underlying formset to actually validate the submitted form count against max_num and reject the submission with a real validation error if it exceeds 20, regardless of how the extra row got there. Only with validate_max = True does "20 teams maximum" become a genuinely enforced rule rather than a display convenience that happens to match the real limit in the common case. WHY THIS WORKS AS AN ANSWER ---------------------------- It precisely separates what max_num actually does (control displayed blank forms) from what it doesn't do (enforce a hard limit on save), and states exactly what validate_max = True adds — real validation against the submitted form count, not just the initially rendered one.