Generic class-based views

Django · Practical · 14 min

Generic views implement the common CRUD patterns so your code only describes what's different.

Class-based views (CBVs) are classes whose as_view() produces a view function: path("", PostListView.as_view(), name="list").

Django's generic views cover common pages:

  • TemplateView: render a template
  • ListView: a list of objects with pagination (paginate_by = 20); context has object_list, post_list and page_obj
  • DetailView: one object by pk or slug from the URL; 404 if missing
  • CreateView, UpdateView: a ModelForm; on success redirect to success_url or the object's get_absolute_url()
  • DeleteView: confirm on GET, delete on POST
  • FormView, RedirectView

Default templates follow app/model_suffix.html, such as blog/post_list.html, blog/post_detail.html and blog/post_form.html.

Customize by setting attributes or overriding methods:

  • get_queryset(): restrict the objects, for example only the user's own
  • get_context_data(**kwargs): add extra context; call super() first
  • form_valid(form): act before saving, such as setting the author

Behaviors come from mixins: LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin. Put them leftmost in the bases so their dispatch runs first.

Use reverse_lazy for success_url class attributes, since URLs aren't loaded when the class body runs.

CBVs shine for standard CRUD; for unusual flows, a function view is often clearer.

Example

# blog/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.views.generic import CreateView, DeleteView, DetailView, ListView
from .models import Post

class PostListView(ListView):
    model = Post
    paginate_by = 20                              # template: blog/post_list.html
    def get_queryset(self):
        return Post.objects.published().select_related("author")

class PostDetailView(DetailView):
    model = Post                                  # URL: <slug:slug>/ -> lookup by slug
class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ["title", "slug", "body"]            # template: blog/post_form.html
    def form_valid(self, form):
        form.instance.author = self.request.user.author
        return super().form_valid(form)
class PostDeleteView(LoginRequiredMixin, DeleteView):
    model = Post
    success_url = reverse_lazy("blog:list")
    def get_queryset(self):                       # only your own posts
        return Post.objects.filter(author__user=self.request.user)

Remember

  • ListView/DetailView/CreateView/UpdateView/DeleteView cover CRUD
  • Override get_queryset, get_context_data and form_valid to customize
  • Mixins like LoginRequiredMixin go leftmost
  • Use reverse_lazy for class-level URLs

Watch out for

  • Putting LoginRequiredMixin after the generic view in the bases, so the check doesn't run first.

Check yourself

  1. Why is success_url = reverse_lazy("blog:list") used instead of reverse() on a class attribute?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.djangoproject.com/en/6.0/topics/class-based-views/generic-display/