URL Dispatch: Django's Own Mechanism for Arbitrary-Depth Routing

Website Rebuild with Django

Chapter 3 · URL Dispatch: Django's Own Mechanism for Arbitrary-Depth Routing

Website Rebuild with Next.js solved arbitrary-depth routing with a special filename: a folder literally named [...slug], whose presence alone tells Next.js "match anything here, and hand me the segments as an array." Django has no filename convention that does anything at all — every route is a line of ordinary Python, read top to bottom, matched in the exact order it's declared. Both frameworks reach the same end result; the actual mechanism underneath is genuinely different, and that difference is this chapter's whole subject.

Path Converters: What Actually Makes a Catch-All Possible

Django's path() function matches URL segments using named convertersstr, int, slug, uuid. Every one of those stops at the first / it encounters. Only one converter doesn't: path, which matches literally anything, forward slashes included — the single piece of syntax this whole chapter is built around.

# osztromok_site/urls.py from django.contrib import admin from django.urls import path from content import views urlpatterns = [ path('admin/', admin.site.urls), path('', views.homepage, name='homepage'), path('<path:full_path>/', views.page_detail, name='page_detail'), ]

<path:full_path> means "match anything, including slashes, and pass it to the view as a keyword argument named full_path" — the direct equivalent of Next.js's own [...slug] array param, just delivered as a single string instead of an array of segments.

The central fact this chapter is built on
Next.js's own routing table is implicit — the folder structure on disk is the routing table, discovered by convention. Django's is expliciturlpatterns is an ordinary Python list, read and matched top to bottom, that you write and can see in full in one file. Neither approach is objectively better; they're two genuinely different philosophies (convention-over-configuration vs. explicit-is-better-than-implicit) that this whole course keeps surfacing, starting with Chapter 1's own project/app split.

The View: One Lookup, Not a Walk

# content/views.py from django.shortcuts import render, get_object_or_404 from .models import Page def page_detail(request, full_path): full_path = full_path.rstrip('/') page = get_object_or_404(Page, full_path=full_path) return render(request, 'content/page_detail.html', {'page': page})

This is exactly the payoff Chapter 2's own full_path field was built for: full_path arrives from the URL already in the identical shape it was stored in, so resolving any URL — no matter how deep — is one indexed database lookup, never a walk back up through parent.

Three Real Edge Cases

Pattern order matters — the catch-all has to come last
Django matches urlpatterns in declaration order and stops at the first match. If path('<path:full_path>/', ...) were listed before path('admin/', ...), a request for /admin/ would never reach the real admin route at all — full_path would greedily match "admin" first, and Django would call page_detail looking for a Page that was never meant to exist. The catch-all pattern has to be the very last entry in the list, always.
The homepage needs its own explicit route
<path:full_path> requires at least one real character — it doesn't match an empty string, so the bare root URL / would 404 with only the catch-all pattern in place. path('', views.homepage, name='homepage') exists specifically to handle that one case the catch-all structurally can't.
A trailing-slash mismatch is a genuine, easy way to break every lookup
Django's own convention appends a trailing slash to normalized URLs, so a request typically arrives as /programming/python/ — but Chapter 2's save() method built full_path with no trailing slash ("programming/python"). Querying with the raw, unstripped full_path parameter would never match a single real row, for every single page, silently. full_path.rstrip('/') exists purely to close that exact gap before the query ever runs.

Django vs. Next.js: The Same Job, Two Mechanisms

Next.jsDjango
How the catch-all is declaredA folder named [...slug] — a filename conventionA path() call using the path converter — an explicit line of code
What the handler receivesAn array of individual URL segmentsA single string, the full remaining path
Where routing logic livesDiscovered from the folder structure on diskDeclared explicitly in urlpatterns, read top to bottom

Hands-On Exercises

Exercise 1

Explain why Next.js's [...slug] file-based catch-all and Django's path('<path:full_path>/', ...) achieve the identical end result through genuinely different mechanisms. Name the actual mechanism each framework relies on.

📄 View solution
Exercise 2

Explain why the catch-all pattern must be listed last in urlpatterns, using a concrete example of what would actually break if it were listed before the admin/ route instead.

📄 View solution
Exercise 3

Explain why full_path.rstrip('/') is necessary inside page_detail, referencing exactly how full_path was built back in Chapter 2.

📄 View solution

Chapter 3 Quick Reference

  • Path convertersstr/int/slug/uuid all stop at a /; only path matches through it
  • <path:full_path> — Django's own catch-all syntax; delivers one string, unlike Next.js's array of segments
  • Explicit vs. implicit routing — Django's urlpatterns is ordinary Python code; Next.js's routing table is the folder structure itself
  • Pattern order matters — the catch-all must be the last entry, or it swallows requests meant for earlier routes
  • The homepage needs its own explicit path('', ...) — the catch-all can't match an empty path
  • full_path.rstrip('/') — reconciles Django's own trailing-slash convention against Chapter 2's own no-trailing-slash full_path storage
  • Next chapter: Views & Templates: Django's MVT Model