Chapter 1 · A request, end to end
1.3 The URL is resolved and the view is called
How BaseHandler turns a path into a call of a view: the resolver is made once and imports the URLconf on first use, a route pattern matches the path and converts its captures, and the view is called with what was captured.
The innermost layer of the chain is not a middleware but a method of the handler, BaseHandler. It does four things in order: resolve the path to a view, offer the view to the view middleware, call it, and refuse what is not a response. The centre of the chain describes the method in full, including the branch for a template response that has not been rendered, which this request does not take. This is the recording’s first request, where the resolver’s first use shows:
BaseHandler._get_response
BaseHandler.resolve_request
URLResolver.resolve('/entries/2026/')
URLResolver.urlconf_module: the root URLconf is imported
import journal.urls
import journal.views
path("entries/<int:year>/", by_year, name="entries-by-year")
URLPattern.resolve('entries/2026/')
RoutePattern.match('entries/2026/')
IntConverter.to_python('2026')
ResolverMatch(journal.views.by_year, kwargs={'year': 2026})
ResolverMatch(journal.views.by_year, kwargs={'year': 2026})
CsrfViewMiddleware.process_view
BaseHandler.make_view_atomic
journal.views.by_year(request, year=2026)
One resolver per process
BaseHandler asks get_resolver for the resolver of the current URLconf, which is settings unless a middleware has set an attribute named "urlconf" on the request. get_resolver is backed by a function wrapped in functools.cache, so one URLResolver is made for each URLconf a process ever resolves with, and kept. The root resolver’s own pattern is the regular expression "^/": it matches the leading slash, and hands what follows to its patterns.
Those patterns are a cached_property, URLResolver, and reading it for the first time reads another, URLResolver, which imports the module settings names. That import is the moment in the recording where the project’s own code first runs for a request: importing the URLconf imports the views module, and each call of path in it makes a pattern object. The URLconf is imported once per process, here by the first request, and under the development server earlier, by the system checks (Before the first request).
What a path call makes
path is _path with the pattern class fixed to RoutePattern. For a view, it makes a RoutePattern from the route string and wraps it with the view in a URLPattern; for an include, it makes a URLResolver instead, which is how URLconfs nest (From a URLconf to a tree).
A RoutePattern turns its route into a regular expression when it is made. _route_to_regex walks the route for angle-bracketed parameters, and for each one looks up the converter named before the colon, int here, in the registered converters, escapes the literal text between parameters, and writes the parameter as a named group whose body is the converter’s own regular expression; for a view’s pattern it anchors the end. The route "entries/ thus becomes the expression "^entries/, and the pattern remembers that the group year belongs to the int converter.
Matching the path
URLResolver tries its patterns in the order of the list and returns at the first match. For a URLPattern that is URLPattern, which asks its RoutePattern to match what is left of the path. RoutePattern has a fast path for a route with no parameters, a string comparison; with parameters it runs the regular expression, and passes each captured string to its converter’s to_python. A converter that raises ValueError makes the pattern not match, which is how a route with <int:year> declines a path whose year is not a number. The converted captures are the view’s keyword arguments (Routes, regular expressions and converters, Resolving a path).
A match is a ResolverMatch: the view, its positional and keyword arguments, the pattern’s name, the route, and the namespaces on the way. The recording shows it made twice. The pattern makes it, and the resolver that found the pattern makes it again with its own application name and namespace put before the pattern’s and the list of patterns tried, which is why a nested include adds a namespace at each level. When no pattern matches, the resolver raises Resolver404 with that list of what was tried, which becomes a 404 response at the wrapper around the centre, and when settings.DEBUG is true the page that shows the patterns tried.
resolve_request keeps the match as HttpRequest, where the view and anything after it can read which pattern answered, and returns it; _get_response unpacks it into the three things it needs, the view, its positional arguments and its keyword arguments.
Between the match and the call
With a view in hand, the handler runs the process_view hooks in the order of settings. Of the seven default middleware only CsrfViewMiddleware has one. For a GET, or any other method the HTTP specification calls safe, it accepts the request at once; for any other method it checks the origin or the referer, and then that the token sent with the request matches the secret from the cookie. A view marked exempt is passed over, and this is the reason the check lives in process_view and not in process_request: the view has to be known. Each step of the check, and what answers a request it refuses, is The CSRF check: CsrfViewMiddleware.
BaseHandler then wraps the view in a transaction for each database whose settings have "ATOMIC_REQUESTS" true, unless the view was marked with non_atomic_requests for it; none has by default, and the view is returned unchanged. An asynchronous view would be wrapped so that it can be called as a function. This one is an ordinary function, and it is called with the request and the keyword arguments the route captured: year=2026, an int, because the converter made it one.
The view
A view is anything callable that takes a request and returns a response. The one in this chapter is a function of two lines, and they are the two things most views do: it asks the model’s manager for a queryset, filtered and ordered, and it returns render of a template with that queryset in the context. A queryset becomes SQL follows the first line, and The template is rendered the second. A class-based view reaches the same point by another route: View.as_view returns a function for the URLconf, and that function makes an instance of the class for each request and dispatches on the method (View.as_view and dispatch).
What the view returns is checked before anything else happens to it. BaseHandler raises ValueError for a view that returned None, with the message naming the view, and for a coroutine that was never awaited. A response that passes leaves the centre, and the chain begins to unwind.