Class-based Views Django Tutorial

The web framework for perfectionists with deadlines.

Django is all about making programmers more productive. As a web application framework, it provides tools and structure to help web developers get their work done fast.

The reason that developers use application frameworks today is because the vast majority of web programming is extremely repetitive: handling incoming requests, checking session information, authenticating users, CRUD database operations, etc.

The fraction of code that makes your web application unique is very small. Frameworks help developers satisfy one of the key principles of good code design: don’t repeat yourself (stay DRY).

View Functions and the Need for Class-based Views

One of the first tools that the Django project provided to developers was view functions:

def list_user_snippets(request):
    # Show list of a user's code snippets
    # for a Pastebin-like application
    snippets = Snippet.objects.filter(user=request.user)
    context = {'snippets':snippets}
    return render(request, 'mysnippets/snippet_list.html', context)

If you’re using Django view functions, the four lines of code above do a lot of work for you. The function’s incoming argument request is an object created by Django which takes data provided by the server in compliance with the WSGI specification and turns that data into an easy to use Python object.

With the request object you can easily inspect GET parameters, POST data, see if the user is logged in, etc.

The call to Snippet.objects.filter() queries the database for the user’s code snippets using Django’s object relational mapper, and the render function binds the provided context dictionary with a template and returns the template to the Django framework as an HttpResponse object, which Django uses to create a WSGI compliant response to send to the web server.

Using view functions in Django save’s a lot of developer time that can be spent solving new problems instead of solving the same old problems over and over again.

However, when you start building an application, you will pretty quickly start to notice a lot of repetitive code.

For example, what if we now want our app to not only show a user’s code snippets, but to also include a URL where we list all of the existing snippets?

def list_all_snippets(request):
    # Show list of all code snippets
    # for a Pastebin-like application - looking pretty repetitive...
    snippets = Snippet.objects.all()
    context = {'snippets':snippets}
    return render(request, 'mysnippets/snippet_list.html', context)

We create a new view that does basically the same work as the list_user_snippets() view, but now we query for all of the snippets instead of just a specific user’s snippets.

The code is still easy to write, but if you’re obsessed with clean code, you might be worried by where things are headed. It’s likely that our application may end up requiring several more views with extremely similar code.

An even bigger problem is that we might start developing more complex views and then find ourselves with complex views copied all over our codebase with only slight variations to distinguish them from each other.

Let’s look at one more view for our app. We can list code snippets by user and we can list all code snippets. Now we want to list all comments left on the site:

def list_all_comments(request):
    # Show list of all comments - hmmm, looks familiar
    comments = Comment.objects.all()
    context = {'comments':comments}
    return render(request, 'mysnippets/comment_list.html', context)

View functions like list_all_snippets() and list_all_comments() are crying out for a layer of abstraction. The two views are exactly the same, the only difference is that the word ‘snippet’ is replaced with the word ‘comment’. Django does a lot to help programmers write less repetitive code, but if you’re stuck copying and pasting view functions just to change a single word in each view, it becomes glaringly clear that more can be done to keep the code non-repetitive (i.e., “DRY”).

Quick Intro to Class-based Generic Views

The Django core developers realized how many view functions were basically doing the same job with only slight differences. Initially, Django offered function-based generic views, which made it easier to create views for common use cases, but in Django 1.3 the team introduced class-based generic views, which provide more powerful abstractions for creating views.

Through inheritance and mix-ins, class-based generic views provide a host of functionality that can be accessed in only a few lines of code.

Here is the original list_all_snippets() view, rewritten as a class-based view that inherits from Django’s built-in ListView class:

class SnippetListAll(ListView):
      model = Snippet

That’s it! The ListView class handles querying the database and rendering a template. All we need to do is specify which model the view should use. Because our SnippetListAll class is so simple, only overriding one attribute of its parent class, we can actually skip writing the view altogether and just specify the attribute to override in urls.py:

# in urls.py

urlpatterns = [
    url(r'all-snippets', views.ListView.as_view(model=Snippet)),
]

In many cases there is no need to write a view at all, simply providing some parameters to a built-in generic view’s as_view() method can do everything you need.

Learn Django? This tutorial might be helpful: Class-based Views Django TutorialClick To Tweet

Use the Source

Before going further into Django’s generic views, there is an important issue to address: the two examples of class-based views contain so little code that it becomes vital to start understanding what is happening behind the scenes.

With the framework doing more and more of the work, the code becomes seemingly less explicit and it can become frustrating to figure out how to use the generic views that you are inheriting. The key is to never think of the framework as a black box.

Instead of wondering how things work, programming in Django becomes much easier if you’re not afraid to dive into the source.

Here is the code for Django’s ListView in version 1.9:

# https://git.io/vaAhu
# github.com/django - django/views/generic/list

class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
    """
    Render some list of objects, set by `self.model` or `self.queryset`.
    `self.queryset` can actually be any iterable of items, not just a queryset.
    """

ListView provides its functionality by inheriting from MultipleObjectTemplateResponseMixin and BaseListView. The code for these classes is very readable. BaseListView is under 20 lines long, and it defines one method. Here is an excerpt:

# https://git.io/vaAhQ
# github.com/django - django/views/generic/list

class BaseListView(MultipleObjectMixin, View):
    """
    A base view for displaying a list of objects.
    """
    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()

        # ...

        context = self.get_context_data()
        return self.render_to_response(context)

We can follow the inheritance tree for the built-in generic views and discover all of the methods and attributes provided for us, what they do, and how they work. Taking just a few minutes to read the source code can save hours of frustration and help us start answering questions like…

Wait a second, where’s the template?

In both of the generic view examples in the previous section, there is no mention of a template, but we know that we want to render a template, so what’s going on?

A quick look at the ListViews mixin MultipleObjectTemplateResponseMixin answers our question:

# https://git.io/vaxJo
# github.com/django - django/views/generic/list

class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
    template_name_suffix = '_list'

    def get_template_names(self):
        # ...code removed here
        if hasattr(self.object_list, 'model'):
            opts = self.object_list.model._meta
            names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))

        return names

We can see that MultipleObjectTemplateResponseMixin causes ListView to automatically check for a template called appname/modelname_list.html.

You can read the source for all of Django’s generic views on Github or locally in the site-packages directory of your Python installation.

Not only does reading the code take the mystery out of what Django is doing for us, it also helps us gain finer gain control over how we use the built-in class-based generic views.

More Control Over Views

We saw how to customize a generic view by overriding the model attribute on the ListView. That’s the most basic example, but there are a few more common use cases that we should look at.

What if we don’t want to name our template with the convention appname/modelname_list.html? Maybe we have a template named “show_snippets.html”, and it’s in a my_templates directory specified explicitly in the TEMPLATES setting.

Simply override the template_name attribute in our view and we’re good to go:

Changing the template name

class SnippetListAll(ListView):
      model = Snippet
      template_name = "my_templates/show_snippets.html"

The other view our app provided was a “snippets by member” view. To provide this functionality using ListView, we need to control the value of the queryset attribute by overriding the the get_queryset() method:

Customizing the query

class SnippetListByUser(ListView):
      template_name = "my_templates/show_snippets.html"
      def get_queryset(self):
          return Snippet.objects.filter(user=self.request.user)

It is possible to just set queryset directly, but in this case we use get_queryset() so that the query can access the self.request.user object.

There’s one attribute that should probably be overridden in almost every scenario, and that is the context_object_name. By default, ListView renders the template with an object named object_list.

This means that designers end up writing a template that looks like this:

{% for comment in object_list %}
   {{ comment }}
{% endfor %}

When it would be much more natural to write this:

{% for comment in comments %}
   {{ comment }}
{% endfor %}

To make things easier when creating templates, it’s a good idea specify the context_object_name in your view:

Customizing the context object name

class CommentList(ListView):
    model = Comment
    context_object_name = 'comments'

More advanced use cases are very similar to the basic one I’ve illustrated here. For instance, you may want to use the ListView functionality, but you also need to add some extra data for the template to render from the request context. In that case, override the get_context_data() method.

If you’re new to object-oriented programming in Python, it’s helpful to know that you can call super() on a parent object to get the default behavior. So it is possible to return the parent’s method implementation, but called with arguments you’ve provided in your method, and it is possible to call the parent implementation first and get its results, and then modify the results in your method.

The Django generic views source code includes many examples of how to use super().

Learn Django? This tutorial might be helpful: Class-based Views Django TutorialClick To Tweet

More Generic Views

ListView is a good starting point for working with generic views. There are simpler views that can be useful, like TemplateView, and there are more complex views that handle a lot of work for you, like the CreateView and UpdateView.

It doesn’t take long to understand all of Django’s available class-based generic views, and the payoff in time saved from writing boilerplate code is great.

Here is a brief glance at some of the available views that you’ll probably use the most:

  • TemplateView – render a given template
  • RedirectView – redirect to a given URL
  • DetailView – show full details of an object
  • ListView – show a collection of an object
  • CreateView – render a form to create an object, provides validation and updates database
  • UpdateView – render a form to edit an object, provides validation and updates database
  • DeleteVew – GET method show confirmation screen, POST method delete object from database

Django also includes class-based generic views that deal with date-based functionality. Views such as ArchiveIndexView and DateDetailView make it easier to provide functionality such as listing blog posts by date and viewing an entry by it’s publication date.

More Resources

If you’re serious about working with Django, you must learn to use class-based views and the bulit-in generic views. In addition to checking the Tests4Geeks blog for detailed tutorials and in-depth guides, there are a few other resources you may want to check out.

The best resource is the Django source code itself — which is well complemented by the official Django documentation.

An excellent resource for navigating the mix-ins that make up Django’s class-based generic views is the Django class-based views inspector.

Finally, once you’ve mastered the built-in generic views, it’s worth taking a look at a simpler library that provides similar functionality, Django Vanilla Views.

Tom from MDashX develops web apps and automation solutions, he’s been working with Django since version 1.0.

Like the article? Share it please!

It will REALLY help us to make more content here.

12 Shares

  4 Comments

  1. Gayathri Chakravarthy   •  

    Thank you so much! As a Django newbie, I could not figure out how the app seems to pick the template and have been going around in circles trying to figure out the missing link. You just made my day.

  2. James Atadoga   •  

    Thank you very much, Tom! I am django newbie also and most tutorials I’ve read on class-based views didn’t give me the information I wanted to know and this article connects all the loose dots in my mind. Thank you for making me truly understand how class-based views make work easier for django developers.

  3. Robel   •  

    Thanks a lot, I have been going aroung to figured out how the template has been called from ListView. Ohh you made my day.

  4. Redmi   •  

    Thanks a lot for this information

Leave a Reply

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