Skip to content

Views & URL Patterns

Employees and volunteers can browse and edit the animals in the admin, but the site has no pages for visitors. To serve a page, we need a view that returns a response, and a URL pattern that routes requests to it.

This chapter's starting branch provides the HTML for our base template, and the site's stylesheet. Check it out before we start:

shell
git stash -u
git fetch --all
git checkout -b my-ch05-start --no-track origin/ch05-start
uv sync

If Git prints No local changes to save, there was nothing to set aside.

A First View

A view is a function that takes a request and returns a response.

The simplest view, in animals/views.py, returns a line of text:

Python
from django.http import HttpResponse


def animal_list(request):
    return HttpResponse("Animals looking for a home")

Django passes an HttpRequest object to every view as its first argument.

This view returns an HttpResponse with a content type of text/html. The status code defaults to 200.

Rendering a Template

Templates are HTML files mixed with Django's template language, which can add logic, insert the variables a view passes in, and build URLs.

Django looks for templates in a templates/ folder inside each installed app, so ours goes in animals/templates/animals/. The inner animals/ folder makes the template's name animals/animal_list.html, so it can't clash with another app's template.

Create animal_list.html in animals/templates/animals/:

HTML
<h1>{{ heading }}</h1>

{{ heading }} shows the value named heading from the template's context, a dictionary of values the view passes in.

We can use the render function from the django.shortcuts module to render the template and pass in the context. Replace the contents of animals/views.py with:

Python
from django.shortcuts import render


def animal_list(request):
    context = {"heading": "Animals looking for a home"}
    return render(request, "animals/animal_list.html", context)

render() loads the template, renders it with the context, and returns an HttpResponse. For now the context holds one string.

URL Patterns

Django matches the request's path against URL patterns to choose the view that handles it. The patterns are in a URLconf, a Python module with a urlpatterns list.

startapp doesn't generate a URLconf for the app, so create animals/urls.py:

Python
from django.urls import path

from . import views

app_name = "animals"
urlpatterns = [
    path("", views.animal_list, name="list"),
]
  • path(): Takes a route, and the view to call when the route matches. The empty route "" matches when there's nothing left of the path to match.
  • name and app_name: Together they give this pattern the name animals:list that we'll use for internal links without hardcoding URLs.
  • urlpatterns: The list of patterns Django tries, in order, to find the view for a request.

Include the App's URLs

Django starts matching in the top-level config/urls.py, and that module doesn't know about our new file.

Add all of the animals app's paths in one line:

config/urls.py
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path

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

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Open the Page

runserver restarts when a Python file in the project changes, so a server that's already running picks up our new URL patterns. If it isn't running, start it with uv run manage.py runserver. Open http://127.0.0.1:8000/.

Success

You should see the heading Animals looking for a home.

From Request to Response

flowchart LR
    browser(["GET /"]) --> root["config/urls.py"]
    root --> app["animals/urls.py"]
    app --> view["animal_list()"]
    view --> template["animal_list.html"]
    template --> response(["200 OK"])
  1. Django reads ROOT_URLCONF, which is set to config.urls. It tries the patterns there in order.
  2. The "" route matches, and include() passes what's left of the path to animals.urls, where the empty route matches too.
  3. Django calls animal_list() with an HttpRequest.
  4. render() returns the HttpResponse sent back to the browser.
Curious about Middleware?

Before Django matches the URL, the request passes through each class listed in the MIDDLEWARE setting, and the response passes back through them after the view returns. Sessions, CSRF protection, and authentication are middleware.

The Debug Page

With the server running, open http://127.0.0.1:8000/animals/:

Text
Page not found (404)

Using the URLconf defined in config.urls, Django tried these URL patterns, in this order:

    admin/ [namespace='admin']
    [name='list']
    ^media/(?P<path>.*)$

The current path, animals/, didn’t match any of these.

The debug page lists every pattern Django tried. The second is ours: its route is empty, so only its name is shown. The last one serves the animals' uploaded photos, from static() in config/urls.py. When a new page gives us a 404, this list is the first place to look.

Info

We see this page because DEBUG = True in config/settings.py.

Debug pages show our URL patterns, and for an exception they show tracebacks and settings.

Run your production site with DEBUG = False.