CBV cheatsheet.
Generic list
from django.views.generic import ListView
class PostListView(ListView):
model = Post
template_name = "blog/list.html"
context_object_name = "posts"
paginate_by = 20
def get_queryset(self):
qs = super().get_queryset()
return qs.filter(published=True).select_related("author")
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["tags"] = Tag.objects.all()
return ctx
Generic detail
from django.views.generic import DetailView
class PostDetailView(DetailView):
model = Post
template_name = "blog/detail.html"
slug_field = "slug"
slug_url_kwarg = "slug"
/posts/<slug:slug>/ → matches <slug:slug> url kwarg.
CreateView / UpdateView / DeleteView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
class PostCreateView(CreateView):
model = Post
fields = ["title", "body"]
success_url = reverse_lazy("blog:list")
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(UpdateView):
model = Post
fields = ["title", "body"]
template_name = "blog/post_form.html" # same as create by default
class PostDeleteView(DeleteView):
model = Post
success_url = reverse_lazy("blog:list")
TemplateView / RedirectView
from django.views.generic import TemplateView, RedirectView
class AboutView(TemplateView):
template_name = "about.html"
class HomeRedirect(RedirectView):
url = "/posts/"
permanent = False
FormView
from django.views.generic.edit import FormView
from .forms import ContactForm
class ContactView(FormView):
template_name = "contact.html"
form_class = ContactForm
success_url = "/thanks/"
def form_valid(self, form):
form.send_email()
return super().form_valid(form)
LoginRequiredMixin / PermissionRequiredMixin
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin
class MyView(LoginRequiredMixin, ListView):
login_url = "/login/"
redirect_field_name = "next"
class AdminView(PermissionRequiredMixin, ListView):
permission_required = ["blog.delete_post"]
class OwnerView(UserPassesTestMixin, UpdateView):
def test_func(self):
return self.get_object().author == self.request.user
Custom mixin
class AjaxOnlyMixin:
def dispatch(self, request, *args, **kwargs):
if not request.headers.get("x-requested-with") == "XMLHttpRequest":
return HttpResponseBadRequest()
return super().dispatch(request, *args, **kwargs)
class MyView(AjaxOnlyMixin, ListView):
...
ContextMixin
class TitleMixin:
title = "Site"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["title"] = self.title
return ctx
URL configuration
path("posts/", PostListView.as_view(), name="list"),
path("posts/<slug:slug>/", PostDetailView.as_view(), name="detail"),
path("posts/new/", PostCreateView.as_view(), name="create"),
path("posts/<int:pk>/edit/", PostUpdateView.as_view(), name="edit"),
path("posts/<int:pk>/delete/", PostDeleteView.as_view(), name="delete"),
as_view() kwargs
path("posts/", PostListView.as_view(paginate_by=10, template_name="custom.html"))
View (base class)
from django.views import View
class MyView(View):
def get(self, request):
return HttpResponse("GET")
def post(self, request):
return HttpResponse("POST")
For when generic CBV is overkill, but you want method dispatch.
Method dispatching
def dispatch(self, request, *args, **kwargs):
if request.user.is_blocked:
return HttpResponseForbidden()
return super().dispatch(request, *args, **kwargs)
FBV vs CBV
FBV wins for:
- Simple custom logic.
- Easier to read top-to-bottom.
CBV wins for:
- CRUD patterns (use the generics).
- Composition via mixins.
- Reusable view logic.
Don’t religiously pick one — use what reads clearly.
get_object override
def get_object(self, queryset=None):
obj = super().get_object(queryset)
if obj.author != self.request.user:
raise Http404
return obj
form_invalid
def form_invalid(self, form):
messages.error(self.request, "Fix the errors below")
return super().form_invalid(form)
Common mistakes
- Forgetting
success_url— redirect fails. - Using
model.objects.all()instead ofget_queryset()for filtered list. LoginRequiredMixinnot first in MRO order —login_urlignored.- Two CBVs sharing a template without specifying
template_name. - Forgetting to call
super()in lifecycle methods.
Read this next
If you want my CBV mixins + base classes, they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .