Class-based views

In Chapter 1 and at the start of this chapter -- in listing 2-1 -- you saw how to define a Django url and make it operate with a Django template without the need of a view method. This was possible due to the django.views.generic.TemplateView class, which is called a class-based view.

Unlike Django view methods backed by standard Python methods that use a Django HttpRequest input parameter and output a Django HttpResponse, class-based views offer their functionality through full-fledged Python classes. This in turn allows Django views to operate with object oriented programming (OOP) principles (e.g. encapsulation, polymorphism and inheritance) leading to greater re-usability and shorter implementation times.

Although Django class-based views represent a more powerful approach to create Django views, they are simply an alternative to the view methods you've used up to this point. If you want to quickly execute business logic on Django requests you can keep using view methods, but for more demanding view requirements (e.g. form processing, boilerplate model queries) class-based views can save you considerable time.

Built-in class-based views

The functionality provided by the django.views.generic.TemplateView class-based view is real a time saver. While it would have been possible to configure a url to execute on an empty view method and then send control to a template, the TemplateView class allows this process to be done in one line.

In addition to the TemplateView class-based view, Django offers many other built-in class-based views to shorten the creation process for common Django view operations using OOP-like principles. Table 2-11 illustrates Django's built-in classes for views.

Table 2-11. Built-in classes for views

Class Description
django.views.generic.View Parent class of all class-based views, providing core functionality.
django.views.generic.TemplateView Allows a url to return the contents of a template, without the need of a view.
django.views.generic.RedirectView Allows a url to perform a redirect, without the need of a view.
django.views.generic.ArchiveIndexView
django.views.generic.YearArchiveView
django.views.generic.MonthArchiveView
django.views.generic.WeekArchiveView
django.views.generic.DayArchiveView
django.views.generic.TodayArchiveView
django.views.generic.DateDetailView
Allows a view to return date-based object results, without the need to explicitly perform Django model queries.
django.views.generic.CreateView
django.views.generic.DetailView
django.views.generic.UpdateView
django.views.generic.DeleteView
django.views.generic.ListView
django.views.generic.FormView
Allows a view to execute Create-Read-Update-Delete (CRUD) operations , without the need to explicitly perform Django model queries.


In the upcoming and final section of this chapter, I'll explain the classes in the top-half of table 2-11 so you can gain a better understanding of the structure and execution process of Django class-based views. The class-based views in bottom half of table 2-11 that involve Django models are described in a separate chapter on Django class-based views models.

Class-based view structure & execution

To create a class-based view you need to create a class that inherits from one of the classes in table 2-11. Listing 2-37 shows a class-based view with this inheritance technique, as well as the corresponding url definition to execute a class-based view.

Listing 2-37 Class-based view inherited from TemplateView with url definition

----------------------------------------------------------------------------
# views.py
from django.views.generic import TemplateView

class AboutIndex(TemplateView):
      template_name = 'index.html'

      def get_context_data(self, **kwargs):
         # **kwargs contains keyword context initialization values (if any)
         # Call base implementation to get a context
         context = super(AboutIndex, self).get_context_data(**kwargs)
         # Add context data to pass to template
         context['aboutdata'] = 'Custom data'
         return context

----------------------------------------------------------------------------
# urls.py
from coffeehouse.about.views import AboutIndex
from django.urls import path

urlpatterns = [
   path('about/index/',AboutIndex.as_view(),{'onsale':True}),
] 

I chose to create a view that inherits from TemplateView first because of its simplicity and because you already know the purpose of this class. The example in listing 2-37 and the first example in this chapter from listing 2-1 produce nearly identical outcomes.

The difference is, listing 2-1 declares a TemplateView class instance directly as part of the url (e.g. TemplateView.as_view(template_name='index.html')) ), where as listing 2-37 declares an instance of a TemplateView sub-class named AboutIndex. Comparing the two approaches, you can get the initial feel for the OOP behavior of class-based views.

The first part in listing 2-37 declares the AboutIndex class-based view which inherits its behavior from the TemplateView class. Notice the class declares the template_name attribute and the get_context_data() method.

The template_name value in the AboutIndex class acts as a default template for the class-based view. But in OOP fashion, this same value can be overridden by providing a value at instance creation (e.g. AboutIndex.as_view(template_name='other.html') to use the other.html template).

The get_context_data method in the AboutIndex class allows you to add context data to the class-view template. Notice the signature of the get_context_data method uses **kwargs to gain access to context initialization values (e.g. declared in the url or parent class-views) and invokes a parent's class get_context_data method using the Python super() method per standard OOP Python practice. Next, the get_context_data method adds the additional context data with the aboutdata key and returns the modified context reference.

Tip See Appendix A - Methods arguments: Default, optional, *args & **kwargs for more details on the use of **kwargs in Python.

In the second part of listing 2-37, you can see how the AboutIndex class-based view is first imported into a urls.py file and then hooked-up to a url definition. Notice how the class-based view is declared on the url definition using the as_view() method. In addition, notice how the url definition declares the url extra option {'onsale':True} which gets passed as context data to the class-based view (i.e. in the **kwargs of the get_context_data method).

Tip All class-based views use the as_view() method to integrate into url definitions.

Now that you have a basic understanding of Django class-based views, listing 2-38 shows another class-based view with different implementation details.

Listing 2-38 Class-based view inherited from View with multiple HTTP handling

----------------------------------------------------------------------------
# views.py
from django.views.generic import View
from django.http import HttpResponse
from django.shortcuts import render

class ContactPage(View):
    mytemplate = 'contact.html'
    unsupported = 'Unsupported operation'
    
    def get(self, request):
        return render(request, self.mytemplate)
    def post(self, request):
        return HttpResponse(self.unsupported)

----------------------------------------------------------------------------
#urls.py
from coffeehouse.contact.views import ContactPage

urlpatterns = [
    url(r'^contact/$',ContactPage.as_view()),
]

The first difference in listing 2-38 is the class-based view inherits its behavior from the general purpose django.views.generic.View class. As outlined in table 2-11, the View class provides the core functionality for all class-based views. So in fact, the TemplateView class used in listing 2-37 is a sub-class of View, meaning class-based views that use TemplateView have access to the same functionalities of class-based views that use View.

The reason you would chose one class over another to implement class-based views is rooted in OOP polymorphism principles. For example, in OOP you can have a class hierarchy Drink→ Coffee → Latte, where a Drink class offers generic functionalities available to Drink, Coffee and Latte instances, a Coffee class offers more specific functionalities applicable to Coffee and Latter instances, and a Latte class offers the most specific functionalities applicable to only Latte instances.

Therefore if you know beforehand you need a class-based view to relinquish control to a template without applying elaborate business logic or custom request & response handling, the TemplateView class offers the quickest path to a solution vs. the more generic View class. Expanding on this same principle, once you start working with Django models and views, you'll come to realize some of the more specialized class-based views in table 2-11 also offer quicker solutions than creating a class-based view that inherits from the general purpose View class. Now that you know the reason why you would chose a View class-based view over a more specialized class, lets break down the functionality in listing 2-38.

Notice the class-based view ContactPage declares two attributes: mytemplate and unsupported. These are generic class attributes and I used the mytemplate name to illustrate there's no relation to the template_name attribute used in listing 2-37 and TemplateView class-based views. Class-based views derived from a TemplateView expect a template_name value and automatically use this template to generate a response. However, class-based views derived from a View class don't expect a specific template, but instead expect you to implement how to generate a response, which is where the get and post methods in listing 2-38 come into play.

The get method is used to handle HTTP GET requests on the view, while the post method is used to HTTP POST requests on the view. This offers a much more modular approach to handle different HTTP operations vs. standard view methods that require explicitly inspecting a request and creating conditionals to handle different HTTP operations. For the moment, don't worry about HTTP GET and HTTP POST view handling, this is explored in greater detail in Django forms where the topic is of greater relevance.

Next, notice both the get and post methods declare a request input, which represents a Django HttpRequest instance just like standard view methods. In both cases, the methods immediately return a response, but it's possible to inspect a request value or execute any business logic before generating a response, just like it can be done in standard view methods.

The get method generates a response with the django.shortcuts.render method and the post method generates a response with the HttpResponse class, both of which are the same techniques used to generate responses in standard view methods . The only minor difference in listing 2-38 is both the render method and HttpResponse class use instance attributes (e.g. self.mytemplate, self.unsupported) to generate the response, but other than this, you're free to return a Django HttpResponse with any of the variations already explained in this chapter (e.g. listing 2-24 response alternatives, table 2-6 shortcut responses)

Finally, the last part in listing 2-38 shows how the ContactPage class-based view is imported into a urls.py file and later hooked up to a url using the as_view() method.

To close out the discussion on class-based views and this chapter, we come to the django.views.generic.RedirectView class. Similar to the TemplateView class-based view which allows you to quickly generate a response without a view method, the RedirectView class-based view allows you to quickly generate an HTTP redirect -- like the ones described in table 2-5 -- without the need of a view method.

The RedirectView class supports four attributes described in the following list:

And with this we conclude our exploration into Django views and urls. In the next two chapters, you'll learn about Django templates and Jinja templates.