Getting Data In Correctly
Last updated:
Three layers, three purposes
- Form validation — user-facing, with helpful messages
- Model validation — applies to every path, including imports and scripts
- Database constraints — the last line, unbypassable
Business rules enforced only in a form are enforced only for people using that form. A management command, an import or an API bypasses them entirely.
Where to put a rule
| Rule type | Belongs in |
|---|---|
| Field format and required | Form and model |
| Cross-field business logic | Model |
| Uniqueness | Database constraint |
| Referential integrity | Foreign key |
| Complex workflow rules | A service layer, called by both |
Error messages people can act on
- Say what is wrong and what would be right
- Attach the error to the field it concerns
- Use the business's language, not technical terms
- Never expose internal detail in a user-facing message
- Preserve what the user entered, so nothing is retyped
A form that clears itself on validation failure is a form people abandon, particularly on a phone.
Long forms need structure
Where a form genuinely needs many fields, split it into steps, ask the easy questions first, and show progress. Completion rates improve substantially against the same fields on one page.
Capture contact details early, so a partial completion is still something you can follow up.
Test the validation, not just the happy path
The interesting tests are the ones that submit invalid data and assert the right error appears. Testing only successful submission tests very little.
Include the boundary cases: empty, maximum length, wrong type, and the specific combinations your business rules prohibit.
Frequently asked questions
Should validation be duplicated?
Where do complex business rules go?
What about client-side validation?
How do we handle validation across models?
Business rules enforced in one form only?
That means imports and scripts bypass them. Worth moving them where they apply everywhere.
Related services
What we build for problems like this one