0% completed
How Django Handles Web Requests
On This Page
Step 1: A Web Server Receives the Request
Step 2: Django Loads the Settings
Step 3: The Request Becomes an Object
Step 4: The URL Resolver Finds the View
Step 5: The View Runs
What happens between the moment a reader types http://127.0.0.1:8000/articles/ into a browser and the moment your article_list view starts to run? Django does not search your code for a function with a matching name. It follows a fixed path: a web server receives the request, Django loads the project settings, and the URL resolver compares the address with a list of patterns until one of them names a view.
This lesson follows that path from the browser to the view. The next lesson covers the full cycle around the view, including the request and response objects and the middleware that both of them pass through.
Step 1: A Web Server Receives the Request
A browser does not talk to Django directly. It sends an HTTP request to a web server, a program that listens on a network port, reads the raw request, and hands it to the Python application behind it. The two sides speak a standard Python interface, so any compliant server can run any Django project.
There are two such interfaces. WSGI (Web Server Gateway Interface) is the long-established one, in which each request is handled from start to finish by one call. ASGI (Asynchronous Server Gateway Interface) is the newer one, which also supports asynchronous code and long-lived connections such as WebSockets. A new project contains an entry point for each:
# mysite/wsgi.py import os from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings') application = get_wsgi_application()
mysite/asgi.py looks the same, except that it calls get_asgi_application(). The object named application is what a web server calls once for every request.
During development, the server is python manage.py runserver. It reads the WSGI_APPLICATION setting, which points to the application object in mysite/wsgi.py, and it prints "Starting WSGI development server" when it starts. In production, a dedicated server takes its place, for example Gunicorn for WSGI or Uvicorn for ASGI, and it is given the same application object.
Step 2: Django Loads the Settings
Before Django can handle any request, it must know which settings to use. Both entry points, and manage.py as well, set the environment variable DJANGO_SETTINGS_MODULE to mysite.settings. Django imports that module once, when the process starts, and reads every setting from it.
Two settings matter for the path to the view. MIDDLEWARE lists the layers every request passes through before it reaches a view, which the next lesson explains. ROOT_URLCONF names the module that holds the first list of URL patterns Django will search:
# mysite/settings.py ROOT_URLCONF = 'mysite.urls'
A module that contains URL patterns is called a URLconf, short for URL configuration. The root URLconf is where every search for a view begins.
Step 3: The Request Becomes an Object
For each request, Django builds an HttpRequest object from the raw data the server passed in. It holds the method, such as GET or POST, the path, such as /articles/, the query string, the headers, and the submitted form data. Your view receives this object as its request argument.
The request then passes through the middleware layers listed in MIDDLEWARE, and only after that does Django look for a view. The next lesson opens that object and those layers in detail; for now, it is enough to know that they run before URL resolution.
Step 4: The URL Resolver Finds the View
Django now has a path, such as /articles/7/, and it must decide which view handles it. This work is called URL resolution, and the code that does it is the URL resolver. The resolver reads the urlpatterns list in the root URLconf. Each entry, made with path(), pairs a pattern with a view or with another URLconf to search.
Connect the blog to the project with include(), which tells the resolver to continue its search in another URLconf:
# mysite/urls.py from django.contrib import admin from django.urls import include, path urlpatterns = [ path('admin/', admin.site.urls), path('articles/', include('blog.urls')), ]
The startapp command does not create a urls.py file for the app, so create blog/urls.py yourself:
# blog/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.article_list, name='article_list'), path('welcome/', views.welcome, name='welcome'), path('<int:pk>/', views.article_detail, name='article_detail'), ]
The pattern '<int:pk>/' matches a whole number followed by a slash, and it passes the number to the view as an argument named pk. Chapter 3 explains these patterns, which are called path converters, and the name argument in detail.
For a request to /articles/7/, the resolver works through these steps:
- It removes the leading slash and works with
articles/7/. The domain name and the query string, such as?page=2, are never part of the match. - It tries the patterns in
mysite/urls.pyin order.admin/does not match, andarticles/matches the start of the path. - Because that entry uses
include(), the resolver removes the matched part and searchesblog/urls.pywith the rest, which is7/. - It tries the patterns in
blog/urls.pyin order. The empty pattern''does not match7/, and neither doeswelcome/, but<int:pk>/does, and it capturespk=7. - The search stops at the first match, and Django calls
views.article_detail(request, pk=7).
The diagram follows that same request. Watch how the path gets shorter at the include() step, and how the resolver stops at the first pattern that matches.
You can ask the resolver directly which view a path leads to. The resolve() function runs the same search and returns what it found. Try it in the interactive shell that python manage.py shell opens:
from django.urls import resolve print(resolve('/articles/7/'))
ResolverMatch(func=blog.views.article_detail, args=(), kwargs={'pk': 7}, url_name='article_detail', app_names=[], namespaces=[], route='articles/<int:pk>/', captured_kwargs={'pk': 7})
The output shows the view function, the arguments it will receive, and the full route that matched, which joins the two patterns.
The order of the list matters because the first match wins. If you add a second path('welcome/', ...) entry lower in blog/urls.py, it is never used, because the first welcome/ entry always matches before the resolver reaches it. Put more specific patterns above general ones.
When no pattern matches, the resolver gives up and Django answers with a 404 "Page not found" response. While DEBUG is True, the error page lists every pattern it tried, in order, which makes a typing mistake in a pattern easy to find. A request for /nowhere/ shows this:
Page not found (404) Request Method: GET Request URL: http://127.0.0.1:8000/nowhere/ Using the URLconf defined in mysite.urls, Django tried these URL patterns, in this order: 1. admin/ [namespace='admin'] 2. articles/ The current path, nowhere/, didn't match any of these.
Step 5: The View Runs
The resolver has found a view, so Django calls it with the request object and the captured values. A view must return a response object. The simplest response is plain text:
# blog/views.py from django.http import HttpResponse from django.shortcuts import render from .models import Article def welcome(request): return HttpResponse("Welcome to the blog!") def article_list(request): articles = Article.objects.all() return render(request, 'blog/article_list.html', {'articles': articles}) def article_detail(request, pk): article = Article.objects.get(pk=pk) return HttpResponse(article.title)
A request to /articles/welcome/ now returns the text "Welcome to the blog!" with status 200, and a request to /articles/1/ returns the title of the article whose id is 1. The article_detail view has a weakness: if no article has the requested id, get() raises an exception and the reader sees a server error. The next lesson shows what Django does with that exception and how to return a proper 404 instead.
Plain text is useful for testing, but a real page needs HTML. Change the welcome view so that it renders a template instead:
# blog/views.py def welcome(request): return render(request, 'blog/welcome.html')
Then create the template in the app's templates folder, at blog/templates/blog/welcome.html:
<!DOCTYPE html> <html> <head> <title>Welcome</title> </head> <body> <h1>Welcome to the blog!</h1> </body> </html>
Now /articles/welcome/ returns a full HTML page. The URL pattern did not change, because the resolver only cares which view to call, not what that view returns.
Reading Progress
0%
On This Page
Step 1: A Web Server Receives the Request
Step 2: Django Loads the Settings
Step 3: The Request Becomes an Object
Step 4: The URL Resolver Finds the View
Step 5: The View Runs