Yesterday, the app had full authentication. Today, for day 70, I covered two things: Class-Based Views, which are Django's cleaner alternative to function-based views, and then used that understanding to jump into Django REST Framework. DRF builds directly.
Class-Based Views (CBVs)
So far, every view I've written has been a function. Django also lets you write views as classes. Same purpose, different structure, and Django provides a set of built-in generic CBVs that handle common patterns with very little code.
Function-Based View vs Class-Based View
Here's the same view written both ways:
# function-based
def post_list(request):
posts = Post.objects.all()
return render(request, 'core/post_list.html', {'posts': posts})
# class-based
from django.views.generic import ListView
class PostListView(ListView):
model = Post
template_name = 'core/post_list.html'
context_object_name = 'posts'
Both do exactly the same thing. The CBV version is shorter because ListView handles the queryset fetching and context building automatically.
Wiring CBVs to URLs
CBVs need .as_view() when connecting to a URL:
from django.urls import path
from .views import PostListView
urlpatterns = [
path('', PostListView.as_view(), name='home'),
]
Django's Built-in Generic CBVs
Django provides generic views for the most common patterns:
ListView — display a list of objects
from django.views.generic import ListView
class PostListView(ListView):
model = Post
template_name = 'core/post_list.html'
context_object_name = 'posts'
ordering = ['-created_at']
paginate_by = 10 # built-in pagination
DetailView — display a single object
from django.views.generic import DetailView
class PostDetailView(DetailView):
model = Post
template_name = 'core/post_detail.html'
context_object_name = 'post'
URL pattern for DetailView expects a pk or slug:
path('post/<int:pk>/', PostDetailView.as_view(), name='post_detail'),
CreateView — form to create an object
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
class PostCreateView(CreateView):
model = Post
fields = ['title', 'content', 'is_published']
template_name = 'core/post_form.html'
success_url = reverse_lazy('home')
reverse_lazy is like redirect() but evaluated lazily — required here because URLs aren't fully loaded when the class is defined.
UpdateView — form to edit an object
from django.views.generic.edit import UpdateView
class PostUpdateView(UpdateView):
model = Post
fields = ['title', 'content', 'is_published']
template_name = 'core/post_form.html'
success_url = reverse_lazy('home')
DeleteView — confirm and delete an object
from django.views.generic.edit import DeleteView
class PostDeleteView(DeleteView):
model = Post
template_name = 'core/post_confirm_delete.html'
success_url = reverse_lazy('home')
Adding Login Required to CBVs
With function-based views you used @login_required. With CBVs you use LoginRequiredMixin:
from django.contrib.auth.mixins import LoginRequiredMixin
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
template_name = 'core/post_form.html'
success_url = reverse_lazy('home')
Always put LoginRequiredMixin as the first parent class.
Overriding Methods in CBVs
CBVs are classes, so you override methods to customize behavior:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
template_name = 'core/post_form.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
form_valid() runs when the form passes validation. Here we attach the current user as author before saving, same as commit=False in the function-based approach, but cleaner.
When to Use FBVs vs CBVs
- FBVs — more explicit, easier to read, better for complex custom logic
- CBVs — less code for standard CRUD operations, built-in mixins for common behavior
Neither is wrong. Most Django projects use both depending on the situation.
Introduction to Django REST Framework
Now that CBVs make sense, DRF becomes much easier to understand. Because DRF is essentially CBVs built for APIs instead of HTML pages.
What is DRF?
Django REST Framework is a package that makes building REST APIs with Django straightforward. It handles:
- Serializing Python objects to JSON
- Deserializing and validating incoming JSON
- API views and viewsets
- Authentication for APIs
- Browsable API interface for testing
Installation
pip install djangorestframework
Add to INSTALLED_APPS:
INSTALLED_APPS = [
...
'rest_framework',
]
Serializers
A serializer is DRF's equivalent of a form. It converts model instances to JSON and validates incoming data.
# core/serializers.py
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'content', 'created_at', 'is_published']
Just like ModelForm, ModelSerializer generates fields automatically from the model.
Serializing Data
post = Post.objects.get(id=1)
serializer = PostSerializer(post)
print(serializer.data)
# {'id': 1, 'title': 'My Post', 'content': '...', ...}
# serializing multiple objects
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
print(serializer.data)
Deserializing and Validating
data = {'title': 'New Post', 'content': 'Some content'}
serializer = PostSerializer(data=data)
if serializer.is_valid():
serializer.save()
else:
print(serializer.errors)
API Views
APIView — the base class
APIView is DRF's equivalent of Django's View. You define methods for each HTTP verb:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Post
from .serializers import PostSerializer
class PostListAPIView(APIView):
def get(self, request):
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
def post(self, request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Response automatically converts Python data to JSON. status gives you named HTTP status codes.
URL Setup for API Views
# core/urls.py
from django.urls import path
from .views import PostListAPIView
urlpatterns = [
path('api/posts/', PostListAPIView.as_view(), name='post-list-api'),
]
Visit http://127.0.0.1:8000/api/posts/. DRF gives you a browsable API interface in the browser automatically. You can view responses and submit data without Postman.
Generic API Views
Just like Django has generic CBVs, DRF has generic API views that reduce boilerplate further:
from rest_framework import generics
class PostListCreateAPIView(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
-
ListCreateAPIView— handlesGET(list) andPOST(create) -
RetrieveUpdateDestroyAPIView— handlesGET(single),PUT,PATCH,DELETE
urlpatterns = [
path('api/posts/', PostListCreateAPIView.as_view(), name='post-list'),
path('api/posts/<int:pk>/', PostRetrieveUpdateDestroyAPIView.as_view(), name='post-detail'),
]
Two classes, two URLs. Full CRUD API.
The Comparison That Makes It Click
| Django (HTML) | DRF (JSON API) |
|---|---|
View |
APIView |
ListView |
ListAPIView |
CreateView |
CreateAPIView |
Form / ModelForm
|
Serializer / ModelSerializer
|
render() |
Response() |
| Template | JSON |
Same concepts, different output format. That's DRF.
Wrapping Up
CBVs cleaned up the view code significantly, and understanding them made DRF immediately readable. Serializers work like forms, API views work like CBVs, and generic API views cut boilerplate to almost nothing. The app now serves both HTML pages and a JSON API from the same Django project.
Thanks for reading. Feel free to share your thoughts!
Top comments (0)