Rendering Content & the Kanji Edge Case

Website Rebuild with Django

Chapter 7 · Rendering Content & the Kanji Edge Case

Every kanji page on the live site today is a real, deliberate exception: a self-contained HTML file with its own self-hosted stroke-order animation, living outside the site's normal subject/subtopic/page hierarchy entirely, dual-written into two separate folders rather than driven by a database row. Website Rebuild with Next.js 7 resolved this same edge case for its own architecture; this chapter reaches the identical resolution independently, for the same underlying reason — and finds one genuinely new, Django-specific wrinkle along the way that Next.js never had to solve.

Why Kanji Was Kept Separate, Originally

Two real constraints, not an arbitrary decision:

  • Fixed depth. The site's existing three-level subject/subtopic/page model had no natural slot for "a flat grid of individual characters" — kanji didn't fit the shape every other course/lesson already used.
  • No self-contained-fragment convention. A kanji page isn't a bare content fragment meant to drop into a shared template — it's a complete, standalone document with its own inline stroke-animation markup and CSS, genuinely different in shape from an ordinary lesson page.

Neither Constraint Survives This Rebuild

Both original reasons are already resolved by earlier chapters
Chapter 2's own Page model supports genuinely arbitrary depth — japanese/kanji/水 fits exactly as naturally as any five-level course path, no special case required. Chapter 4's own {{ page.body|safe }} already renders real, admin-authored HTML directly — a kanji page's stroke-animation markup and inline CSS can live in body and render through the identical mechanism every other page already uses. The two constraints that justified keeping kanji separate in the first place were both artifacts of the old three-level, fragment-only model — neither one is a property of this rebuild's own architecture at all.

The Resolution: One Page Row Per Kanji Character

kanji_parent = Page.objects.get(full_path="japanese/kanji") Page.objects.create( title="水 (mizu) — water", slug="水", parent=kanji_parent, body="<!-- stroke-animation markup and inline CSS, same as the current live page -->", ) # full_path becomes: japanese/kanji/水

A New Wrinkle Django-Specific: Non-ASCII Characters in a Slug

SlugField's default validator rejects the kanji character itself
Chapter 2's original slug = models.SlugField(max_length=100) only accepts ASCII letters, numbers, hyphens, and underscores by default — a literal "水" fails that validation outright, immediately, before the row would ever save. This is a genuinely new problem, one Next.js's own file-based routing never had to confront the same way.
# content/models.py — Chapter 2's slug field, revisited slug = models.SlugField(max_length=100, allow_unicode=True)

allow_unicode=True is Django's own built-in answer to exactly this scenario — real, non-ASCII characters become valid slug content, not just tolerated as an escape hatch. The alternative (romanizing to something like "sui" and keeping the real character only in title) was considered and rejected here: it would be less faithful to the source material, and Chapter 3's own path converter already handles UTF-8 URL segments correctly with zero changes needed — nothing about routing has to adapt to make this work.

Stroke-Animation Assets: Still Self-Hosted, Now Under Chapter 5's Own Convention

content/ └── static/ └── content/ └── animcjk/ # the stroke-order library's own CSS/font/data files ├── animcjk.css └── ...

The animation library's own asset files move into the exact same app-namespaced static folder Chapter 5 already established — staticfiles serves them locally, the same as always. The one non-negotiable property of the original kanji pages carries forward unchanged: these assets are genuinely self-hosted, never loaded from a third-party CDN, so a kanji page's stroke animation never depends on any outside service staying online.

The |safe trust boundary isn't reopened, just reused
Rendering a kanji page's own <style>/animation markup through {{ page.body|safe }} relies on the exact same reasoning Chapter 4 already established: this is safe specifically because kanji content, like every other page's body, is admin-authored and migrated deliberately — never submitted by an anonymous visitor. Nothing new is being trusted here; it's the same trust boundary, applied to one more kind of content.

Hands-On Exercises

Exercise 1

Name the two original reasons kanji pages were kept separate from the rest of the site's content model, and explain why this chapter concludes neither one still applies in this rebuild specifically.

📄 View solution
Exercise 2

Explain why Chapter 2's original slug = models.SlugField(max_length=100) needed to change specifically to support kanji pages, and what allow_unicode=True actually does.

📄 View solution
Exercise 3

Explain why moving the stroke-animation CSS/font files into content/static/content/animcjk/ still satisfies the original "self-hosted, not CDN-dependent" requirement the kanji pages were built around.

📄 View solution

Chapter 7 Quick Reference

  • Two original constraints: a fixed three-level hierarchy with no natural slot for kanji, and a fragment-only rendering model kanji pages never fit
  • Both resolved already — Chapter 2's arbitrary-depth Page model and Chapter 4's {{ page.body|safe }} rendering remove both constraints entirely
  • The resolution: one Page row per kanji character, e.g. japanese/kanji/水
  • A genuinely new wrinkle: SlugField's default validator is ASCII-only; kanji slugs need allow_unicode=True
  • Stroke-animation assets — moved into static/content/animcjk/, still fully self-hosted, never CDN-dependent
  • The |safe trust boundary isn't new — the same reasoning from Chapter 4, applied to one more kind of admin-authored content
  • Next chapter: Dynamic Content & Forms