PageView

Subscribe to our YouTube Channel!
[Jul 12, 2021] New Video: How to Use Django Rest Framework Permissions (DRF Tutorial - Part 7)


Class-Based Views vs. Function-Based Views

Class-Based Views vs. Function-Based Views (Picture: https://www.pexels.com/photo/black-and-white-city-man-people-1984/)

If you follow my content here in the blog, you probably have already noticed that I’m a big fan of function-based views. Quite often I use them in my examples. I get asked a lot why I don’t use class-based views more frequently. So I thought about sharing my thoughts about that subject matter.

Before reading, keep that in mind: class-based views does not replace function-based views.


Introduction

I’m not an old school Django developer that used it when there was only function-based views and watched the class-based views been released. But, there was a time that only function-based views existed.

I guess it didn’t take long until all sort of hacks and solutions was created to extend and reuse views, make them more generic.

There was a time that function-based generic views was a thing. They were created to address the common use cases. But the problem was that they were quite simple, and was very hard to extend or customize them (other than using configurations parameters).

To address those issues, the class-based views was created.

Now, views are always functions. Even class-based views.

When we add them to the URL conf using the View.as_view() class method, it returns a function.

Here is what the as_view method looks like:

class View:
    @classonlymethod
    def as_view(cls, **initkwargs):
        """Main entry point for a request-response process."""
        for key in initkwargs:
            # Code omitted for clarity
            # ...

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)

        # Code omitted for clarity
        # ...

        return view

Parts of the code was omitted for clarity, you can see the full method on GitHub.

So if you want to explicitly call a class-based view here is what you need to do:

return MyView.as_view()(request)

To make it feel more natural, you can assign it to a variable:

view_function = MyView.as_view()
return view_function(request)

The view function returned by the as_view() method is outer part of every class-based view. After called, the view pass the request to the dispatch() method, which will execute the appropriate method accordingly to the request type (GET, POST, PUT, etc).

Class-Based View Example

For example, if you created a view extending the django.views.View base class, the dispatch() method will handle the HTTP method logic. If the request is a POST, it will execute the post() method inside the view, if the request is a GET, it will execute the get() method inside the view.

views.py

from django.views import View

class ContactView(View):
    def get(self, request):
        # Code block for GET request

    def post(self, request):
        # Code block for POST request

urls.py

urlpatterns = [
    url(r'contact/$', views.ContactView.as_view(), name='contact'),
]
Function-Based View Example

In function-based views, this logic is handled with if statements:

views.py

def contact(request):
    if request.method == 'POST':
        # Code block for POST request
    else:
        # Code block for GET request (will also match PUT, HEAD, DELETE, etc)

urls.py

urlpatterns = [
    url(r'contact/$', views.contact, name='contact'),
]

Those are the main differences between function-based views and class-based views. Now, Django’s generic class-based views are a different story.


Generic Class-Based Views

The generic class-based-views was introduced to address the common use cases in a Web application, such as creating new objects, form handling, list views, pagination, archive views and so on.

They come in the Django core, and you can implement them from the module django.views.generic.

They are great and can speed up the development process.

Here is an overview of the available views:

Simple Generic Views
  • View
  • TemplateView
  • RedirectView
Detail Views
  • DetailView
List Views
  • ListView
Editing Views
  • FormView
  • CreateView
  • UpdateView
  • DeleteView
Date-Based Views
  • ArchiveIndexView
  • YearArchiveView
  • MonthArchiveView
  • WeekArchiveView
  • DayArchiveView
  • TodayArchiveView
  • DateDetailView

You can find more details about each implementation in the official docs: Built-in class-based views API.

I find it a little bit confusing, because the generic implementations uses a lot of mixins, so at least for me, sometimes the code flow is not very obvious.

Now here is a great resource, also from the Django Documentation, a flattened index with all the attributes and methods from each view: Class-based generic views - flattened index. I keep this one in my bookmarks.


The Different Django Views Schools

Last year I got myself a copy of the Two Scoops of Django: Best Practices for Django 1.8 Amazon AdSystem book. It’s a great book. Each chapter is self-contained, so you don’t need to read the whole book in order.

In the chapter 10, Daniel and Audrey talk about the best practices for class-based views. They brought up this tip that was very interesting to read, so I thought about sharing it with you:

School of “Use all the generic views”!
This school of thought is based on the idea that since Django provides functionality to reduce your workload, why not use that functionality? We tend to belong to this school of thought, and have used it to great success, rapidly building and then maintaining a number of projects.

School of “Just use django.views.generic.View”
This school of thought is based on the idea that the base Django CBV does just enough and is ‘the True CBV, everything else is a Generic CBV’. In the past year, we’ve found this can be a really useful approach for tricky tasks for which the resource-based approach of “Use all the views” breaks down. We’ll cover some use cases for it in this chapter.

School of “Avoid them unless you’re actually subclassing views”
Jacob Kaplan-Moss says, “My general advice is to start with function views since they’re easier to read and understand, and only use CBVs where you need them. Where do you need them? Any place where you need a fair chunk of code to be reused among multiple views.”

Excerpt from “Two Scoops of Django: Best Practices for Django 1.8”Amazon AdSystem - 10.4: General Tips for Django CBV, page 121.

The authors said in the book they are in the first school. Personally, I’m in the third school. But as they said, there is no consensus on best practices.

PS: The authors released an updated version of the book, covering Django 1.11 and many updates: Two Scoops of Django 1.11: Best Practices for the Django Web Framework Amazon AdSystem


Pros and Cons

For reference, some pros and cons about function-based views and class-based views.

Pros Cons
Function-Based Views
  • Simple to implement
  • Easy to read
  • Explicit code flow
  • Straightforward usage of decorators
  • Hard to extend and reuse the code
  • Handling of HTTP methods via conditional branching
Class-Based Views
  • Can be easily extended, reuse code
  • Can use O.O techniques such as mixins (multiple inheritance)
  • Handling of HTTP methods by separate class methods
  • Built-in generic class-based views
  • Harder to read
  • Implicit code flow
  • Hidden code in parent classes, mixins
  • Use of view decorators require extra import, or method override

There is no right or wrong. It all depends on the context and the needs. As I mentioned in the beginning of this post, class-based views does not replace function-based views. There are cases where function-based views are better. In other cases class-based views are better.

For example, if you are implementing a list view, and you can get it working just by subclassing the ListView and overriding the attributes. Great. Go for it.

Now, if you are performing a more complex operation, handling multiple forms at once, a function-based view will serve you better.


Conclusions

The reason why I use function-based views often in my post examples, is because they are way easier to read. Many readers that stumble upon my blog are beginners, just getting started. Function-based views communicate better, as the code flow is explicit.

I usually always start my views as function-based views. If I can use a generic class-based view just by overriding the attributes, I go for it. If I have some very specific needs, and it will replicate across several views, I create my own custom generic view subclassing the django.views.generic.View.

Now a general advice from the Django documentation: If you find you’re struggling to implement your view as a subclass of a generic view, then you may find it more effective to write just the code you need, using your own class-based or functional views.