The recording for this lesson has not been published yet.
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.
GET, POST), a
path, headers, maybe a body.HttpRequest object and hands it to the middleware
chain.urlpatterns and picks one
callable.HttpResponse.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.
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.
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.
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
Class attributes are shared by every request in the process. Anything that
belongs to one request goes on self inside a method, never in the class
body. And note the call is PostDetailView.as_view() in the URLconf - not
PostDetailView().
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 view | What it does | Generic replacement |
|---|---|---|
post_list | lists published posts | ListView |
post_detail | one post by pk | DetailView |
post_new | empty form, then save | CreateView |
post_edit | bound form, then save | UpdateView |
Those four replacements are exactly what lessons 3.2 and 3.3 do, line by line.
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:
>>> from django.views.generic import ListView
>>> for cls in ListView.__mro__:
... print(cls.__name__)
ListView
MultipleObjectTemplateResponseMixin
TemplateResponseMixin
BaseListView
MultipleObjectMixin
ContextMixin
View
object
django.views.generic.list. The whole module is under 200 lines.Before you override a method, read it. A class-based view you cannot explain is worse than the function view it replaced.
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.dispatch() override that prints
request.method and confirm it runs before get().curl -X POST) and observe the 405 response
http_method_not_allowed produces.DetailView.__mro__ in python manage.py shell and find
which class defines get_object(). Then open that class on ccbv.co.uk.