Class-based Views

1. Basics

Django's class-based views provide an object-oriented (OO) way of organizing your view code. Most Django tutorials and training materials start developers off with the simple style of function-based views (which were available in Django long before class-based views). Class-based views were introduced to help make view code more reusable and provide for better view code organization.

The structure of a simple function-based view that is used to process both GET and POST requests might look like this:

# views.py
def simple_function_based_view(request):
    if request.method == 'GET':
        ...  # code to process a GET request
    elif request.method == 'POST':
        ...  # code to process a POST request

The same thing with a class-based view could look like this:

# views.py
from django.views import View

class SimpleClassBasedView(View):
    def get(self, request):
        ...  # code to process a GET request

    def post(self, request):
        ...  # code to process a POST request

Hooking up class-based views in the urls.py file is a little different too:

# urls.py
...
from . import views

urlconfig = [
    # the function-based view is added as the second param
    url(r'^function/$', views.simple_function_based_view),
    # the as_view method is called on the class-based view and 
    # the result is the value of the second param
    url(r'^class/$', views.SimpleClassBasedView.as_view()),
]

To illustrate this further we will walk through converting a function-based view to a class-based view that does the same thing.

# views.py
from datetime import datetime
from django.http import HttpResponse

def show_the_time(request):
    now = datetime.now()
    html = "<html><body>It is now {}</body></html>".format(now)
    return HttpResponse(html)
# urls.py
from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^now/$', views.show_the_time),
]

In order to do the same thing using a class-based view the files would have to change as follows:

# views.py
from datetime import datetime
from django.http import HttpResponse
from django.views import View  # import the View parent class

class ShowTimeView(View):  # create a view class

    # change the function-based view to be called get and add the self param
    def get(self, request):
        now = datetime.now()
        html = "<html><body>It is now {}</body></html>".format(now)
        return HttpResponse(html)
# urls.py
from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^now/$', views.ShowTimeView.as_view()),  # change how we reference the new view.
]

Note that there are not many changes in order to change from one type of view (function-based) to the other (class-based). The benefit of going with the class-based view (even in this simple example) is that the view is going to be more robust. The class-based views (view classes that extend the View class) get built-in responses to unsupported methods (POST, PUT, PATCH, etc.) and get support for the OPTIONS HTTP method too. All that is required to support other HTTP methods is to implement the same named method in the view class.

There are also generic class-based views. These are class-based views that provide extra common functionality. For example, a common type of view might be called a template view: a view that generates some context and sends the context to a specified template for rendering. Django provides a generic class-based view for that very purpose, TemplateView.

To use the example above, we will assume that the html portion is in a template called show_time.html. If so, we can change the ShowTimeView class to extend from the TemplateView instead of View and get the benefits of the common code.

# views.py
from datetime import datetime
from django.http import HttpResponse
from django.views.generic import TemplateView  # import the TemplateView parent class

class ShowTimeView(TemplateView):  # extend from TemplateView
    template_name = 'show_time.html'  # add a template_name attribute

    # change the get method to get_context_data
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = datetime.now()
        return context

The urls.py file shouldn't have to be modified.

To learn more about using class-based views and generic class-based views see the Django documentation ref and topics sections.

2. Deep Dive: Code Walk-through

View

The class-based views in Django all extend from the parent class View. This class can be found in django.views.generic.base (code here).

The View class has three methods that we will take a closer look at. For convenience the important parts of these methods are included below.

# django/views/generic/base.py
...
class View:
    ...
    def __init__(self, **kwargs):
        ...
        for key, value in kwargs.items():
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        ...
        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)
        view.view_class = cls
        view.view_initkwargs = initkwargs
        ...
        return view

    def dispatch(self, request, *args, **kwargs):
        ...
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

    ...

The __init__ method is fairly simple. It takes keyword arguments and sets each of them as attributes on the instance with the values provided. A Django developer doesn't have a need to instantiate class-based views directly, so the constructor isn't something that you would interact with directly, but it is used by the as_view method to provide a chance when calling as_view to override attributes then.

The dispatch method contains the actual view logic. It takes a request, finds the method that should be called for the given request (by using the HTTP method used), and returns the results of calling that method. If there isn't a method for the HTTP method used, the default view is the http_method_not_allowed view. It returns an HTTP 405 response.

The as_view method creates and returns a new function that is used as a Django view. Here we can see that even class-based views are simply just function-based views when they are actually used.

The view function that is created and returned from the as_view method, looks a lot like a function-based view. It takes a request and returns the results of calling the class-based views dispatch method. It is also interesting to see that the view function on each request will instantiate a new instance of the view class, set the request, args, and kwargs attributes, and then call and return the results from the view instance's dispatch method. It is also interesting to note that the view function also gets a few attributes of its own including the view_class and view_initkwargs attributes.

ContextMixin and TemplateResponseMixin

The suffix Mixin in Python is a convention that is often used to signify that the class provides functionality that can be used by other things. These types of classes aren't usually instantiated or used on their own other than to be extended from so that other classes can use the functionality they provide. These types of classes usually have no parent class and provide a very specific bit of functionality.

The ContextMixin class found in the generic views code provides child classes with a get_context_data method that can be used.

class ContextMixin:
    ...
    def get_context_data(self, **kwargs):
        if 'view' not in kwargs:
            kwargs['view'] = self
        if self.extra_context is not None:
            kwargs.update(self.extra_context)
        return kwargs

By itself, the ContextMixin isn't that helpful. The get_context_data method makes sure that there is a value view in kwargs and also makes sure that if there is anything in the extra_context attribute it is also added to kwargs before returning kwargs. In the next section we will see why this mixin is helpful, but first we will take a looks at the TemplateResponseMixin.

The TemplateResponseMixin (also in the same file) looks like this:

...
class TemplateResponseMixin:
    ...
    template_name = None
    ...
    def render_to_response(self, context, **response_kwargs):
        ...
        response_kwargs.setdefault('context_type', self.context_type)
        return self.response_class(
            request=self.request,
            template=self.get_template_names(),
            context=context,
            using=self.template_engine,
            **response_kwargs
        )
    ...

This class provides a few more attributes and another method that are not seen here, but what is shown is the important part for understanding what this mixin provides. The render_to_response method takes a context dict then instantiates a response object and returns the response object. It also takes care of defining which template should be used, as denoted by the template_name attribute.

These two mixins don't seem to provide very much, but when combined into the same view class, we can begin to see the power they offer. In the next section we look at the TemplateView class-based view and what it provides.

TemplateView

The TemplateView class-based view provides a simple reusable (generic) class-based view for rendering views that rely on context data and rendering a template. We look at it below (code on github).

class TemplateView(TemplateResponseMixin, ContextMixin, View):
    ...
    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)

As we can see the TemplateView provides a default get implementation that builds the context, by calling the get_context_data method provided by the ContextMixin class and then returns the results of calling the render_to_response method provided by the TemplateResponseMixin class. If you want to use the TemplateView all you should have to do is extend it and define the template_name attribute as well as an extended definition for the get_context_data method. For example:

class ShowTimeView(TemplateView):
    template_name = 'show_time.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = datetime.now()
        return context

The ShowTimeView will take care of rendering the proper response object for us and we have no need of defining a get method because we extended the TemplateView class-based view.

3. Deep Dive: Language Features

First-class functions

In Python, functions are known as first-class or higher order functions. Essentially this means that functions can be passed to other function calls, returned from other functions, and be assigned to variable names.

We see an example of the use of higher order functions in the View class's as_view method. When that method is called, it generates (defines) a new function called view and returns it. This means that the urls configuration gets a function where it is expecting a function:

    url(r'^$', views.ShowTimeView.as_view()),

With function-based views, the second argument is the function itself that should be executed when the url regex is matched. For class-based views we call another function, as_view, which returns a function (instead of naming it explicitly).

Let's look at another example of using functions as first-class citizens:

>>> def make_power_func(power):
...     def to_the_power(number):
...         return number ** power
...     return to_the_power
>>> pow_10 = make_power_func(10)
>>> pow_10(2)
1024

In this example the make_power_func function takes a number, power, and creates a new function that encapsulates the power value. The make_power_func function also returns the newly defined to_the_power function. We then assign that function to the value of pow_10 and we can then call the function through that new name. We could call the make_power_func function for as many values as we wish and create a power function on the fly.

Multiple inheritance

Python's OOP model allows for multiple inheritance. This can be easily seen in the TemplateView class above:

class TemplateView(TemplateResponseMixin, ContextMixin, View):

When defining a class the parents of that class are defined in parentheses after the class name. The TemplateView class has three explicit parent classes. There would also most likely be some implicit parent classes (such as object which is the parent of all classes in Python, but rarely explicitly defined). The order of the parents matters here because when the super function is called in any method in order to call a parent method, parents are searched in the order defined. So when we call the get_context_data method on the TemplateView class instance, it first looks in TemplateResponseMixin and then moves to ContextMixin (before stopping because the method was found there).

In order to see the parent classes, and the order that they will be called/searched in, you can call the mro method of a class (MRO stands for Method Resolution Order).

>>> from django.views.generic import TemplateView
>>> TemplateView.mro()
[<class 'django.views.generic.base.TemplateView'>, <class 'django.views.generic.base.TemplateResponseMixin'>, <class 'django.views.generic.base.ContextMixin'>, <class 'django.views.generic.base.View'>, <class 'object'>]

This parent list is a predictable and specifically ordered list of parents that defines the order that parents are searched in when calling a method and using super.

4. Deep Dive: Software Architecture Features

Multiple inheritance

Multiple inheritance can be a tricky problem to solve, what do you do if multiple parents define the same method, which one do you call if the method is called from a child class instance?

One algorithm that solves this problem (and the algorithm that is used for MRO in Python) is the C3 linearization algorithm. The C3 linearization algorithm provides a consistent order to the MRO list of parent classes because of this it is deterministic in the order of the parent classes it produces.

5. Hands-on Exercises

Implement a generic class-based view called StaticView that extends from View and provides a get method that will respond with a static HTML page.

Hints

  • Make sure it is reusable. The static HTML should to be specified by each child class-based view of StaticView.
  • Take a look at what the TemplateView does in its get method.

Possible Solution

There are most definitely other ways to solve this problem, but here is one simple solution.

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


class StaticView(View):
    static_html = None

    def get(self, request):
        html = open(self.static_html).read()
        return HttpResponse(html)


class MyStaticView(StaticView):
    static_html = 'static_file.html'

The StaticView class can be extended and the static_html attribute updated. The urls.py file would have an entry for the MyStaticView child class. Because of the way View is implemented and the as_view method, we can also add an entry to our urls that looks like this.

    url(r'^test/$', views.StaticView.as_view(static_html='test.html'))

6. Contribute

Resources