DEV Community

Cover image for Day 70 of #100DaysOfCode — Class-Based Views + Introduction to DRF
M Saad Ahmad
M Saad Ahmad

Posted on

Day 70 of #100DaysOfCode — Class-Based Views + Introduction to DRF

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'
Enter fullscreen mode Exit fullscreen mode

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'),
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

URL pattern for DetailView expects a pk or slug:

path('post/<int:pk>/', PostDetailView.as_view(), name='post_detail'),
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Add to INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    'rest_framework',
]
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Deserializing and Validating

data = {'title': 'New Post', 'content': 'Some content'}
serializer = PostSerializer(data=data)
if serializer.is_valid():
    serializer.save()
else:
    print(serializer.errors)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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'),
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • ListCreateAPIView — handles GET (list) and POST (create)
  • RetrieveUpdateDestroyAPIView — handles GET (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'),
]
Enter fullscreen mode Exit fullscreen mode

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)