Django Best Practices: Function-Based Views vs Class-Based Views

Updated Best, Practices

Table of Contents

Django's use of both function-based views (FBVs) and class-based views (CBVs) causes a lot of confusion for newcomers. Why have multiple ways to do the same thing? And which one should I use? This article will cover the history of FBVs and CBVs in Django and cover the pros and cons of each approach.

In a Django project, the order of operations is generally urls.py -> views.py -> models.py + template.html. An HTTP request is made to a URL like example.com. Within Django, a project-level urls.py file routes the request to an appropriate views.py file, which includes a template.html file and models.py file (if needed) along with additional logic in the view itself. The view then returns an HTTP response to the user.

This process happens over and over again for each webpage within a Django site.

Function-Based Views

Historically Django used only function-based views (FBV). These take a standard Python function form of accepting a request object as an argument and returning a response object. Everything is visible in one place. Here is an example of a function that returns a simple template. For simplicity, no database models are included here.

# helloworld/views.py
from django.shortcuts import render

def homepage_view(request):
    return render(request, "helloworld/index.html")

Notice that the view accepts a single request object as the argument and uses the built-in render shortcut to return a response. In this case, it displays the template index.html within the helloworld app.

FBVs can often grow quite large in their size because all the logic for the view must be included. The upside of this approach is that there is no hidden behavior; you know exactly what you're getting. Decorators can be added, yes, but largely, what you see is what you get. The downside is that FBVs can often become quite long--15 or even 30+ lines isn't unheard of--which is difficult to reason about. And since most web functionality is essentially the same there is a lot of repeated code that could be abstracted away.

The downside is length and repetition. How readable is a 15-line FBV? How about a 30-line FBV? Especially when probably 12 of the lines are doing the exact same work as other FBVs, and really, there are only 3 lines that are different. As a result, 14 function-based generic views were added to handle common use cases.

Class-Based Views (CBVs)

Starting with Django 1.3, class-based views were added that use inheritance to write much more concise views. Generic class-based views were also added, deliberately mimicking earlier function-based generic views.

The base View class handles the mechanics of HTTP dispatch: it routes each HTTP method to a matching method on the class (get, post, put, etc.). Here is the equivalent of the FBV example above written as a CBV:

# helloworld/views.py
from django.shortcuts import render
from django.views import View

class HomepageView(View):
    def get(self, request):
        return render(request, "helloworld/index.html")

One important difference from FBVs: CBVs must be connected to a URL pattern using .as_view():

# helloworld/urls.py
from django.urls import path
from .views import HomepageView

urlpatterns = [
    path("", HomepageView.as_view(), name="homepage"),
]

Using a CBV requires understanding the inheritance structure, which is a higher learning curve since it is not as readily apparent as it is in an FBV. However, the payoff is that HTTP method handling is explicit and separated rather than buried in an if request.method == "POST": block.

Generic Class-Based Views (GCBVs)

Generic class-based views go one step further. Rather than inheriting from the bare View class, they provide pre-built behavior for the most common view patterns: displaying a template, listing objects from a model, showing a single object's detail page, and handling create/update/delete forms.

For a view that simply renders a template, TemplateView reduces the example above to two lines:

# helloworld/views.py
from django.views.generic import TemplateView

class HomepageView(TemplateView):
    template_name = "helloworld/index.html"

The URL configuration stays the same — .as_view() is still required.

For views that interact with database models, GCBVs like ListView and DetailView handle queryset fetching, pagination, and context automatically, leaving only the model and template name to specify. The site Classy Class-Based Views is an invaluable reference for exploring what each GCBV provides and which attributes and methods can be overridden. The upsides are no more code repetition and much more concise views that make it easier to focus on what's been changed rather than repeat generic code.

The Winner?

As the three examples above illustrate, each approach trades explicitness for conciseness. Personally, I always start with a GCBV if possible, drop down to a CBV when I need more control over HTTP method handling, and reach for an FBV only when the logic is complex enough that the inheritance chain would obscure more than it helps. Most--but not all--Django developers I know follow a similar approach.

It's worth noting that the Django source code itself uses CBVs, and via mixins more and more behavior that in the past required an FBV is becoming available within a CBV context.

I do think, however, that once you've hit an intermediate level of comfort with Django, the ability to write the same view both as an FBV and as a CBV is valuable. Arguably, it is easier from a learning perspective to see all the logic in one place via an FBV versus having to go through the inheritance chain of a CBV.

So, FBVs have their place, and it's a strength of Django that both FBVs and CBVs are supported. Unfortunately, this comes at the cost of some additional confusion for newcomers, hence why I wrote this post.

Not all Django developers agree, though. There are very experienced and vocal proponents of function-based views who consider class-based views to be a mistake. Luke Plant wrote a very extensive series of articles called Django Views - The Right Way arguing this very point. It is highly recommended to read it. To truly master Django it is necessary to be comfortable with all three forms of views--FBVs, CBVs, GCBVs--and to be aware of their relative pros and cons.