Video coming soon

The recording for this lesson has not been published yet.

What we cover

  • From URL to view to response
  • as_view(), dispatch() and HTTP method handlers
  • The generic view hierarchy in one picture
  • Reading the Django source and ccbv.co.uk

Lesson notes

1. From URL to view to response

Every page you built in the tutorial went through the same five steps. Knowing them by heart is what makes class-based views feel simple instead of magic.

  1. The browser sends an HTTP request: a method (GET, POST), a path, headers, maybe a body.
  2. Django wraps it in an HttpRequest object and hands it to the middleware chain.
  3. The URL resolver matches the path against urlpatterns and picks one callable.
  4. That callable receives the request and returns an HttpResponse.
  5. The middleware chain sees the response on the way out, and Django writes it to the socket.
Browser GET /post/3/ Middleware session, csrf URLconf path() match VIEW request in, response out Model Template HttpResponse (200 text/html)

Notice what step 3 says: a callable. Django does not care whether that callable is a function you wrote or something a class produced. That single detail is the whole door through which class-based views walk in.

2. The view contract, twice

Here is post_detail from the tutorial, and the smallest possible class that does the same thing:

# blog/views.py - the tutorial version
from django.shortcuts import get_object_or_404, render

from .models import Post


def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, "blog/post_detail.html", {"post": post})
# blog/views.py - the same thing as a class
from django.shortcuts import get_object_or_404, render
from django.views import View

from .models import Post


class PostDetailView(View):
    def get(self, request, pk):
        post = get_object_or_404(Post, pk=pk)
        return render(request, "blog/post_detail.html", {"post": post})
# blog/urls.py
urlpatterns = [
    path("post/<int:pk>/", views.post_detail, name="post_detail"),          # before
    path("post/<int:pk>/", views.PostDetailView.as_view(), name="post_detail"),  # after
]

The class version is one line longer and gains you nothing yet. That is honest and important: View on its own is not the point. The point is that once behaviour lives in methods, you can override one method instead of copying a whole function.

3. as_view(), dispatch() and the method handlers

as_view() is a class method that returns a plain function. That function creates a fresh instance of your class for every request, attaches self.request, self.args and self.kwargs, and then calls dispatch(). dispatch() looks at request.method, lowercases it, and calls the method with that name.

URLconf as_view() view(request) new instance, setup() dispatch() reads method get(request, *a, **kw) post(request, ...) no handler: 405 Not Allowed

Written out, dispatch() is about six lines of Django source:

def dispatch(self, request, *args, **kwargs):
    if request.method.lower() in self.http_method_names:
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

That gives you three very useful hooks:

  • setup() - runs before anything else, good place to stash an object on self.
  • dispatch() - runs for every HTTP method, the natural home for permission checks and logging.
  • get() / post() / delete() - one method per method, so the if request.method == "POST": branch of post_new disappears.
class PostDetailView(View):
    def dispatch(self, request, *args, **kwargs):
        # Runs for GET, POST, HEAD - everything.
        response = super().dispatch(request, *args, **kwargs)
        response["X-Blog-View"] = "PostDetailView"
        return response

4. The generic view hierarchy in one picture

The real win is not View, it is the ready-made subclasses. They are built from small mixins, each with one job, and they all end at View.

View
├── TemplateView            render a template, no model
├── RedirectView            just redirect
├── ListView                many objects  (MultipleObjectMixin + TemplateResponseMixin)
├── DetailView              one object    (SingleObjectMixin + TemplateResponseMixin)
├── FormView                a form, no model
├── CreateView              a ModelForm that saves a new object
├── UpdateView              a ModelForm that saves an existing object
└── DeleteView              confirm, then delete
Tutorial viewWhat it doesGeneric replacement
post_listlists published postsListView
post_detailone post by pkDetailView
post_newempty form, then saveCreateView
post_editbound form, then saveUpdateView

Those four replacements are exactly what lessons 3.2 and 3.3 do, line by line.

5. Reading the source instead of guessing

Generic views are short but deeply inherited, so the honest question is always "which class defines the method I want to override?". Three ways to answer it:

  • ccbv.co.uk - shows every class with all inherited attributes and methods flattened into one page. Bookmark it now.
  • The MRO - ask Python directly:
>>> from django.views.generic import ListView
>>> for cls in ListView.__mro__:
...     print(cls.__name__)
ListView
MultipleObjectTemplateResponseMixin
TemplateResponseMixin
BaseListView
MultipleObjectMixin
ContextMixin
View
object
  • Your editor - jump to the definition of django.views.generic.list. The whole module is under 200 lines.

6. Exercise

  1. Convert post_detail to a View subclass with a single get() method and change the URLconf to use PostDetailView.as_view(). The page must look identical.
  2. Add a dispatch() override that prints request.method and confirm it runs before get().
  3. Send a POST to that URL (curl -X POST) and observe the 405 response http_method_not_allowed produces.
  4. Print DetailView.__mro__ in python manage.py shell and find which class defines get_object(). Then open that class on ccbv.co.uk.

Further reading

After this lesson you will

  • Describe the request and response cycle in Django
  • Explain what a class-based view adds to a function