As your Django projects grow, writing everything with function-based views can become repetitive. Many pages follow common patterns such as displaying a template, listing objects, showing details, creating data, updating data, or deleting data. Django solves this by offering Class-Based Views (CBVs).
Class-Based Views help you write cleaner, more reusable, and more organized code. Instead of defining a view as a simple function, you define it as a class. Django then uses built-in methods to handle requests in a structured way.
In this tutorial, you will learn what Class-Based Views are, why they are useful, how they work, and how to use some of the most common ones in real Django projects.
By the end of this tutorial, you will understand:
TemplateViewListViewDetailViewBefore starting this tutorial, you should already know:
In Django, a view is responsible for receiving a request and returning a response.
Until now, you have probably used function-based views, like this:
from django.http import HttpResponse
def home(request):
return HttpResponse("Hello from a function-based view!")A Class-Based View does the same job, but with a class:
from django.views import View
from django.http import HttpResponse
class HomeView(View):
def get(self, request):
return HttpResponse("Hello from a class-based view!")Here:
HomeView is a classViewget() method handles GET requestsClass-Based Views are useful because they:
For example, instead of writing the same logic again and again for listing database objects, Django gives you ListView.
Instead of manually loading an object by ID and rendering its detail page, Django gives you DetailView.
from django.shortcuts import render
from .models import Post
def post_list(request):
posts = Post.objects.all()
return render(request, 'blog/post_list.html', {'posts': posts})from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'Both do a similar job, but the CBV version is shorter and more structured.
Function-based views are often easier for:
Class-based views are often better for:
Let us create a simple CBV manually.
views.pyfrom django.views import View
from django.http import HttpResponse
class HelloView(View):
def get(self, request):
return HttpResponse("Hello, this is a basic Class-Based View!")urls.pyfrom django.urls import path
from .views import HelloView
urlpatterns = [
path('hello/', HelloView.as_view(), name='hello'),
]as_view()When using a Class-Based View in urls.py, you must write:
HelloView.as_view()and not just:
HelloViewas_view() converts the class into a callable view that Django can use.
One big advantage of CBVs is that you can separate logic by HTTP method.
from django.views import View
from django.http import HttpResponse
class ContactView(View):
def get(self, request):
return HttpResponse("Display the contact form")
def post(self, request):
return HttpResponse("Process the submitted form")This is much cleaner than putting all GET and POST logic inside one function.
TemplateViewTemplateView is one of the simplest generic CBVs. It is used when you want to display a template page.
views.pyfrom django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = 'about.html'urls.pyfrom django.urls import path
from .views import AboutView
urlpatterns = [
path('about/', AboutView.as_view(), name='about'),
]templates/about.html<h1>About Us</h1>
<p>Welcome to our Django website.</p>This is perfect for pages like:
when no complex backend logic is needed.
TemplateViewSometimes you want to send extra data to the template.
You can do that by overriding get_context_data().
views.pyfrom django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = 'about.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['site_name'] = 'My Django Blog'
context['author'] = 'Youssef'
return contextabout.html<h1>About {{ site_name }}</h1>
<p>Created by {{ author }}</p>super()?super().get_context_data(**kwargs) keeps the default context from Django, then you add your own variables.
ListViewListView is used to display a list of objects from the database.
This is very common for:
Let us suppose we have this model:
models.pyfrom django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.titleListViewviews.py
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
By default, Django will:
Postapp_name/post_list.htmlListViewUsually, you want more control.
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
ordering = ['-created_at']model = Post → tells Django which model to usetemplate_name → tells Django which template to rendercontext_object_name = 'posts' → the template variable becomes postsordering = ['-created_at'] → shows newest posts firstListViewtemplates/blog/post_list.html<h1>All Posts</h1>
{% for post in posts %}
<h2>{{ post.title }}</h2>
<p>{{ post.content|truncatewords:20 }}</p>
<hr>
{% empty %}
<p>No posts found.</p>
{% endfor %}
ListViewYou can customize the queryset by overriding get_queryset().
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
def get_queryset(self):
return Post.objects.order_by('-created_at')from django.views.generic import ListView
from .models import Post
class PostSearchView(ListView):
model = Post
template_name = 'blog/post_search.html'
context_object_name = 'posts'
def get_queryset(self):
query = self.request.GET.get('q')
if query:
return Post.objects.filter(title__icontains=query)
return Post.objects.all()This is a powerful feature because it lets you adapt the data based on user input.
DetailViewDetailView is used to display one single object.
For example:
views.pyfrom django.views.generic import DetailView
from .models import Post
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
urls.pyfrom django.urls import path
from .views import PostListView, PostDetailView
urlpatterns = [
path('posts/', PostListView.as_view(), name='post_list'),
path('posts/<int:pk>/', PostDetailView.as_view(), name='post_detail'),
]
Here, <int:pk> passes the primary key of the post to the view.
DetailViewtemplates/blog/post_detail.html<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<p>Published on: {{ post.created_at }}</p>When the user visits:
/posts/1/Django finds the Post with pk=1 and sends it to the template.
Sometimes you want cleaner URLs like:
/posts/my-first-django-post/instead of:
/posts/1/You can do this with a slug field.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.titlefrom django.views.generic import DetailView
from .models import Post
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
slug_field = 'slug'
slug_url_kwarg = 'slug'path('posts/<slug:slug>/', PostDetailView.as_view(), name='post_detail'),Now the detail page becomes more readable and SEO-friendly.
DetailViewYou can also override get_context_data() in a DetailView.
from django.views.generic import DetailView
from .models import Post
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['related_posts'] = Post.objects.exclude(id=self.object.id)[:3]
return contextNow your template can display related posts too.
ListView and DetailViewmodels.pyfrom django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.titleviews.pyfrom django.views.generic import ListView, DetailView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
ordering = ['-created_at']
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
slug_field = 'slug'
slug_url_kwarg = 'slug'urls.pyfrom django.urls import path
from .views import PostListView, PostDetailView
urlpatterns = [
path('posts/', PostListView.as_view(), name='post_list'),
path('posts/<slug:slug>/', PostDetailView.as_view(), name='post_detail'),
]post_list.html<h1>Blog Posts</h1>
{% for post in posts %}
<h2>
<a href="{% url 'post_detail' post.slug %}">{{ post.title }}</a>
</h2>
<p>{{ post.content|truncatewords:25 }}</p>
<hr>
{% empty %}
<p>No posts available.</p>
{% endfor %}post_detail.html<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<p><small>Published on {{ post.created_at }}</small></p>
<p>
<a href="{% url 'post_list' %}">Back to all posts</a>
</p>Here are some very common attributes you will often use:
ListViewmodeltemplate_namecontext_object_nameorderingpaginate_byDetailViewmodeltemplate_namecontext_object_nameslug_fieldslug_url_kwargThese attributes save time and reduce code.
get_context_data()Used to add extra variables to the template context.
get_queryset()Used mainly in ListView to customize which objects are returned.
get_object()Used in DetailView when you want to customize how one object is retrieved.
Example:
def get_object(self):
return Post.objects.get(slug=self.kwargs['slug'])Usually, Django handles this automatically, but it is good to know.
CBVs offer many benefits:
Each view is structured inside a class.
You can inherit from built-in generic views or from your own custom base views.
Generic views remove a lot of boilerplate.
You can override only the parts you need.
For medium and large projects, CBVs help keep code cleaner.
CBVs are powerful, but they also have a few drawbacks:
They may look more complex than functions.
Sometimes it is not obvious where behavior comes from.
For very simple cases, FBVs may feel easier to read.
Use CBVs when:
Use FBVs when:
In real Django projects, many developers use both depending on the situation.
as_view()Wrong:
path('about/', AboutView, name='about')Correct:
path('about/', AboutView.as_view(), name='about')template_nameDjango may look for a default template path that does not exist.
If you set:
context_object_name = 'posts'then in the template use posts, not object_list.
pk and slugBe sure your URL pattern matches what the view expects.
Let us build a tiny article system with CBVs.
Create:
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=150)
slug = models.SlugField(unique=True)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.titlefrom django.views.generic import ListView, DetailView
from .models import Article
class ArticleListView(ListView):
model = Article
template_name = 'articles/article_list.html'
context_object_name = 'articles'
ordering = ['-created_at']
class ArticleDetailView(DetailView):
model = Article
template_name = 'articles/article_detail.html'
context_object_name = 'article'
slug_field = 'slug'
slug_url_kwarg = 'slug'from django.urls import path
from .views import ArticleListView, ArticleDetailView
urlpatterns = [
path('articles/', ArticleListView.as_view(), name='article_list'),
path('articles/<slug:slug>/', ArticleDetailView.as_view(), name='article_detail'),
]
article_list.html<h1>Articles</h1>
{% for article in articles %}
<h2>
<a href="{% url 'article_detail' article.slug %}">
{{ article.title }}
</a>
</h2>
<p>{{ article.body|truncatewords:20 }}</p>
{% empty %}
<p>No articles yet.</p>
{% endfor %}article_detail.html<h1>{{ article.title }}</h1>
<p>{{ article.body }}</p>
<p><a href="{% url 'article_list' %}">Back to articles</a></p>This is a simple but real example of how CBVs save time.
In this tutorial, you learned that:
View is the base class for custom CBVsTemplateView is useful for static pagesListView is used to display lists of objectsDetailView is used to show one objectget_context_data() helps pass extra dataget_queryset() helps customize resultsdef post_list(request):
posts = Post.objects.all()
return render(request, 'blog/post_list.html', {'posts': posts})class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'The CBV version is shorter, cleaner, and easier to extend.
A. Class Built View
B. Class-Based View
C. Custom Browser View
D. Class Backend View
urls.py when using a CBV?A. render()
B. get()
C. as_view()
D. dispatch()
A. ListView
B. DetailView
C. TemplateView
D. FormView
A. DetailView
B. DeleteView
C. ListView
D. RedirectView
A. get_object()
B. get_context_data()
C. post()
D. dispatch()
Now that you understand the basics of Class-Based Views, the perfect next step is:
Tutorial : Generic Class-Based Views
Subject: CreateView, UpdateView, DeleteView, and faster CRUD development.
These views will show you how to create complete database applications with much less code.