Class-based Views Django Tutorial
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”).
Recommendation
If your company is hiring a Python developer, check out this Python test prepared by our professionals. It will allow you to identify the best talents very easily!
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.
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()
.
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.
Coding skills assessment
If you're looking to hire software engineers and want to ensure that you only interview qualified candidates, we've created coding tests that can significantly simplify your hiring process:
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.
Hi. Thanks!
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.
Thanks a lot, I have been going aroung to figured out how the template has been called from ListView. Ohh you made my day.
Thanks a lot for this information
fff
Great article!
Thank you for this article. Helped understand the concept in easy language. I appreciate it.
There is a spelling error in the section- ‘More Generic Views’. Its written ‘DeleteVew’ instead of ‘DeleteView ‘.