Going DeeperExtra· 45 min read

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 viewClass-based view
What it isa functiona class
Good forcustom, one-off logicstandard CRUD patterns
You writeall the logic yourselfjust the parts that differ
Real codebasescommonvery 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:

A function view vs a ListView
# 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 template

The 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:

Use .as_view() to plug a CBV into a URL
# 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:

DetailView shows one record by its pk
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):

CreateView / UpdateView / DeleteView = full CRUD
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:

  1. CreateView shows a blank form (built from fields), validates the submission, saves a new record, then redirects to success_url.
  2. UpdateView does the same but pre-fills the form with an existing record (found by its pk in the URL) and updates it.
  3. DeleteView shows a "are you sure?" confirmation page, then deletes the record on POST.
  4. reverse_lazy("post_list") looks up the URL named post_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?

Answer: DetailView fetches one object by its pk (captured as <int:pk> in the URL) and renders it. ListView shows many; CreateView shows a blank form.

✍️ Practice

  1. Rewrite your post list and post detail pages as a ListView and DetailView.
  2. Replace your add/edit/delete views with CreateView, UpdateView and DeleteView wired with .as_view().

🏠 Homework

  1. Convert one full CRUD feature from function-based views to class-based generic views and confirm every page still works.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →