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:
from django.http import HttpResponse
def my_view(request):
if request.method == "GET":
# <view logic>
return HttpResponse("result")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.
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).
Incoming Request ➔ URLconf ➔ as_view() ➔ setup() ➔ dispatch() ➔ get() / post() ➔ HttpResponseBase 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.
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.
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 contextRedirectView
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.
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.
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.
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 templateEditing 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.
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.
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.
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.
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 Class | Primary Purpose |
|---|---|
ArchiveIndexView | A top-level page showing the most recent items sorted by date |
YearArchiveView | Displays all items published in a specific year |
MonthArchiveView | Displays all items from a specific year and month |
WeekArchiveView | Displays all items from a specific year and week |
DayArchiveView | Displays all items published on an exact day |
TodayArchiveView | Displays all items published on the current day |
DateDetailView | Shows 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.
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 templateMixins
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.
# 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 = ArticleEssential 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.
| Mixin | Purpose |
|---|---|
LoginRequiredMixin | Sends logged-out users to your login page automatically. |
PermissionRequiredMixin | Checks if a user has specific permissions set in your application. |
UserPassesTestMixin | Lets you write a custom function (test_func) that must return True to allow access. |
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.userContext & Rendering Mixins
Found in django.views.generic.base, these handle data and templates.
| Mixin | Purpose |
|---|---|
ContextMixin | Lets you pass extra data to your templates using the extra_context attribute or the get_context_data method. |
TemplateResponseMixin | Manages the HTML rendering process using your defined template_name. |
Data Retrieval Mixins
These mixins fetch data from your database.
| Mixin | Purpose |
|---|---|
SingleObjectMixin | Gets a single item using an ID or slug from the URL. |
MultipleObjectMixin | Grabs a list of items and handles splitting them across multiple pages. |
Form Processing Mixins
Located in django.views.generic.edit, these handle form submissions.
| Mixin | Purpose |
|---|---|
FormMixin | Takes care of validating data and redirecting the user upon success. |
ModelFormMixin | Automatically saves validated data directly to your database. |
DeletionMixin | Provides 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.
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 likeget_context_data(), callingsuper()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.
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.
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.
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
| Task | Recommended View | Key Settings and Methods |
|---|---|---|
| Render a static template | TemplateView | template_name, extra_context |
| Redirect to another URL | RedirectView | url, pattern_name, permanent |
| Display a list of database records | ListView | model, queryset, paginate_by |
| Display details for one object | DetailView | model, pk_url_kwarg, slug_url_kwarg |
| Render and process a custom form | FormView | form_class, success_url, form_valid() |
| Create a new database record | CreateView | model, fields, form_valid() |
| Update an existing record | UpdateView | model, fields, get_object() |
| Delete a database record | DeleteView | model, success_url |
| Filter content by date | MonthArchiveView (and others) | date_field, make_object_list |
