Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Django

Class-based Views Django Tutorial

9 Jan 2018CPOL9 min read 5.9K  
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 post appeared first on Tests4Geeks.

Image 1

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:

Python
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 saves 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?

Python
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:

Python
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:

Python
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:

Python
# 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.

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:

Python
# 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:

Python
# 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:

Python
# 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

Python
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 get_queryset() method:

Customizing the Query

Python
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:

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

When it would be much more natural to write this:

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

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

Customizing the Context Object Name

Python
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().

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
  • DeleteViewGET method shows confirmation screen, POST method deletes 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 its 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)