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:
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:
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/:
<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:
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:
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.nameandapp_name: Together they give this pattern the nameanimals:listthat 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:
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"])
- Django reads
ROOT_URLCONF, which is set toconfig.urls. It tries the patterns there in order. - The
""route matches, andinclude()passes what's left of the path toanimals.urls, where the empty route matches too. - Django calls
animal_list()with anHttpRequest. render()returns theHttpResponsesent 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/:
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.