Capstone: A Complete, Working Flexible-Routing Site

Website Rebuild with Django

Chapter 12 · Capstone: A Complete, Working Flexible-Routing Site

Sam is the site's own admin — the same real person who worked through Website Rebuild with Next.js's own capstone, now doing the identical job against this Django rebuild instead. Every chapter since Chapter 1 gets used here, in the order a genuine admin session would actually reach them, building the exact five-level-deep chain this whole course was built around from the very first paragraph — no longer a hypothetical example.

Step 1 — Logging In

Sam visits /admin-login/ and signs in with the same password the old PHP site always used — Chapter 9's own bcrypt-compatibility work means nothing had to be reset. This is the first real login since that chapter shipped, and it silently upgrades Sam's own stored hash to Django's preferred PBKDF2 format, exactly as promised.

Step 2 — Building the Real Chain

Through the admin (Chapter 10's own raw_id_fields-improved parent picker), Sam creates five real pages, one level at a time:

programming = Page.objects.create(title="Programming", slug="programming") gpl = Page.objects.create(title="General-Purpose Languages", slug="general-purpose-languages", parent=programming) java = Page.objects.create(title="Java", slug="java", parent=gpl) fundamentals = Page.objects.create(title="Java Fundamentals", slug="fundamentals", parent=java) chapter1 = Page.objects.create(title="Chapter 1: Getting Started", slug="chapter-1", parent=fundamentals) chapter1.full_path # 'programming/general-purpose-languages/java/fundamentals/chapter-1'

No special case anywhere — Chapter 2's own model handles five levels exactly as easily as one.

Step 3 — Visiting It Live

Sam opens /programming/general-purpose-languages/java/fundamentals/chapter-1/ directly. Chapter 3's own <path:full_path> catches the whole thing in one pattern; rstrip('/') normalizes it; one indexed lookup against Chapter 2's own full_path field finds the row — no walk, no five-level chase. Chapter 4's breadcrumb renders all five ancestors correctly, and Chapter 5's dark theme wraps the whole page.

Step 4 — Reorganizing It, and Paying the Real Cascade Cost

Sam decides "General-Purpose Languages" belongs under a new top-level "Languages" page instead of directly under "Programming" — the exact kind of move Chapter 2 deliberately deferred, and Chapter 10 actually built:

languages = Page.objects.create(title="Languages", slug="languages") move_page(gpl, languages) chapter1.refresh_from_db() chapter1.full_path # 'languages/general-purpose-languages/java/fundamentals/chapter-1'

move_page was only ever called with gplchapter1 itself was never directly touched. Its own full_path is still correct anyway, three levels below the page that actually moved, because Chapter 10's own breadth-first cascade walked all the way down through java and fundamentals before ever reaching it. This is the real, working payoff of a cost Chapter 2 named honestly and left open eight chapters ago.

Step 5 — Confirming the Kanji Migration Held Up

Sam visits /japanese/kanji/水/. Chapter 7's allow_unicode=True slug resolves correctly through Chapter 3's own UTF-8-safe path converter — no special-casing needed anywhere in the routing layer. The stroke-order animation renders from the self-hosted static/content/animcjk/ assets Chapter 5's own convention organized, through the exact same {{ page.body|safe }} trust boundary every other page already uses.

Step 6 — A Safe Edit

Sam notices a typo and fixes it through update_page_title — Chapter 8's own mutation, now genuinely protected by Chapter 9's @staff_member_required. A stranger who found this same URL months ago, back when Chapter 8 first shipped it, could have renamed anything; today, they can't.

Step 7 — Shipping It

collectstatic gathers every app's own static files — Chapter 5's stylesheet, Chapter 7's stroke-animation assets — into one STATIC_ROOT. gunicorn runs the real application; nginx serves /static/ directly and proxies everything else. DEBUG=False, ALLOWED_HOSTS set, SECRET_KEY read from the environment — Chapter 11's own checklist, complete.

Chapter Attribution

StepChapter(s) applied
1 — Logging inChapter 9 (bcrypt-compatible auth, automatic hash upgrade)
2 — Building the chainChapter 2 (the self-referencing model), Chapter 10 (the admin interface)
3 — Visiting it liveChapter 3 (routing), Chapter 4 (breadcrumb/templates), Chapter 5 (styling)
4 — ReorganizingChapter 2 (the deferred cost), Chapter 10 (move_page, the real payoff)
5 — Kanji checkChapter 7 (the edge case resolution)
6 — Safe editChapter 8 (the mutation), Chapter 9 (the fix that protects it)
7 — Shipping itChapter 11 (deployment)
What this whole course was really about
Every single step in Sam's session traces back to a specific, real decision made earlier in this course — nothing here is arbitrary or newly introduced just for the capstone. Chapter 2's own read-heavy/write-rare design reasoning shows up directly in Chapter 3's one-lookup routing. Chapter 2's own deferred cascade cost, named honestly rather than quietly ignored, becomes Chapter 10's own working move_page. Chapter 1's own "batteries-included" observation about INSTALLED_APPS becomes Chapter 9's real login. This capstone isn't a new demonstration — it's every earlier chapter's own decision, paying off exactly where it was always going to.
Honest scope note
This capstone deliberately stays within a single-admin, single-server scope. It doesn't cover multi-admin roles or fine-grained permissions beyond one staff flag, revision history or an undo mechanism for content edits, media/image uploads, an automated test suite, internationalization beyond kanji's own allow_unicode slug support, or real search ranking beyond a plain title/path match if one were ever added. Those are each genuinely separate topics, not gaps in what this course could responsibly cover in twelve chapters.

Hands-On Exercises

Exercise 1

Explain why visiting the five-level-deep chapter URL in Step 3 only requires one database lookup, rather than five, tracing the answer back to a specific design decision made in Chapter 2.

📄 View solution
Exercise 2

Explain why chapter1.full_path is already correct after Step 4's move_page(gpl, languages) call, even though chapter1 was never passed into that function directly.

📄 View solution
Exercise 3

Explain why Step 6's page-title edit is described as something "a stranger could have done months ago" but not today. What specifically changed between Chapter 8 and Chapter 9 to make that true?

📄 View solution

Chapter 12 Quick Reference — Course Complete

  • 7 real steps, 11 prior chapters — one realistic admin session, building the exact chain this course opened with, no longer hypothetical
  • This course's own throughline, closed out: every step traces to a real, specific decision made earlier — the deferred cascade cost, the batteries-included auth, the one-lookup routing — nothing arbitrary
  • Honest scope note: single-admin, no revision history, no media uploads, no automated tests, no i18n beyond kanji, no real search ranking
  • Website Rebuild with Django is now complete — 12/12 chapters