In the previous tutorial, you learned the basics of Class-Based Views (CBVs) and saw how they help organize Django code in a cleaner and more reusable way. Now it is time to go one step further with Generic Class-Based Views.
Generic Class-Based Views are pre-built views provided by Django for common tasks such as:
These views save a lot of time because Django already handles much of the repeated logic for you. Instead of writing the same CRUD code again and again, you can use Django’s generic views and customize only what you need.
In this tutorial, you will learn how to use the most important generic views, especially:
ListViewDetailViewCreateViewUpdateViewDeleteViewBy the end, you will be able to build a complete CRUD application with much less code.
In this tutorial, you will learn:
ListViewDetailViewCreateViewUpdateViewDeleteViewBefore starting, you should already know:
Generic Class-Based Views are built-in Django views that solve very common problems.
For example:
ListView displays multiple objectsDetailView displays one objectCreateView creates a new objectUpdateView edits an existing objectDeleteView deletes an objectInstead of writing all logic manually, Django gives you ready-made classes that already know how to do these jobs.
This is especially useful in CRUD applications.
CRUD means:
Generic views are helpful because they:
Without generic views, you often write the same patterns again and again. With them, many tasks become much shorter.
Throughout this tutorial, we will use a simple Post model.
models.pyfrom django.db import models
from django.urls import reverse
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
def get_absolute_url(self):
return reverse('post_detail', kwargs={'slug': self.slug})title stores the post titleslug creates clean URLscontent stores the body textcreated_at stores the publication dateget_absolute_url() tells Django where to redirect after creating or updating an objectThat last method is very useful with CreateView and UpdateView.
ListView — Display Many ObjectsListView is used when you want to display a list of records.
views.pyfrom 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']Post objectsblog/post_list.htmlpostsurls.pyfrom django.urls import path
from .views import PostListView
urlpatterns = [
path('posts/', PostListView.as_view(), name='post_list'),
]post_list.html<h1>All Posts</h1>
{% for post in posts %}
<h2>
<a href="{% url 'post_detail' post.slug %}">{{ post.title }}</a>
</h2>
<p>{{ post.content|truncatewords:20 }}</p>
<hr>
{% empty %}
<p>No posts available.</p>
{% endfor %}
DetailView — Display One ObjectDetailView is used to show one single object.
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'
slug_field = 'slug'
slug_url_kwarg = 'slug'urls.pyfrom django.urls import path
from .views import PostDetailView
urlpatterns = [
path('posts/<slug:slug>/', PostDetailView.as_view(), name='post_detail'),
]post_detail.html<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<p>Published on: {{ post.created_at }}</p>
<p>
<a href="{% url 'post_update' post.slug %}">Edit</a> |
<a href="{% url 'post_delete' post.slug %}">Delete</a>
</p>When the user visits a URL like:
/posts/my-first-post/Django finds the Post whose slug matches my-first-post and displays it.
CreateView — Create a New ObjectCreateView is used to create new objects through a form.
views.pyfrom django.views.generic import CreateView
from .models import Post
class PostCreateView(CreateView):
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'slug', 'content']model = Post tells Django which model to usetemplate_name tells Django which template to usefields defines which fields appear in the formBecause our model has get_absolute_url(), Django automatically redirects to the post detail page after successful creation.
urls.pyfrom django.urls import path
from .views import PostCreateView
urlpatterns = [
path('posts/create/', PostCreateView.as_view(), name='post_create'),
]post_form.html<h1>Create a New Post</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Save Post</button>
</form>
<p><a href="{% url 'post_list' %}">Back to posts</a></p>UpdateView — Edit an Existing ObjectUpdateView is used to modify an existing object.
views.pyfrom django.views.generic import UpdateView
from .models import Post
class PostUpdateView(UpdateView):
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'slug', 'content']
slug_field = 'slug'
slug_url_kwarg = 'slug'urls.pyfrom django.urls import path
from .views import PostUpdateView
urlpatterns = [
path('posts/<slug:slug>/edit/', PostUpdateView.as_view(), name='post_update'),
]Because we reused post_form.html, one template works for both create and update.
DeleteView — Delete an ObjectDeleteView is used to remove an object, usually after a confirmation page.
views.pyfrom django.urls import reverse_lazy
from django.views.generic import DeleteView
from .models import Post
class PostDeleteView(DeleteView):
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('post_list')
slug_field = 'slug'
slug_url_kwarg = 'slug'reverse_lazy?reverse_lazy() is often used in class-based views because URLs are resolved only when needed.
urls.pyfrom django.urls import path
from .views import PostDeleteView
urlpatterns = [
path('posts/<slug:slug>/delete/', PostDeleteView.as_view(), name='post_delete'),
]post_confirm_delete.html<h1>Delete Post</h1>
<p>Are you sure you want to delete "{{ post.title }}"?</p>
<form method="post">
{% csrf_token %}
<button type="submit">Yes, delete</button>
</form>
<p><a href="{% url 'post_detail' post.slug %}">Cancel</a></p>Now let us put everything together.
views.pyfrom django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
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'
class PostCreateView(CreateView):
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'slug', 'content']
class PostUpdateView(UpdateView):
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'slug', 'content']
slug_field = 'slug'
slug_url_kwarg = 'slug'
class PostDeleteView(DeleteView):
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('post_list')
slug_field = 'slug'
slug_url_kwarg = 'slug'urls.pyfrom django.urls import path
from .views import (
PostListView,
PostDetailView,
PostCreateView,
PostUpdateView,
PostDeleteView,
)
urlpatterns = [
path('posts/', PostListView.as_view(), name='post_list'),
path('posts/create/', PostCreateView.as_view(), name='post_create'),
path('posts/<slug:slug>/', PostDetailView.as_view(), name='post_detail'),
path('posts/<slug:slug>/edit/', PostUpdateView.as_view(), name='post_update'),
path('posts/<slug:slug>/delete/', PostDeleteView.as_view(), name='post_delete'),
]This is a complete CRUD system with generic views.
success_urlSometimes you do not want to use get_absolute_url().
In that case, you can define success_url.
CreateViewfrom django.urls import reverse_lazy
from django.views.generic import CreateView
from .models import Post
class PostCreateView(CreateView):
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'slug', 'content']
success_url = reverse_lazy('post_list')Now, after creating a post, Django redirects to the post list page instead of the detail page.
You can customize the form used in CreateView and UpdateView in two main ways:
fieldsfields = ['title', 'slug', 'content']This is simple and good for beginners.
Example:
forms.pyfrom django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'content']views.pyfrom django.views.generic import CreateView
from .models import Post
from .forms import PostForm
class PostCreateView(CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'Using a custom form is better when you need:
You can still use get_context_data() in generic views.
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page_title'] = 'All Blog Posts'
return contextThen in the template:
<h1>{{ page_title }}</h1>get_queryset()You can customize which objects appear in ListView.
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.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()Now users can search with URLs like:
/posts/?q=djangoDjango has default template naming rules.
For example, if your app is called blog and model is Post, Django expects:
blog/post_list.htmlblog/post_detail.htmlblog/post_form.htmlblog/post_confirm_delete.htmlYou can follow these defaults or set template_name manually.
modelDefines the model the view works with.
template_nameDefines which template to render.
context_object_nameDefines the variable name used in the template.
fieldsDefines which model fields appear in create/update forms.
success_urlDefines where to redirect after success.
slug_fieldDefines which model field is used as slug.
slug_url_kwargDefines the URL parameter name for the slug.
as_view()Wrong:
path('posts/', PostListView, name='post_list')Correct:
path('posts/', PostListView.as_view(), name='post_list')get_absolute_url() or success_urlIf you use CreateView or UpdateView and define neither, Django may not know where to redirect.
If your view expects slug, your URL must also use slug.
Correct:
path('posts/<slug:slug>/', PostDetailView.as_view(), name='post_detail')Always include:
{% csrf_token %}inside POST forms.
reverse() instead of reverse_lazy() in class attributesIn class-based views, reverse_lazy() is usually safer for attributes like success_url.
from django.shortcuts import render, redirect
from .models import Post
from .forms import PostForm
def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.save()
return redirect('post_list')
else:
form = PostForm()
return render(request, 'blog/post_form.html', {'form': form})CreateViewfrom django.views.generic import CreateView
from .models import Post
class PostCreateView(CreateView):
model = Post
fields = ['title', 'slug', 'content']
template_name = 'blog/post_form.html'The generic version is much shorter.
Generic views are ideal when:
They may be less suitable when:
In those cases, function-based views or custom CBVs may be better.
Let us create another example with books.
models.pyfrom django.db import models
from django.urls import reverse
class Book(models.Model):
title = models.CharField(max_length=150)
author = models.CharField(max_length=100)
description = models.TextField()
slug = models.SlugField(unique=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('book_detail', kwargs={'slug': self.slug})
views.pyfrom django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from .models import Book
class BookListView(ListView):
model = Book
template_name = 'books/book_list.html'
context_object_name = 'books'
class BookDetailView(DetailView):
model = Book
template_name = 'books/book_detail.html'
context_object_name = 'book'
slug_field = 'slug'
slug_url_kwarg = 'slug'
class BookCreateView(CreateView):
model = Book
template_name = 'books/book_form.html'
fields = ['title', 'author', 'description', 'slug']
class BookUpdateView(UpdateView):
model = Book
template_name = 'books/book_form.html'
fields = ['title', 'author', 'description', 'slug']
slug_field = 'slug'
slug_url_kwarg = 'slug'
class BookDeleteView(DeleteView):
model = Book
template_name = 'books/book_confirm_delete.html'
success_url = reverse_lazy('book_list')
slug_field = 'slug'
slug_url_kwarg = 'slug'This is the same pattern, just with a different model.
In this tutorial, you learned that:
ListView displays multiple objectsDetailView displays one objectCreateView creates new objectsUpdateView edits existing objectsDeleteView deletes objectsGeneric views are one of Django’s most useful features because they let you build real applications with much less code.
A. ListView
B. UpdateView
C. CreateView
D. DeleteView
A. DetailView
B. UpdateView
C. TemplateView
D. ListView
A. DeleteView
B. RemoveView
C. DestroyView
D. EraseView
A. get_url()
B. redirect_url()
C. get_absolute_url()
D. get_redirect_path()
DeleteView for redirecting after deletion?A. get_absolute_url()
B. reverse_lazy()
C. render()
D. object_list
The perfect next tutorial after this is:
Tutorial: Django QuerySets and ORM Deep Dive
Subject: filtering, ordering, excluding, chaining queries, and efficient data retrieval.
That tutorial will help you understand how Django retrieves and manipulates data behind all these generic views.