Generic Views

Reviewed & published by Brayan K

Django's generic class-based views are pre-built views that implement the most common web patterns — listing objects, showing one object's detail, and creating, updating, or deleting records — so you get full pages of working functionality by setting just a few attributes.

Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll meet the five generic views you'll use every day — ListView, DetailView, CreateView, UpdateView, and DeleteView — and learn how to customise them when the defaults aren't quite enough.

What You'll Learn in This Lesson

1 Displaying Records: ListView and DetailView

ListView renders a page showing many objects, and DetailView renders a page for a single object. You point each at a model, and Django queries the database, passes the results to a template, and renders the response — all without you writing query or rendering code.

Here are the two views for a Post model in an app called blog:

# blog/views.py
from django.views.generic import ListView, DetailView
from .models import Post

class PostListView(ListView):
    model = Post
    template_name = "blog/post_list.html"   # optional — this is the default
    context_object_name = "posts"           # name used inside the template

class PostDetailView(DetailView):
    model = Post
    template_name = "blog/post_detail.html"  # optional — this is the default
    context_object_name = "post"

If you leave out template_name and context_object_name, Django builds defaults from the app and model names:

# Django's ListView and DetailView do this fetching for you. Here we
# simulate that dispatch in plain Python over a list of dictionaries
# so you can see exactly what each view returns to the template.

posts = [
    {"pk": 1, "title": "Hello World", "published": True},
    {"pk": 2, "title": "Django Tips", "published": True},
    {"pk": 3, "title": "Draft Idea", "published": False},
]

def list_view():
    """Like ListView: hand the whole collection to the template."""
    context = {"posts": posts}          # context_object_name = "posts"
    return context

def detail_view(pk):
    """Like DetailView: find one object by its primary key."""
    for post in posts:
        if post["pk"] == pk:
            return {"post": post}       # context_object_name = "post"
    return {"error": "404 Not Found"}

# Simulate visiting the list page, then a detail page for pk=2
list_context = list_view()
print("List page shows", len(list_context["posts"]), "posts")
for post in list_context["posts"]:
    print(" -", post["title"])

detail_context = detail_view(2)
print("Detail page title:", detail_context["post"]["title"])
print("Missing object:", detail_view(99))

# ✅ Expected output:
# List page shows 3 posts
#  - Hello World
#  - Django Tips
#  - Draft Idea
# Detail page title: Django Tips
# Missing object: {'error': '404 Not Found'}

2 Editing Records: CreateView, UpdateView, DeleteView

The editing views build and process HTML forms for you. CreateView shows a blank form and saves a new record, UpdateView shows a form pre-filled with an existing record, and DeleteView shows a confirmation page before removing one.

For CreateView and UpdateView you list the editable fields, and you tell Django where to go after a successful save with success_url — or by defining get_absolute_url() on the model:

# blog/models.py
from django.db import models
from django.urls import reverse

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()

    def get_absolute_url(self):
        # CreateView/UpdateView redirect here after saving
        return reverse("post-detail", kwargs={"pk": self.pk})


# blog/views.py
from django.views.generic import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Post

class PostCreateView(CreateView):
    model = Post
    fields = ["title", "body"]          # which fields the form shows
    # no success_url needed: get_absolute_url() is used after saving

class PostUpdateView(UpdateView):
    model = Post
    fields = ["title", "body"]
    template_name = "blog/post_form.html"   # shared with CreateView by default

class PostDeleteView(DeleteView):
    model = Post
    success_url = reverse_lazy("post-list")  # where to go after deleting
# CreateView saves a record, then redirects to get_absolute_url().
# DeleteView removes a record, then redirects to success_url.
# Here is that flow simulated in plain Python.

posts = [{"pk": 1, "title": "First Post", "body": "..."}]
next_pk = 2

def get_absolute_url(pk):
    return f"/blog/{pk}/"            # like reverse("post-detail", pk=pk)

def create_view(form_data):
    """Like CreateView: validate, save, then redirect to the new object."""
    global next_pk
    new_post = {"pk": next_pk, **form_data}
    posts.append(new_post)
    redirect_to = get_absolute_url(next_pk)
    next_pk += 1
    return f"Saved! Redirecting to {redirect_to}"

def delete_view(pk, success_url):
    """Like DeleteView: remove the object, then go to success_url."""
    for post in posts:
        if post["pk"] == pk:
            posts.remove(post)
            return f"Deleted pk={pk}. Redirecting to {success_url}"
    return "404 Not Found"

print(create_view({"title": "Second Post", "body": "Hello"}))
print(delete_view(1, "/blog/"))
print("Remaining posts:", [p["title"] for p in posts])

# ✅ Expected output:
# Saved! Redirecting to /blog/2/
# Deleted pk=1. Redirecting to /blog/
# Remaining posts: ['Second Post']

3 Customising Views: get_queryset and get_context_data

The defaults are great, but real pages need tweaks. Override get_queryset() to control which objects appear — filtering or ordering them — and override get_context_data() to add extra data to the template alongside the objects.

# blog/views.py
from django.views.generic import ListView
from .models import Post, Category

class PublishedPostListView(ListView):
    model = Post
    context_object_name = "posts"

    def get_queryset(self):
        # Only show published posts, newest first
        return Post.objects.filter(published=True).order_by("-created")

    def get_context_data(self, **kwargs):
        # Start from the default context, then add our own keys
        context = super().get_context_data(**kwargs)
        context["categories"] = Category.objects.all()
        context["total"] = self.get_queryset().count()
        return context

Always call super().get_context_data(**kwargs) first — that gives you the object list Django already prepared — then add your keys and return the dictionary.

# get_queryset() filters/orders the objects; get_context_data() adds
# extra values for the template. This simulates both overrides.

all_posts = [
    {"title": "Published A", "published": True,  "created": 3},
    {"title": "Draft B",     "published": False, "created": 2},
    {"title": "Published C", "published": True,  "created": 1},
]
categories = ["News", "Tutorials", "Reviews"]

def get_queryset():
    # Like Post.objects.filter(published=True).order_by("-created")
    published = [p for p in all_posts if p["published"]]
    return sorted(published, key=lambda p: p["created"], reverse=True)

def get_context_data():
    queryset = get_queryset()
    context = {"posts": queryset}               # the default context
    context["categories"] = categories          # extra data we added
    context["total"] = len(queryset)
    return context

context = get_context_data()
print("Showing", context["total"], "published posts:")
for post in context["posts"]:
    print(" -", post["title"])
print("Sidebar categories:", context["categories"])

# ✅ Expected output:
# Showing 2 published posts:
#  - Published A
#  - Published C
# Sidebar categories: ['News', 'Tutorials', 'Reviews']

🎯 Your Turn: Wire Up a ListView

Fill in the blank so the simulated view points at the right model. In real Django this is the model = Post attribute a generic view needs.

# 🎯 YOUR TURN: replace ___ so the view reports the model "Post"

class Post:
    pass

class PostListView:
    # In real Django you'd write: model = Post
    model = ___

    def describe(self):
        return f"This view lists objects of model: {self.model.__name__}"

view = PostListView()
print(view.describe())

# EXPECTED OUTPUT:
# This view lists objects of model: Post

Common Errors (And How to Fix Them)

Django looked for the default template but couldn't find it on disk.

✅ Fix: create the file at blog/templates/blog/post_list.html, or set template_name to a template you do have.

❌ ImproperlyConfigured: No URL to redirect to

A CreateView or UpdateView saved successfully but doesn't know where to send the user next.

✅ Fix: set success_url on the view, or define get_absolute_url() on the model.

❌ AttributeError: 'Post' object has no attribute 'get_absolute_url'

The view tried to redirect to the object's URL, but the model never defined that method.

✅ Fix: add a get_absolute_url() method that returns a reverse(...) URL on the model.

📋 Quick Reference

Generic ViewWhat It Does
ListViewShow many objects (default context: object_list)
DetailViewShow one object found by pk or slug
CreateViewForm to add a new record
UpdateViewForm to edit an existing record
DeleteViewConfirm and remove a record (needs success_url)

🏆 Mini Challenge: Default Template Names

Write a helper that produces the default template path Django would pick for a given app, model, and view suffix — for example blog/post_list.html.

# 🏆 Mini Challenge: build the default template name a generic view uses.
# Django's rule: <app>/<model lowercased>_<suffix>.html

def default_template(app, model, suffix):
    return f"{app}/{model.lower()}_{suffix}.html"

print(default_template("blog", "Post", "list"))           # ListView
print(default_template("blog", "Post", "detail"))         # DetailView
print(default_template("blog", "Post", "form"))           # Create/Update
print(default_template("blog", "Post", "confirm_delete")) # DeleteView

# EXPECTED:
# blog/post_list.html
# blog/post_detail.html
# blog/post_form.html
# blog/post_confirm_delete.html

❓ Frequently Asked Questions

Lesson complete — you can build full pages with a few attributes!

You now know how ListView and DetailView display records, how CreateView, UpdateView, and DeleteView handle forms, what default templates and context names Django generates, and how to customise it all with get_queryset() and get_context_data().

Practice quiz

Which generic view displays a list of objects?

  • DetailView
  • ListView
  • CreateView
  • FormView

Answer: ListView. ListView fetches and renders a list of model instances.

Which generic view shows a single object's detail page?

  • DetailView
  • ListView
  • UpdateView
  • TemplateView

Answer: DetailView. DetailView fetches one object by its pk or slug and renders its detail template.

Which attribute tells a generic view which model to use?

  • queryset_class
  • table
  • model
  • object_name

Answer: model. You set the model attribute (or override get_queryset) so the view knows what to fetch.

Which generic view handles creating a new object via a form?

  • DeleteView
  • DetailView
  • ListView
  • CreateView

Answer: CreateView. CreateView renders a ModelForm and saves a new instance on valid POST.

What default template does a ListView for a Post model in app blog look for?

  • blog/post_list.html
  • blog/list.html
  • post/list.html
  • blog/posts.html

Answer: blog/post_list.html. ListView builds the default name app/model_list.html, i.e. blog/post_list.html.

Which method do you override to filter which objects a ListView shows?

  • get_objects()
  • get_queryset()
  • filter_objects()
  • get_list()

Answer: get_queryset(). Override get_queryset() and return the filtered or ordered queryset.

Which method adds extra data to a generic view's template context?

  • add_context()
  • extra_context_data()
  • get_context_data()
  • context()

Answer: get_context_data(). Override get_context_data(), call super(), add keys, and return the context.

Which template do CreateView and UpdateView share by default for a Post?

  • blog/post_detail.html
  • blog/post_list.html
  • blog/post_edit.html
  • blog/post_form.html

Answer: blog/post_form.html. Both use app/model_form.html, i.e. blog/post_form.html.

Which generic view confirms and deletes an object?

  • DeleteView
  • RemoveView
  • DetailView
  • ListView

Answer: DeleteView. DeleteView shows a confirmation page (model_confirm_delete.html) and deletes on POST.

When is a function view a better choice than a generic view?

  • For a standard object list page
  • For a basic create form
  • When logic is unusual or doesn't map cleanly to one model
  • Always

Answer: When logic is unusual or doesn't map cleanly to one model. Generic views shine for standard patterns; function views are clearer for unusual logic.

Continue this course

Frequently asked questions

What are generic class-based views in Django?

Generic class-based views are ready-made views Django provides for the most common web patterns: listing records, showing one record's detail, and creating, updating, or deleting records. Instead of writing the request-handling logic by hand, you subclass a view like ListView or CreateView and set a few attributes such as the model and template name. Django fills in the rest — fetching objects, rendering the template, and handling form submission — so a full working page can take just a handful of lines.

When should I use a generic view instead of a function view?

Reach for a generic view when your page follows a standard pattern: a list of objects, a single object's detail, or a basic create, update, or delete form. These cases are where generic views save the most code. When your logic is unusual or doesn't map cleanly onto one model, a plain function view (or a custom class-based view) can be clearer. Many projects mix both, using generic views for the routine pages and function views for the special cases.

What templates do generic views look for by default?

Each generic view builds a default template name from your app name and model. A ListView for a Post model in an app called blog looks for blog/post_list.html, and the matching DetailView looks for blog/post_detail.html. CreateView and UpdateView share blog/post_form.html, and DeleteView looks for blog/post_confirm_delete.html. You can always override this by setting template_name explicitly on the view.

How do I change which objects a generic view shows?

Override the get_queryset() method on the view and return the queryset you want. This is how you filter a ListView to show only published posts, or order results a certain way. To pass extra data to the template beyond the objects themselves, override get_context_data(), call super() to get the default context, add your own keys, and return it.