Skip to content

Class-Based Views

Class-Based Views (CBVs) provide a way to implement views as Python objects instead of functions. This approach offers a few key advantages over traditional function-based views:

  • They allow you to organize code related to specific HTTP methods into separate class methods, instead of conditional branching inside a single function.
  • You can also use object-oriented features like mixins (multiple inheritance), to create reusable code components.

Usage

A class-based view allows you to respond to different HTTP requests with different methods. instead of conditionally branching code inside a single view function.

Consider the following example:

python
from django.http import HttpResponse

def my_view(request):
    if request.method == "GET":
        # <view logic>
        return HttpResponse("result")
python
from django.http import HttpResponse
from django.views import View

class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse("result")

Usage in URLconf

Django's URL router expects a function to handle requests, not a class. To use a class-based view in your routing file, you call its as_view() method. This method generates the function that Django needs.

The as_view() method also accepts arguments, which will override the class attributes. Alternatively, you can override the class attributes by subclassing it.

python
from django.urls import path
from django.views.generic import TemplateView
from .views import MyView

urlpatterns = [
    # Basic usage
    path("my-view/", MyView.as_view(), name="my-view"),
    
    # Changing class settings directly in the URL file
    path("about/", TemplateView.as_view(template_name="about.html"), name="about"),
]

How CBVs Work Under the Hood

When a request hits a URL mapped to MyView.as_view(), a specific sequence of events happens to process it:

  • as_view() returns the callable function to handle the incoming request.
  • The function creates a brand new instance of your view class just for that specific request.
  • The setup() method runs to save the request object and any URL variables so your entire class can access them.
  • The dispatch() method checks the HTTP request type (like GET or POST) and routes the request to the matching method in your class.
  • If the requested method does not exist in your class, the view raises a HttpResponseNotAllowed response (405 status code).
text
Incoming Request ➔ URLconf ➔ as_view() ➔ setup() ➔ dispatch() ➔ get() / post() ➔ HttpResponse

Base Views

These base views live in django.views.generic.base. They serve as the foundation for all other class-based views in Django.

View

This is the core parent class for all class-based views. Use View when you need complete control over how to handle specific HTTP requests without any built-in template features.

python
from django.views import View
from django.http import HttpResponse

class SimpleView(View):
    def get(self, request, *args, **kwargs):
        return HttpResponse("Handled GET request")

    def post(self, request, *args, **kwargs):
        return HttpResponse("Handled POST request")

TemplateView

This view renders a specific HTML template and passes data to it. You define the template using the template_name attribute and provide static data using extra_context. To pass dynamic data, you override the get_context_data method.

python
from django.views.generic import TemplateView

class HomePageView(TemplateView):
    template_name = "home.html"
    extra_context = {"title": "Welcome Home"}

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["latest_item"] = "Django 6.0 Guide"
        return context

RedirectView

This view redirects the user to a different URL or named URL pattern. You set the destination using the url or pattern_name attributes. You can also make the redirect permanent by setting permanent = True or forward URL parameters by setting query_string = True. If you need to calculate the destination dynamically, override the get_redirect_url method.

python
from django.views.generic import RedirectView

class GitHubRedirectView(RedirectView):
    url = "https://github.com"
    permanent = True

class ProfileRedirectView(RedirectView):
    pattern_name = "user-profile" # Redirects to URL pattern by name
    query_string = True          # Forwards GET query parameters (?ref=123)

Generic Views

These views live under django.views.generic. They were developed to handle common and repetitive tasks.

Display Views

These views handle retrieving and displaying data from your models.

ListView

The ListView displays a list of objects from your database and easily handles pagination. You define the data source using the model or queryset attributes. By default, Django sends the data to your template using the name <model>_list, but you can change this by setting context_object_name. You can also configure the view using attributes like template_name, paginate_by to limit items per page, and ordering to sort the results.

If you need to filter the database query dynamically, you can override the get_queryset() method. To pass extra data to your template, override the get_context_data() method.

python
from django.views.generic import ListView
from .models import Article

class ArticleListView(ListView):
    model = Article
    template_name = "article_list.html"
    context_object_name = "articles"  # Replaces 'object_list' in the template
    paginate_by = 10
    ordering = ["-published_at"]

    def get_queryset(self):
        # Override to filter the query dynamically
        return Article.objects.filter(is_published=True)

DetailView

The DetailView shows the details of a single database object. It automatically finds this object using a primary key (pk) or slug provided in your URL.

Similar to the list view, you set the model or queryset and can define a custom template_name. The object is passed to the template as <model> by default, which you can rename using context_object_name. You can also customize the expected URL variables by changing the pk_url_kwarg or slug_url_kwarg attributes. If you need complex logic to retrieve the item, you can override the get_object() method.

python
from django.views.generic import DetailView
from .models import Article

class ArticleDetailView(DetailView):
    model = Article
    template_name = "article_detail.html"
    context_object_name = "article"  # Replaces 'object' in the template

Editing Views

These views live in django.views.generic.edit and make it easy to render forms, check for input errors, and object creation/updating/deletion. Forms are covered in detail later, but for now, simply know that they are used to securely collect and process information typed by your users.

FormView

The FormView helps you collect information from a user, such as a message on a contact page. You tell the view what kind of input to expect using the form_class attribute. You also set the template_name to display the page and a success_url to redirect the user to after they submit their data. You can override the form_valid() method to define what happens when the input is correct, like sending an email.

python
from django.views.generic.edit import FormView
from django.urls import reverse_lazy
from .forms import ContactForm

class ContactFormView(FormView):
    form_class = ContactForm
    template_name = "contact.html"
    success_url = reverse_lazy("contact-success")

    def form_valid(self, form):
        form.send_email()
        return super().form_valid(form)

CreateView

The CreateView automatically generates the user interface needed to add a new item to your database. You set the model and list the specific fields the user can fill out. Once the user submits the data, Django saves it and redirects them to your success_url. You can override the form_valid() method if you need to add extra information behind the scenes, like linking the new item to the currently logged-in user.

python
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
from .models import Article

class ArticleCreateView(CreateView):
    model = Article
    fields = ["title", "body", "category"]
    template_name = "article_form.html"
    success_url = reverse_lazy("article-list")

    def form_valid(self, form):
        form.instance.author = self.request.user  # Attach current logged-in user
        return super().form_valid(form)

UpdateView

The UpdateView lets users modify an existing item in your database. It works almost exactly like the create view, but it automatically populates the page with the current information from the database so the user can edit it.

python
from django.views.generic.edit import UpdateView
from django.urls import reverse_lazy
from .models import Article

class ArticleUpdateView(UpdateView):
    model = Article
    fields = ["title", "body"]
    template_name = "article_form.html"
    success_url = reverse_lazy("article-list")

DeleteView

The DeleteView safely removes an item from your database. It usually displays a simple confirmation page first. Once the user confirms the action, the view deletes the item and redirects the user to the success_url.

python
from django.views.generic.edit import DeleteView
from django.urls import reverse_lazy
from .models import Article

class ArticleDeleteView(DeleteView):
    model = Article
    template_name = "article_confirm_delete.html" # Default template convention
    success_url = reverse_lazy("article-list")

Date Views

These views live in django.views.generic.dates. They are incredibly useful for blogs, news sites, or any application where you want users to browse content chronologically. Instead of writing custom code to filter your database by a specific year, month, or day, these views handle the filtering automatically based on the date information provided in the URL.

View ClassPrimary Purpose
ArchiveIndexViewA top-level page showing the most recent items sorted by date
YearArchiveViewDisplays all items published in a specific year
MonthArchiveViewDisplays all items from a specific year and month
WeekArchiveViewDisplays all items from a specific year and week
DayArchiveViewDisplays all items published on an exact day
TodayArchiveViewDisplays all items published on the current day
DateDetailViewShows a single item found using its exact publish date and a unique ID or slug

To use these views, you specify which database model to query and tell the view which date field to check. Django then automatically matches the date from the web address to your database records.

By default, some date views are designed for speed and only fetch the dates that have content, rather than retrieving the actual database items. If you want to display the full list of items on your page, you can set make_object_list = True. This tells Django to fetch the actual records for that time period and pass them directly to your HTML template.

python
from django.views.generic.dates import MonthArchiveView
from .models import Article

class ArticleMonthArchiveView(MonthArchiveView):
    queryset = Article.objects.all()
    date_field = "published_at"
    make_object_list = True  # Includes the filtered list of articles in the template

Mixins

A Mixin is a special type of class that lets you share reusable features across multiple views. By using Python's multiple inheritance, mixins allow you to add features like user authentication or custom template data without rewriting the same code multiple times.

How Inheritance Order Works

In Python, parent classes are read from left to right.

Inheritance Order Rule: You must list your mixin classes before the main view class in your code. If the main view comes first, its methods will run immediately and skip the mixin entirely.

python
# CORRECT: Mixins execute before TemplateView / ListView
class ArticleListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
    permission_required = "articles.view_article"
    model = Article

# INCORRECT: ListView catches dispatch first, ignoring LoginRequiredMixin!
class ArticleListView(ListView, LoginRequiredMixin):
    model = Article

Essential Built-in Django Mixins

Django provides several built-in mixins grouped by their purpose.

Authentication & Security Mixins

These mixins live in django.contrib.auth.mixins and help secure your views.

MixinPurpose
LoginRequiredMixinSends logged-out users to your login page automatically.
PermissionRequiredMixinChecks if a user has specific permissions set in your application.
UserPassesTestMixinLets you write a custom function (test_func) that must return True to allow access.
python
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import UpdateView
from .models import Article

class ArticleUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Article
    fields = ["title", "content"]

    def test_func(self):
        # Only allow the article's author to edit
        return self.get_object().author == self.request.user

Context & Rendering Mixins

Found in django.views.generic.base, these handle data and templates.

MixinPurpose
ContextMixinLets you pass extra data to your templates using the extra_context attribute or the get_context_data method.
TemplateResponseMixinManages the HTML rendering process using your defined template_name.

Data Retrieval Mixins

These mixins fetch data from your database.

MixinPurpose
SingleObjectMixinGets a single item using an ID or slug from the URL.
MultipleObjectMixinGrabs a list of items and handles splitting them across multiple pages.

Form Processing Mixins

Located in django.views.generic.edit, these handle form submissions.

MixinPurpose
FormMixinTakes care of validating data and redirecting the user upon success.
ModelFormMixinAutomatically saves validated data directly to your database.
DeletionMixinProvides the logic needed to safely delete a database record.

Writing Custom Mixins

You can also create your own mixins to share custom logic across your project.

python
from django.core.exceptions import PermissionDenied

class PageTitleMixin:
    """Injects a page_title attribute into the template context."""
    page_title = ""

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["page_title"] = self.page_title
        return context

class OwnerRequiredMixin:
    """Ensures that only the owner of the object can view or modify it."""
    owner_field = "author"

    def get_object(self, queryset=None):
        obj = super().get_object(queryset)
        if getattr(obj, self.owner_field) != self.request.user:
            raise PermissionDenied("You are not authorized to edit this item.")
        return obj

# Usage of custom mixins
class ArticleUpdateView(LoginRequiredMixin, OwnerRequiredMixin, PageTitleMixin, UpdateView):
    model = Article
    fields = ["title", "content"]
    page_title = "Edit Article"
    owner_field = "author"

Best Practices for Mixins

When working with mixins, there are a few best practices to follow:

  • Always call super(): When overriding methods like get_context_data(), calling super() ensures all classes in the inheritance chain get a chance to run.
  • Keep it focused: Each mixin should handle a single responsibility rather than trying to do too much at once.
  • Use unique names: Give your attributes clear and unique names to prevent them from accidentally overwriting settings in other mixins.

Decorating Class-Based Views

Since class-based views use methods instead of standard functions, you cannot apply regular view decorators like @login_required directly to the class. Instead, you must use one of two approaches.

Method 1: Decorating in URLs (urls.py)

You can apply the decorator directly to the view function generated by as_view() in your routing file.

python
from django.urls import path
from django.contrib.auth.decorators import login_required
from .views import ProtectedView

urlpatterns = [
    path("protected/", login_required(ProtectedView.as_view()), name="protected"),
]

Method 2: Using @method_decorator

You can use method_decorator to convert a standard view decorator into one that works on class methods. You can place this decorator directly above the method definition or above the class itself while specifying the target method name.

When you need to apply multiple decorators, you can group them into a list or tuple, and pass it to method_decorator. Django will process a request using these decorators in the exact order they appear.

python
from django.views import View
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.views.decorators.cache import never_cache, cache_page
from django.http import HttpResponse

decorators = [never_cache, login_required]

# Approach A: Decorating the class and targeting a method by name
@method_decorator(decorators, name="dispatch")
class ProtectedView(View):
    
    @method_decorator(cache_page(60 * 15))
    def get(self, request, *args, **kwargs):
        return HttpResponse("Cached and protected content")

# Approach B: Decorating the method directly
class AnotherProtectedView(View):
    
    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

NOTE

method_decorator passes *args and **kwargs to the decorated method. If your method signature does not accept these parameters, Django will raise a TypeError exception.

Asynchronous Class-Based Views

Django allows you to write asynchronous methods in your class-based views. The as_view() method automatically detects if your request handlers (like get or post) use the async def syntax and processes them accordingly.

python
import asyncio
from django.views import View
from django.http import HttpResponse
from .models import Article

class AsyncArticleView(View):
    async def get(self, request, *args, **kwargs):
        # Perform async operations or async database queries
        count = await Article.objects.acount()
        await asyncio.sleep(0.1)
        return HttpResponse(f"Total articles: {count}")

NOTE

Every incoming request creates a new instance of your view class. It is safe to set instance variables inside setup() or dispatch(). However, you should avoid modifying class-level variables to prevent data from leaking between different user requests.

Quick Reference

TaskRecommended ViewKey Settings and Methods
Render a static templateTemplateViewtemplate_name, extra_context
Redirect to another URLRedirectViewurl, pattern_name, permanent
Display a list of database recordsListViewmodel, queryset, paginate_by
Display details for one objectDetailViewmodel, pk_url_kwarg, slug_url_kwarg
Render and process a custom formFormViewform_class, success_url, form_valid()
Create a new database recordCreateViewmodel, fields, form_valid()
Update an existing recordUpdateViewmodel, fields, get_object()
Delete a database recordDeleteViewmodel, success_url
Filter content by dateMonthArchiveView (and others)date_field, make_object_list