Debug School

rakesh kumar
rakesh kumar

Posted on

Class-based vs Function-based Views in Django

Refer here
Refer here1
Refer here2
Refer here3
Refer here4
Refer here5
Refer here6

Now let’s compare both of the views and see the pros and cons of both of them.

  1. Function-Based Views Function-based views are good for beginners. It is very easy to understand in comparison to class-based views. Initially when you want to focus on core fundamentals, using the function-based views gives the advantage to understand it. Let’s discuss some pros and cons of it.

Image description

Image description

Image description

Image description

Image description

Image description

Pros:

  1. Easy to read, understand and implement.
  2. Explicit code flow
  3. Straightforward usage of decorators.
  4. Good for the specialized functionality.
    Cons:

  5. Code redundancy and hard to extend

  6. Conditional branching will be used to handle HTTP methods
    .
    As we have discussed function-based views are easy to understand but due to the code redundancy in a large Django project, you will find similar kinds of functions in the views. You will find a similar kind of code is repeated unnecessarily.

def example_create_view(request, pk):
  template_name = 'form.html'
  form_class = FormExample

  form = form_class

  if request.method == 'POST':
    form = form_class(request.POST)
    if form.is_valid():
      form.save()
      return HttpResponseRedirect(reverse('list-view'))

  return render(request, template_name, {'form': form})
Enter fullscreen mode Exit fullscreen mode

All the above cons of FBVs you won’t find in class-based views. You won’t have to write the same code over and over in your boilerplate.

Class-Based Views
Class-based views are the alternatives of function-based views. It is implemented in the projects as Python objects instead of functions. Class-based views don’t replace function-based views, but they do have certain advantages over function-based views. Class-based views take care of basic functionalities such as deleting an item or add an item.

Using the class-based view is not easy if you’re a beginner. You will have to go through the documentation, and you will have to study it properly. Once you understand the function-based view in Django and your concepts are clear, you can move to the class-based views. Let’s discuss the class-based views in detail.

Pros

  1. The most significant advantage of the class-based view is inheritance. In the class-based view, you can inherit another class, and it can be modified for the different use cases.
  2. It helps you in following the DRY principle. You won’t have to write the same code over and over in your boilerplate. Code reusability is possible in class-based views.
  3. You can extend class-based views, and you can add more functionalities using Mixins.
  4. Another advantage of using a class-based view is code structuring. In class-based views, you can use different class instance methods (instead of conditional branching statements inside function-based views) to generate different HTTP requests.
  5. Built-in generic class-based views.
    Cons

  6. Complex to implement and harder to read

  7. Implicit code flow.

  8. Extra import or method override required in view decorators.

  9. Below is an example of a class-based view…

class MyCreateView(View):
  template_name = 'form.html'
  form_class = MyForm

  def get(self, request, *args, **kwargs):
    form = self.form_class
    return render(request, template_name, {'form': form})

  def post(self, request, *args, **kwargs):
    form = self.form_class(request.POST)
    if form.is_valid():
      form.save()
      return HttpResonseRedirect(reverse('list-view'))
    else:
      return render(request, self.template_name, {'form': form})
Enter fullscreen mode Exit fullscreen mode

We have a little abstraction and method as_view() is calling the dispatch() to determine which class method needs to be executed, depending on the HTTP request. as_view() let you override the class attributes in your URLs confs. You can do something like the below…

urlpatterns = [
    url(r'^new/$', MyCreateView.as_view(), name='original-create-view')
    url(r'^new_two/$', MyCreateView.as_view(template_name='other_form.html',
                    form_class='MyOtherForm'), name='modified-create-view')
  ]
Enter fullscreen mode Exit fullscreen mode

Once you start using the Django generic class-based views, you will be able to over-write the helper method like get_form_class and get_template_names. You can insert the additional logic at these points instead of just overriding the class attribute.

One of the good examples of it is…ModelFormMixin. form_valid method is overridden. With the updated value stored in self.object() form_valid method is overridden.

Django Generic Class-Based View
Creating a new object, form handling, list views, pagination, archive views all these things are the common use cases in a Web application. It comes in Django core, you can implement them from the module django.views.generic. Generic class-based views are a great choice to perform all these tasks. It speeds up the development process.

Django provides a set of views, mixins, and generic class-based views. Taking the advantage of it you can solve the most common tasks in web development.

The main goal is not to reduce the boilerplate. It saves you from writing the same code again and again. Modify MyCreateView to inherit from django.views.generic.CreateView.

from django.views.generic import CreateView 
class MyCreateView(CreateView):
    model = MyModel  
    form_class = MyForm
Enter fullscreen mode Exit fullscreen mode

You might be thinking that where all the code disappears. The answer is that it’s all in django.views.generic.CreateView. You get a lot of functionality and shortcuts when you inherit from CreateView. You also buy into a sort of ‘convention over configuration.’ style arrangement. Let’s discuss few more details…

By default template should reside in //_form.html. You can change it by setting the class attribute template_name and template_name_suffix.

  1. We also need to declare the model and form_class attributes. Methods you inherit from CreateView rely on them.
  2. You will have to declare success_url as a class attribute on the view or you will have to specify get_absolute_url() in the model. This is important for the view in your boilerplate else the view won’t know where to redirect to following a successful form submission.
  3. Define the fields in your form or specify the fields class attribute on the view. Here in this example, you can choose to do the latter . Look at the example given below to check how it will look like.
from django import forms
from . models import MyModel 
class MyModelForm(forms.ModelForm):
  class Meta:
    model = MyModel
    fields = ['name', 'description']
Enter fullscreen mode Exit fullscreen mode

Function-based views (FBVs)

At their core, FBVs are just functions. They're easy to read and work with since you can see exactly what's happening. Due to their simplicity, they're a great fit for Django beginners. So, if you're just starting with Django, it's recommended to have some working knowledge of FBVs before diving into CBVs.

Pros and Cons
Pros

  1. Explicit code flow (you have full control over what happens)
  2. Simple to implement
  3. Easy to understand
  4. Great for unique view logic
  5. Easy to integrate with decorators
    Cons

  6. A lot of repeated (boilerplate) code

  7. Handling of HTTP methods via conditional branching

  8. Don't take advantage of OOP

  9. Harder to maintain

An example FBV looks like this:

from django.shortcuts import render, redirect
from django.views import View


def task_create_view(request):
    if request.method == 'POST':
        form = TaskForm(data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('task-list'))

    return render(request, 'todo/task_create.html', {
        'form': TaskForm(),
    })
Enter fullscreen mode Exit fullscreen mode

This view takes in a request, performs some logic, and returns an HttpResponse. Just by looking at the code, we can see the first downside: conditional branching. For each HTTP method, we have to create a separate branch. This can increase code complexity and lead to spaghetti code.

The next downside of FBVs is that they do not scale well. As your codebase grows bigger and bigger, you'll notice a lot of repeated (boilerplate) code for handling models (especially with CRUD operations). Try imagining how much a view for creating articles would differ from the example above... They'd be pretty much the same.

In order to use FBVs, we have to register them inside urls.py like so:

urlpatterns = [
    path('create/', task_create_view, name='task-create'),
]
Enter fullscreen mode Exit fullscreen mode

You should opt for FBVs when you're working on highly customized view logic. In other words, FBVs are a great use case for a view that doesn't share much code with other views. A few real-world examples for using FBVs would be: a statistics view, a chart view, and a password reset view.

Todo App (using FBVs)

Let's look at how a simple todo application that allows CRUD operations would be written using only FBVs.

Firstly, we'd initialize our project, define our models, create HTML templates and then start working on views.py. We'd probably end up with something like this:

# todo/views.py

from django.shortcuts import render, get_object_or_404, redirect

from .forms import TaskForm, ConfirmForm
from .models import Task


def task_list_view(request):
    return render(request, 'todo/task_list.html', {
        'tasks': Task.objects.all(),
    })


def task_create_view(request):
    if request.method == 'POST':
        form = TaskForm(data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('task-list'))

    return render(request, 'todo/task_create.html', {
        'form': TaskForm(),
    })


def task_detail_view(request, pk):
    task = get_object_or_404(Task, pk=pk)
    return render(request, 'todo/task_detail.html', {
        'task': task,
    })


def task_update_view(request, pk):
    task = get_object_or_404(Task, pk=pk)

    if request.method == 'POST':
        form = TaskForm(instance=task, data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('task-detail', args={pk: pk}))

    return render(request, 'todo/task_update.html', {
        'task': task,
        'form': TaskForm(instance=task),
    })


def task_delete_view(request, pk):
    task = get_object_or_404(Task, pk=pk)

    if request.method == 'POST':
        form = ConfirmForm(data=request.POST)
        if form.is_valid():
            task.delete()
            return HttpResponseRedirect(reverse('task-list'))

    return render(request, 'todo/task_delete.html', {
        'task': task,
        'form': ConfirmForm(),
    })
Enter fullscreen mode Exit fullscreen mode

You can get the full source code on GitHub.

We ended up with simple and straightforward view logic. You can't improve this code much.

Class-based views (CBVs)

Class-based views, which were introduced in Django 1.3, provide an alternative way to implement views as Python objects instead of functions. They allow us to use OOP principles (most importantly inheritance). We can use CBVs to generalize parts of our code and extract them as superclass views.

CBVs also allow you to use Django's built-in generic class-based views and mixins, which we'll take a look at in the next section.

Pros and Cons
Pros

  1. Are extensible
  2. They take advantage of OOP concepts (most importantly inheritance)
  3. Great for writing CRUD views
  4. Cleaner and reusable code
  5. Django's built-in generic CBVs
  6. They're similar to Django REST framework views
    Cons

  7. Implicit code flow (a lot of stuff happens in the background)

  8. Use many mixins, which can be confusing

  9. More complex and harder to master

  10. Decorators require an extra import or code override
    For more, review What are the pros and cons of using class-based views in Django/Python?

Quick Example
Let's rewrite our previous FBV example as a CBV:

from django.shortcuts import render, redirect
from django.views import View


class TaskCreateView(View):

    def get(self, request, *args, **kwargs):
        return render(request, 'todo/task_create.html', {
            'form': TaskForm(),
        })

    def post(self, request, *args, **kwargs):
        form = TaskForm(data=request.POST)
        if form.is_valid():
            task = form.save()
            return redirect('task-detail', pk=task.pk)

        return self.get(request)
Enter fullscreen mode Exit fullscreen mode

We can see that this example is not much different from the FBV approach. The logic is more or less the same. The main difference is code organization. Here each HTTP method is addressed with a separate method instead of conditional branching. In CBVs you can use the following methods: get, post, put, patch, delete, head, options, trace.

Another upside of this approach is that HTTP methods that are not defined automatically return a 405 Method Not Allowed response.

When using FBVs, you can use one of the allowed HTTP method decorators, like @require_http_methods, to achieve the same thing.

Because Django's URL resolver expects a callable function, we need to call as_view() when registering them in urls.py:

urlpatterns = [
    path('create/', TaskCreateView.as_view(), name='task-create'),
]
Enter fullscreen mode Exit fullscreen mode

Code Flow
The code flow for CBVs is a little more complex because some stuff happens in the background. If we extend the base View class the following code steps will be executed:

  1. An HttpRequest is routed to MyView by the Django URL dispatcher.
  2. The Django URL dispatcher calls as_view() on MyView.
  3. as_view() invokes setup() and dispatch().
  4. dispatch() triggers a method for a specific HTTP method or http_method_not_allowed().
  5. An HttpResponse is returned.

Image description

Todo App (using CBVs)

Now, let's rewrite our todo application to only use CBVs:

# todo/views.py

from django.shortcuts import render, get_object_or_404, redirect
from django.views import View

from .forms import TaskForm, ConfirmForm
from .models import Task


class TaskListView(View):

    def get(self, request, *args, **kwargs):
        return render(request, 'todo/task_list.html', {
            'tasks': Task.objects.all(),
        })


class TaskCreateView(View):

    def get(self, request, *args, **kwargs):
        return render(request, 'todo/task_create.html', {
            'form': TaskForm(),
        })

    def post(self, request, *args, **kwargs):
        form = TaskForm(data=request.POST)
        if form.is_valid():
            task = form.save()
            return redirect('task-detail', pk=task.pk)

        return self.get(request)


class TaskDetailView(View):

    def get(self, request, pk, *args, **kwargs):
        task = get_object_or_404(Task, pk=pk)

        return render(request, 'todo/task_detail.html', {
            'task': task,
        })


class TaskUpdateView(View):

    def get(self, request, pk, *args, **kwargs):
        task = get_object_or_404(Task, pk=pk)
        return render(request, 'todo/task_update.html', {
            'task': task,
            'form': TaskForm(instance=task),
        })

    def post(self, request, pk, *args, **kwargs):
        task = get_object_or_404(Task, pk=pk)
        form = TaskForm(instance=task, data=request.POST)
        if form.is_valid():
            form.save()
            return redirect('task-detail', pk=task.pk)

        return self.get(request, pk)


class TaskDeleteView(View):

    def get(self, request, pk, *args, **kwargs):
        task = get_object_or_404(Task, pk=pk)
        return render(request, 'todo/task_confirm_delete.html', {
            'task': task,
            'form': ConfirmForm(),
        })

    def post(self, request, pk, *args, **kwargs):
        task = get_object_or_404(Task, pk=pk)
        form = ConfirmForm(data=request.POST)
        if form.is_valid():
            task.delete()
            return redirect('task-list')

        return self.get(request, pk)
Enter fullscreen mode Exit fullscreen mode

Also, let's not forget to make our urls.py call as_view():

todo/urls.py

from django.urls import path

from .views import TaskListView, TaskDetailView, TaskCreateView, TaskUpdateView, TaskDeleteView


urlpatterns = [
    path('', TaskListView.as_view(), name='task-list'),
    path('create/', TaskCreateView.as_view(), name='task-create'),
    path('<int:pk>/', TaskDetailView.as_view(), name='task-detail'),
    path('update/<int:pk>/', TaskUpdateView.as_view(), name='task-update'),
    path('delete/<int:pk>/', TaskDeleteView.as_view(), name='task-delete'),
]
Enter fullscreen mode Exit fullscreen mode

You can get the full source code on GitHub.

We sacrificed a few lines of code for a bit cleaner code. We no longer use conditional branching. If we, for example, look at TaskCreateView and TaskUpdateView, we can see that they're pretty much the same. We could further improve this code by extracting the common logic into a parent class. Additionally, we could extract the view logic and use it for views for other models.

Django's Generic Class-based Views

If you were to follow all of the refactoring suggestions mentioned in the previous section, you'd eventually be left with a view that mimics some of Django's generic class-based views. Django's generic CBVs are great for solving common problems like retrieving, creating, modifying, and deleting objects as well as pagination and archive views. They speed up the development process too.

Quick Example
Let's look at an example:

from django.views.generic import CreateView


class TaskCreateView(CreateView):
    model = Task
    context_object_name = 'task'
    fields = ('name', 'description', 'is_done')
    template_name = 'todo/task_create.html'
Enter fullscreen mode Exit fullscreen mode

We created a class named TaskCreateView and inherited CreateView. By doing that we gained a lot of functionality, with almost no code. Now we just need to set the following attributes:

  1. model defines what Django model the view works with.
  2. fields is used by Django to create a form (alternatively, we could provide form_class).
  3. template_name defines which template to use (defaults to //_form.html).
  4. context_object_name defines the context key under which the model instance is passed to the template (defaults to object).
  5. success_url defines where the user gets redirected on success (alternatively, you can set get_absolute_url in your model). For more information about generic CBVs, refer to the official documentation.

As you've probably guessed, there's a lot more magic happening behind the scenes with generic CBVs. They can be confusing even for experienced Django developers. Once you get the hang of them, though, you'll probably feel like a wizard.

You should use generic CBVs for views that perform common tasks (e.g., CRUD operations). If your view needs to do something that's not covered by CBVs, use mixins or a function-based view.

Top comments (0)