Django Function-Based Views vs. Class-Based Views

Django views are an essential part of most Django applications. They carry out user requests to perform actions and return different types of data. Django provides two distinct methods for creating a view: function-based and class-based. Many discussions have been had over the benefits of using one over the other. I believe that the benefits of using one or the other is highly circumstantial. In this post, I will outline the guidelines I use to determine which option is better for different circumstances.

Function-Based Views (FBVs)

What are they?

You are probably familiar with function-based views. They are generally the first type of view a beginner learns about (and in some cases, the only one). FBVs are basically just functions that take a request argument.

from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>It is now %s.</body></html>" % now
    return HttpResponse(html)

FBVs are simple to write and understand. Unlike class-based views, the logic is sequential and explicit. There is no need to think about hidden functionality.

When should they be used?

In my opinion, FBVs should be used minimally. If you are going to use them, only use them for really short and simple views. CBVs have the edge on FBVs in most cases. When deciding whether or not to use an FBV, consider the caveats in the next section.

When shouldn’t they be used?

FBVs are best used for short and simple view logic. The best way to think about when to use FBVs is to first think about when not to use them.

When views get too long

In keeping with general best coding practices and keeping your functions short (no longer than your editor’s scroll height), using FBVs can be challenging.  Breaking up your view into multiple functions means polluting your view file (or a separate utility file) with dissociated non-view helper functions. Consider switching to a CBV when your FBV gets too long and needs to be broken up.

When views can be generalized

If you find yourself writing similar views as your project grows, you may benefit from generalizing to keep your views DRY. With FBVs, this requires creating shared helper functions to call from your views. This method has limited flexibility, and as your project gets complex, this design will not scale well.

To avoid boilerplate logic

As you begin to write different kinds of views, you may notice that similar views require similar boilerplate logic. Consider the following pattern for views that accept multiple HTTP methods.

def my_view(request):
    if request.method == 'GET':
        # Do something
    else:
        # Handle POST request

In addition to this case, form validation and template rendering add even more common boilerplate. There is not a great way to avoid this type of boilerplate with FBVs.

Summary

Advantages

  • Simple and easy to write.
  • Easy for anyone to read and understand.
  • Explicit; logic is sequential and highly visible.

Disadvantages

  • Views with a lot of logic are harder to modularize.
  • Does not allow for easily sharing generic logic.
  • Some boilerplate is required for common functionality.

Class-Based Views (CBVs)

What are they?

Class-based views, or CBVs, are Python classes that extend Django’s View class. CBVs were created to solve the same problems as FBVs, but in a more customizable and flexible way. The difference is best illustrated with an example from the Django docs.

Function-based view:

from django.http import HttpResponse

def my_view(request):
    if request.method == 'GET':
        # <view logic>
        return HttpResponse('result')

Class-based equivalent:

from django.http import HttpResponse
from django.views import View

class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse('result')

Essentially, Django uses the power of object-oriented programming to simplify the process of creating a view and to abstract away common functionality.

Generic Class-Based Views

With the power of CBVs, Django provides subclasses that further simplify common types of views. For example, consider this example of one of Django’s generic views, TemplateView.

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

class AboutView(TemplateView):
    template_name = "about.html"

Django provides subclasses like this for many common types of views, including LoginView, FormView, and ListView. ccbv.co.uk is an excellent resource for information on different class-based views, including generics.

When should they be used?

CBVs can always be used. As discussed, they are far more powerful than simple FBVs, and come at very little extra effort. That said, if you still prefer to stick with FBVs as much as possible, or are working with an existing application that already uses FBV, the following are some key situations where you may want to consider switching to a CBV.

To keep functions short

We discussed previously that FBVs break down when the view gets long and needs to be broken up. CBVs allow you to break up code into methods on the class. This is better than having dissociated functions for the following reasons.

  • Class methods can share instance variables; cutting down on the number of parameters that need to be passed to each helper method.
  • Class methods are associated to a class. There is a clear relationship between the function and the view that depends on it.
  • Common methods can be put into base view classes, or mixins to share with other views.

To generalize your views

It is far simpler to share logic between CBVs than with FBVs. Using inheritance, existing views can be extended. Also, since Python classes support multiple inheritance, a class can extend multiple mixins (like a Java interface with an implementation) that each add their own methods or attributes. I won’t cover mixins in depth here, but this article explains it concisely.

If you find yourself needing to share common functionality between views, you should be using CBVs.

To avoid boilerplate logic

As discussed, CBVs cut out common boilerplate logic. Consider the CBV version of the boilerplate example for FBVs.

class MyView(views.View):
    def get(self, request):
        # Handle GET request
    
    def post(self, request):
        # Handle POST request

While we still have the exact same number of lines of code, we have eliminated the conditional request routing logic. This is beneficial because as Django is a well-tested and trusted framework, we should let it do as much of the work as possible. Writing this logic ourselves, no matter how trivial, introduces the risk of defects.

Although this is a trivial example, Django provides generic functionality that can help us keep our code as concise and stable as possible. Again, ccbv.co.uk is a great resource for determining which classes you may benefit from using.

When shouldn’t they be used?

As I have expressed, I prefer CBVs for nearly all cases. They are at least as simple and concise for simple views, and overwhelming more so as view complexity increases. However, there are a few common criticisms that are at least worth mentioning.

To keep views explicit

The same criticisms of object-oriented programming apply to CBVs as well. Some claim that CBVs hide too much information in their inheritance hierarchy. Especially for Django’s generic views, this can be the case. Some would consider it a feature that boilerplate is abstracted away, but I can see how it could cause confusion in some cases.

In my opinion, any decent software engineer should at least be familiar with object-orientation, and should be able to easily understand a reasonably-shallow inheritance chain. Furthermore, Django is open-source, so the entirely of the view implementation is free to read (and easy to understand, as it is written in Python).

However, I must admit that there is some value in keeping views clearly sequential and explicit. I also acknowledge that not every developer can keep object inheritance simple, or is able or willing to read and understand source code. If your team is composed of developers that would benefit from the increased understandability, it is not inadmissible to use FBVs.

Summary

Advantages

  • Allows for better and simpler design.
  • Makes it easy to share code between views.
  • Generic CBVs expedite the creation of common types of views.

Disadvantages

  • Hides implementation details with inheritance.
  • Lack of clear sequence can be confusing for some developers.

Closing Thoughts

Function-based and class-based views both get the job done. FBVs came first, and CBVs came later as a welcome improvement. Both are used in modern applications, but CBVs have a distinct advantage in my opinion. Although FBVs have better understandability, CBVs are far more powerful and help manage complexity.

How do you use FBVs or CBVs in your application? What difficulties have you faced using one or the other? Which one do you prefer? If you have answers to any of these questions, let me know in the comments.

One thought on “Django Function-Based Views vs. Class-Based Views”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.