Introduction

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.

What You Will Learn

By the end of this tutorial, you will understand:

Prerequisites

Before starting this tutorial, you should already know:

1. What Are Class-Based Views?

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:

2. Why Use Class-Based Views?

Class-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.

3. Function-Based Views vs Class-Based Views

Function-Based View Example

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})

Class-Based View Example

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.

When FBVs feel simpler

Function-based views are often easier for:

When CBVs are better

Class-based views are often better for:

4. Your First Basic Class-Based View

Let us create a simple CBV manually.

In views.py

from 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!")

In urls.py

from django.urls import path
from .views import HelloView

urlpatterns = [
    path('hello/', HelloView.as_view(), name='hello'),
]

Important Note: as_view()

When using a Class-Based View in urls.py, you must write:

HelloView.as_view()

and not just:

HelloView

as_view() converts the class into a callable view that Django can use.

5. Handling Different HTTP Methods

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.

6. Using TemplateView

TemplateView is one of the simplest generic CBVs. It is used when you want to display a template page.

Example

In views.py

from django.views.generic import TemplateView

class AboutView(TemplateView):
    template_name = 'about.html'

In urls.py

from django.urls import path
from .views import AboutView

urlpatterns = [
    path('about/', AboutView.as_view(), name='about'),
]

In 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.

7. Passing Context Data in TemplateView

Sometimes you want to send extra data to the template.

You can do that by overriding get_context_data().

Example

In views.py

from 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 context

In about.html

<h1>About {{ site_name }}</h1>
<p>Created by {{ author }}</p>

Why super()?

super().get_context_data(**kwargs) keeps the default context from Django, then you add your own variables.

8. Using ListView

ListView is used to display a list of objects from the database.

This is very common for:

Let us suppose we have this model:

In models.py

from 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.title

Basic ListView

In views.py

 

from django.views.generic import ListView
from .models import Post

class PostListView(ListView):
    model = Post

 

By default, Django will:

9. Customizing ListView

Usually, 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']

What these mean

10. Template for ListView

In templates/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 %}

11. Filtering Data in ListView

You can customize the queryset by overriding get_queryset().

Example: Only latest 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'

    def get_queryset(self):
        return Post.objects.order_by('-created_at')

Example: Search by title

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.

12. Using DetailView

DetailView is used to display one single object.

For example:

Example

In views.py

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'

In urls.py

from 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.

13. Template for DetailView

In templates/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.

14. Using Slugs Instead of Primary Keys

Sometimes you want cleaner URLs like:

/posts/my-first-django-post/

instead of:

/posts/1/

You can do this with a slug field.

Update the model

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.title

Update the view

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'
    slug_field = 'slug'
    slug_url_kwarg = 'slug'

Update the URL

path('posts/<slug:slug>/', PostDetailView.as_view(), name='post_detail'),

Now the detail page becomes more readable and SEO-friendly.

15. Adding Extra Context in DetailView

You 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 context

Now your template can display related posts too.

16. Full Example: Blog with ListView and DetailView

models.py

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.title

views.py

from 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.py

from 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>

17. Common Attributes in Generic CBVs

Here are some very common attributes you will often use:

In ListView

In DetailView

These attributes save time and reduce code.

18. Common Methods in Class-Based Views

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.

19. Advantages of Class-Based Views

CBVs offer many benefits:

Better code organization

Each view is structured inside a class.

Code reuse

You can inherit from built-in generic views or from your own custom base views.

Less repetitive code

Generic views remove a lot of boilerplate.

Easier extension

You can override only the parts you need.

Scales better

For medium and large projects, CBVs help keep code cleaner.

20. Disadvantages of Class-Based Views

CBVs are powerful, but they also have a few drawbacks:

Harder for beginners at first

They may look more complex than functions.

Inheritance can be confusing

Sometimes it is not obvious where behavior comes from.

Too much abstraction

For very simple cases, FBVs may feel easier to read.

21. When Should You Use CBVs?

Use CBVs when:

Use FBVs when:

In real Django projects, many developers use both depending on the situation.

22. Common Beginner Mistakes with CBVs

Forgetting as_view()

Wrong:

path('about/', AboutView, name='about')

Correct:

path('about/', AboutView.as_view(), name='about')

Forgetting to set template_name

Django may look for a default template path that does not exist.

Using wrong context variable names

If you set:

context_object_name = 'posts'

then in the template use posts, not object_list.

Confusing pk and slug

Be sure your URL pattern matches what the view expects.

23. Practical Mini Project

Let us build a tiny article system with CBVs.

Goal

Create:

Model

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.title

Views

from 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'

URLs

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'),
]

Template: 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 %}

Template: 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.

24. Summary

In this tutorial, you learned that:

25. Final Code Comparison

Function-Based View

def post_list(request):
    posts = Post.objects.all()
    return render(request, 'blog/post_list.html', {'posts': posts})

Class-Based View

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.

 

26. Mini Quiz

1. What does CBV stand for?

A. Class Built View
B. Class-Based View
C. Custom Browser View
D. Class Backend View

2. What method is required in urls.py when using a CBV?

A. render()
B. get()
C. as_view()
D. dispatch()

3. Which generic view is best for displaying one object?

A. ListView
B. DetailView
C. TemplateView
D. FormView

4. Which generic view is best for displaying many objects?

A. DetailView
B. DeleteView
C. ListView
D. RedirectView

5. Which method is used to add extra context data?

A. get_object()
B. get_context_data()
C. post()
D. dispatch()

27. What Comes Next?

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.