Class-Based & Generic Views
Replace pages of repetitive view code with Django’s ready-made generic views — ListView, DetailView, and the Create/Update/Delete trio.
What you will learn
- Explain class-based vs function-based views
- Use ListView and DetailView to show data
- Use CreateView, UpdateView and DeleteView for CRUD
Two ways to write a view
Everything so far used function-based views (FBVs) — a view is a plain Python function. They are clear and great for learning. But you quickly notice that "list these records", "show one record", "save this form" are the same shapes repeated in every project. Django bottles those shapes up as class-based views (CBVs): views written as Python classes that already contain the common logic, so you only fill in the few details that differ.
| Function-based view | Class-based view | |
|---|---|---|
| What it is | a function | a class |
| Good for | custom, one-off logic | standard CRUD patterns |
| You write | all the logic yourself | just the parts that differ |
| Real codebases | common | very common — expect both |
Note: You will meet both on the job, so you must be able to read and write each. CBVs shine for the everyday list/detail/create/update/delete pages; FBVs stay handy when the logic is unusual.
Generic views: the ready-made classes
A generic view is a class-based view Django ships with for a common job. The two read-only ones are ListView (show many records) and DetailView (show one record). Compare the function version with the class version of a post list:
# FUNCTION-BASED — you write the whole thing
def post_list(request):
posts = Post.objects.all()
return render(request, "post_list.html", {"posts": posts})
# CLASS-BASED — the same job, far less code
from django.views.generic import ListView
from .models import Post
class PostList(ListView):
model = Post # which model to list
template_name = "post_list.html"
context_object_name = "posts" # the variable name in the templateThe ListView already knows how to fetch Post.objects.all(), render a template, and pass the list — so you only declare three things: which model, which template_name, and what to call the list inside the template (context_object_name). That is the whole view.
Wiring a class-based view to a URL
A URL pattern expects a function, but a CBV is a class. The class provides a .as_view() method that hands back a function for you — so you call it in urls.py:
# blog/urls.py
from django.urls import path
from .views import PostList, PostDetail
urlpatterns = [
path("", PostList.as_view(), name="post_list"),
path("<int:pk>/", PostDetail.as_view(), name="post_detail"),
]<int:pk> captures a number from the URL and names it pk ("primary key" — each row’s unique id). DetailView uses that pk automatically to look up the single record — you write almost nothing:
from django.views.generic import DetailView
class PostDetail(DetailView):
model = Post
template_name = "post_detail.html"
context_object_name = "post"Note: Output: visiting /5/ runs PostDetail, which fetches the Post with pk=5 and renders post_detail.html with that post available as post. No objects.get(id=5) to write — the generic view did it.
The CRUD trio: Create / Update / Delete
Three more generic views complete CRUD and even build the form from your model for you. You point them at a model, list the fields to include, and give a place to go after success (success_url):
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Post
class PostCreate(CreateView):
model = Post
fields = ["title", "body"] # builds the form for you
template_name = "post_form.html"
success_url = reverse_lazy("post_list")
class PostUpdate(UpdateView):
model = Post
fields = ["title", "body"]
template_name = "post_form.html"
success_url = reverse_lazy("post_list")
class PostDelete(DeleteView):
model = Post
template_name = "post_confirm_delete.html"
success_url = reverse_lazy("post_list")What each one does, step by step:
CreateViewshows a blank form (built fromfields), validates the submission, saves a new record, then redirects tosuccess_url.UpdateViewdoes the same but pre-fills the form with an existing record (found by itspkin the URL) and updates it.DeleteViewshows a "are you sure?" confirmation page, then deletes the record on POST.reverse_lazy("post_list")looks up the URL namedpost_list— "lazy" because URLs are not loaded yet when the class is defined.
Watch out: The form template still needs {% csrf_token %} inside its <form> — generic views build the form fields, but the CSRF safety token is still your responsibility. (Render the auto-built form with {{ form.as_p }}.)
Tip: Reach for generic views when a page is a standard list/detail/create/update/delete. Drop back to a function view the moment the logic gets unusual — mixing both in one project is normal and expected.
Q. Which generic view shows a single record looked up by its primary key in the URL?
✍️ Practice
- Rewrite your post list and post detail pages as a
ListViewandDetailView. - Replace your add/edit/delete views with
CreateView,UpdateViewandDeleteViewwired with.as_view().
🏠 Homework
- Convert one full CRUD feature from function-based views to class-based generic views and confirm every page still works.