← All posts
Python65 min total · 18 parts
Django Fundamentals: The ORM, Migrations, and Shipping a Real App
Part 18 of 18 · ~2 min
Common Mistakes Worth Remembering
- N+1 queries. The single line that causes it never looks any different whether it's running once or five hundred times, which is exactly what makes this the bug ToolShed shipped without anyone catching it in review.
- A bare
.get()standing in for a lookup that might come back empty.get_object_or_404exists precisely so a missing row becomes a 404 a visitor understands, instead of a stack trace they don't. CASCADEpicked out of habit rather than considered per relationship.Loan.borrowerneededPROTECT, notCASCADE— the difference between a member's account being deletable and their entire loan history disappearing along with it.- A model edited without a matching migration.
migratereporting nothing to do isn't a bug inmigrate; it's a missingmakemigrationsrun. - Logic that belongs on the model creeping into the template instead — an inline overdue check in
tool_card.htmlinstead ofLoan.is_overdue, where it can actually be unit tested. - A form missing
{% csrf_token %}, which renders perfectly and then hands back a 403 with nothing in the error pointing at the actual cause. - Middleware stacked without checking what it needs to already exist below it — the specific failure being
AuthenticationMiddlewarereaching for a sessionSessionMiddlewarehasn't populated yet. - A
post_savesignal treated as guaranteed to fire, when any bulk.update()route to the same change skips it — andauto_now— without warning.
None of these are unique to a website, either. Send ToolShed's due-date reminders from a scheduled script instead of a request, and the exact same shape of bug reappears with nothing web-specific about it at all:
# management/commands/send_overdue_reminders.py
for loan in Loan.objects.filter(status="approved"): # no select_related — an N+1 waiting to happen
email_reminder(loan.borrower.email, loan.tool.name) # one extra query per loan, every single run
Same fix, same reasoning, zero web framework involved in causing it: Loan.objects.select_related("borrower", "tool").filter(status="approved"). If the pattern is recognizable here — no request, no template, no browser anywhere in sight — the model that's actually been built up over these sixteen chapters is a query-shape problem, not a web-page problem that happens to also show up in scripts.
Practice this
Continue learning
- PythonPython Fundamentals for Interviews: Data Structures, Comprehensions, and Gotchas
- Interview & Career PrepThe Non-Technical Half of the Interview: Behavioral Questions, the STAR Method, and What Recruiters Are Actually Scoring
- AI & LLM EngineeringAI & LLM Engineering Fundamentals: Prompting, RAG, Embeddings, and Function Calling