jongkwan.dev
Development · Essay №089

Django FBVs and CBVs

How function-based and class-based views differ in behavior, the as_view and dispatch flow, and how to choose all the way up to generic views

Jongkwan Lee2026년 5월 18일6 min read
Contents

Writing the same page as a function or as a class is a choice between reusability and readability.

What a view takes and what it returns

In Django, a view is a callable that takes an HTTP request object and returns a response object. Written as a Python function, it is a function-based view (FBV). Written as a class, it is a class-based view (CBV). Both produce the same response; the difference is in how the code is structured.

The axis of the choice is the trade-off between reusability and readability. A simple page reads better as a single function, while repeated similar pages benefit from class inheritance cutting duplication.

Function-based views

An FBV is a Python function that takes the request as an argument. Branching on the request method is written directly with an if request.method conditional.

python
from django.http import HttpResponse
 
 
def my_view(request):
    if request.method == 'GET':
        return HttpResponse('handle GET request')
    elif request.method == 'POST':
        return HttpResponse('handle POST request')

The function above shows all branches in one place. Reading top to bottom tells you which method leads to which handling. That explicitness is the FBV's biggest strength, and the shorter the logic, the more it pays off. The cost is that once several similar functions exist, the shared parts have to be lifted out of the functions, and that takes real work.

Class-based views

A CBV defines the view as a class and uses HTTP method names directly as method names. Methods such as get and post handle their respective request methods.

python
from django.views import View
from django.http import HttpResponse
 
 
class MyView(View):
    def get(self, request):
        return HttpResponse('handle GET request')
 
    def post(self, request):
        return HttpResponse('handle POST request')

The key point is that branching happens through method definitions rather than conditionals. You define only the methods you handle, and Django rejects undefined methods automatically. Inheritance and mixins let several views share common behavior, which helps reuse. The cost is that the flow scatters across methods and parent classes, which is excessive structure for a small page.

URLconf registration and as_view

The URLconf maps each path to a callable. An FBV is already a function and maps directly, but a CBV is a class and cannot. as_view() bridges that gap.

python
from django.urls import path
from . import views
 
urlpatterns = [
    path('posts/', views.my_view, name='posts'),          # FBV: the function itself
    path('about/', views.MyView.as_view(), name='about'),  # CBV: turned into a function by as_view()
]

as_view() is a class method on View; calling it builds and returns a view function that will handle requests. From the URLconf's perspective, FBVs and CBVs both look like functions. The class structure hides inside that function.

How as_view and dispatch work

When a request arrives, the function built by as_view() instantiates the class and calls dispatch(). dispatch() lowercases the request method, finds the method of the same name, and runs it. For an undefined method, it routes to http_method_not_allowed and returns an HTTP 405 response. This flow is defined in Django's official documentation on class-based views and in the implementation of django.views.generic.base.View.

This is where the branching difference between FBVs and CBVs becomes visible. In an FBV the developer writes the branch with if; in a CBV, dispatch() branches on the method name instead. The list of allowed methods lives in the http_method_names attribute, so any method not on the list is blocked before a handler is looked up. You avoid writing branch code, but understanding the flow means knowing the parent class implementation.

Generic views and mixins

The practical benefit of CBVs comes from the generic views Django ships. Common page patterns such as list, detail, create, update, and delete are already implemented as classes. Specify a model and a template and the repetitive query and pagination code is handled for you.

python
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'
    paginate_by = 10

ListView handles querying the Post list, rendering the template, and splitting pages through inherited behavior. Setting paginate_by alone is enough to get pagination. Writing the same page as an FBV means repeating the query, paging, and rendering by hand.

Common behavior is composed in through mixins. A mixin is not a standalone view but a small class that adds capability to another view. A login check, for example, is handled by putting LoginRequiredMixin first.

python
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView
from .models import Post
 
 
class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ['title', 'body']
    login_url = '/login/'

Inheritance order matters for mixins. LoginRequiredMixin must come before CreateView so that the login check runs first in the method resolution order (MRO). Reversing the order can silently disable the check, so permission mixins always go on the left.

When to choose which

The two approaches produce the same result but differ in structure and cost.

AspectFBVCBV
StructureFunctionClass
ReusabilityDifficultEasy through inheritance and mixins
ReadabilitySimple and directStructured but with scattered flow
ExtensibilityComplexity grows as features are addedEasy to extend through inheritance
Generic viewsMust be implemented yourselfUse the generic views Django provides

FBVs suit simple logic with no complicated handling. They fit small projects, quick prototypes, and a preference for a functional style.

CBVs win when the logic is complex and several request methods are involved. They pay off when common functionality must be shared across views, or when a growing project makes extensibility matter. Using generic views such as list and detail directly is another case where a CBV is the natural fit.

Summary

FBVs and CBVs produce the same behavior; what differs is whether the code is structured as a function or as a class. An FBV gathers branching into one conditional and reads easily, while a CBV lets dispatch() branch on method names and grows reuse through inheritance and mixins. The dividing criterion is the complexity of the page and how much it repeats: simple means FBV, and a repeating pattern or a matching generic view means CBV. Mixing both within one project is perfectly fine, so pick whichever makes each individual page simpler.