URLs and Routing

Introduction

Django maps incoming URL paths to Python callables through URLconf modules—Python lists of path() (or re_path()) entries. This chapter builds on the blog/urls.py wiring from chapter 03: path converters, include(), named URLs, reverse(), and RESTful routing conventions that reappear in class-based views and Django REST Framework.

Prerequisites

URLconf Basics

Root vs app URLconfs

config/urls.py (project root):

python
from django.contrib import admin
from django.urls import include, path
 
urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("blog.urls")),
    path("blog/", include("blog.urls")),  # optional prefix — pick one pattern
]

Tip

One Mount Point

Use either path("", include(...)) or path("blog/", include(...)), not both with duplicate patterns, unless you intentionally expose the same app at two prefixes.

blog/urls.py (app):

python
from django.urls import path
 
from . import views
 
urlpatterns = [
    path("", views.home, name="home"),
    path("posts/", views.post_list, name="post-list"),
    path("posts/<int:pk>/", views.post_detail, name="post-detail"),
]

If mounted at path("blog/", include("blog.urls")), the list URL is /blog/posts/.

path() signature

python
path(route, view, kwargs=None, name=None)
PartMeaning
routeURL pattern string (no leading domain)
viewCallable or View.as_view()
nameLogical name for reverse lookup
kwargsExtra dict passed to view (rare at path level)

Path Converters

Capture part of the URL and pass it as a view argument:

ConverterMatchesView receives
strNon-empty string (default)str
intIntegerint
slugSlug ([-a-zA-Z0-9_]+)str
uuidUUIDUUID
pathAny path including /str

Examples:

python
path("posts/<int:pk>/", views.post_detail),
path("category/<slug:slug>/", views.category),
path("files/<path:filepath>/", views.serve_file),

blog/views.py (stubs for routing practice):

Code explanation:

  • pk in the path becomes post_detail(request, pk=1) for /posts/1/
  • Converter name in angle brackets must match the view parameter name

include() for App Routes

python
path("api/", include("blog.urls")),
path("accounts/", include("django.contrib.auth.urls")),

Benefits:

  • Each app owns its urls.py
  • Reusable apps ship URLconf you mount once
  • Root file stays small

Namespaces (optional)

When the same app is included twice or you need disambiguation:

python
# config/urls.py
path("blog/", include(("blog.urls", "blog"), namespace="blog")),

Template: {% url 'blog:post-detail' pk=1 %}. Use when docs or third-party apps require it; simple projects often skip namespaces.

re_path() (Regular Expressions)

Legacy and edge cases:

python
from django.urls import re_path
 
re_path(r"^posts/(?P<year>[0-9]{4})/$", views.posts_by_year),

Prefer path() with converters for readability. Use re_path() when patterns do not fit converters.

Named URLs and Reverse Resolution

name= in path()

python
path("posts/<int:pk>/", views.post_detail, name="post-detail"),

reverse() in Python

python
from django.shortcuts import redirect
from django.urls import reverse
 
def some_view(request):
    url = reverse("post-detail", args=[42])
    # '/posts/42/' or '/blog/posts/42/' depending on mount
    return redirect("post-detail", pk=42)  # shortcut

redirect() shortcuts

python
return redirect("home")
return redirect("post-detail", pk=1)
return redirect("/hard-coded/")  # avoid when possible

Hard-coded paths break when URL structure changes; named URLs stay stable.

Templates (preview)

In templates (covered in chapter 08):

django
<a href="{% url 'post-detail' pk=post.id %}">Read more</a>

Requires name="post-detail" on the path.

Query Strings vs Path Parameters

Path: /posts/5/ → view arg pk=5

Query: /posts/?page=2&tag=djangorequest.GET

python
def post_list(request):
    page = request.GET.get("page", "1")
    tag = request.GET.get("tag")
    ...

Use path segments for resource identity; query strings for filters, pagination, optional params.

RESTful Routing Conventions

Conceptual map (full CRUD in views chapter):

MethodPathAction
GET/posts/List
GET/posts/<id>/Detail
POST/posts/Create
PUT/PATCH/posts/<id>/Update
DELETE/posts/<id>/Delete

Django URLconf only maps path + view; HTTP method dispatch happens in the view (@require_http_methods, CBV, or DRF). Same path, different methods:

python
@require_http_methods(["GET", "POST"])
def post_list(request):
    if request.method == "GET":
        ...
    elif request.method == "POST":
        ...

DRF ViewSets and routers automate this table—chapter 19.

Trailing Slashes

Django APPEND_SLASH (default True) redirects /posts/posts/ when a trailing-slash pattern exists.

Convention: define patterns with trailing slash:

python
path("posts/", views.post_list),  # preferred

Compare: Spring MVC routing uses different path rules—Django’s slash behavior is easy to miss in API-only apps (APPEND_SLASH=False sometimes used for REST).

Debugging URL Issues

bash
python manage.py show_urls

If django-extensions is not installed, inspect manually or add temporary logging:

python
# config/urls.py — dev only
from django.urls import get_resolver
print(get_resolver().url_patterns)

Common errors:

ErrorCause
404Pattern order wrong; more specific paths must come before catch-alls
NoReverseMatchWrong name, missing arg, or namespace
TypeErrorView missing pk parameter matching converter

Full Example: blog/urls.py

python
from django.urls import path
 
from . import views
 
urlpatterns = [
    path("", views.home, name="home"),
    path("posts/", views.post_list, name="post-list"),
    path("posts/new/", views.post_create, name="post-create"),
    path("posts/<int:pk>/", views.post_detail, name="post-detail"),
    path("posts/<int:pk>/edit/", views.post_edit, name="post-edit"),
    path("category/<slug:slug>/", views.category, name="category"),
]

config/urls.py:

python
urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("blog.urls")),
]

Test:

bash
python manage.py runserver
curl http://127.0.0.1:8000/posts/1/

Post-Chapter Checklist

  • You can add path() entries with int and slug converters
  • You use include("blog.urls") from the project root
  • You assign name= and use reverse() / redirect()
  • You distinguish path args from request.GET
  • You know RESTful path naming for later CRUD chapters

FAQ

path() vs url()?

url() was removed in Django 4+—use path() / re_path().

API without trailing slash?

Set APPEND_SLASH = False in settings and define patterns consistently; document for API clients.

Multiple parameters?

python
path("archive/<int:year>/<int:month>/", views.archive),

View: def archive(request, year, month):

{% url %} is for internal routes only. Use plain href="https://...".

Same name in two apps?

Use namespaces or unique names like blog-post-detail vs shop-item-detail.