Challenge 2: Add Custom Validation — Possible Solution ==================================================================== # catalog/forms.py from django import forms from .models import Author class AuthorForm(forms.ModelForm): class Meta: model = Author fields = ["name", "bio"] def clean_bio(self): bio = self.cleaned_data.get("bio", "") if bio and len(bio) > 500: raise forms.ValidationError( "Bio must be 500 characters or fewer (currently %(count)d)." % {"count": len(bio)} ) return bio # --- Usage in the shell, to demonstrate both outcomes --- >>> from catalog.forms import AuthorForm >>> short_bio_form = AuthorForm(data={"name": "Ann Leckie", "bio": "A sci-fi author."}) >>> short_bio_form.is_valid() True >>> long_bio_form = AuthorForm(data={"name": "Ann Leckie", "bio": "x" * 600}) >>> long_bio_form.is_valid() False >>> long_bio_form.errors {'bio': ['Bio must be 500 characters or fewer (currently 600).']} WHY THIS WORKS -------------- - Django automatically calls any method named clean_() as part of is_valid()'s validation pass — clean_bio() specifically targets the bio field, running AFTER that field's own built-in validation (e.g. TextField's basic type check) has already passed. - self.cleaned_data.get("bio", "") reads the already-validated value for this field — using .get() with a default avoids a KeyError in the edge case where bio failed its own built-in validation before clean_bio() even runs. - raise forms.ValidationError(...) is how a clean_() method reports a failure — Django catches it, attaches the message to that specific field's errors, and is_valid() correctly returns False. - Returning bio at the end (the ONLY path that doesn't raise) is required — a clean_() method must return the cleaned value, since whatever it returns becomes the new cleaned_data value for that field.