
Chapter 8. Raising Your Productivity with CBV
Class-based views (CBVs) are views generated from models. In simple terms, we can say that these are like ModelForms, in that they simplify the view and work for common cases.
CRUD is the short form we use when referring to the four major operations performed on a database: create, read, update, and delete. CBV is the best way to create pages that perform these actions very quickly.
Creating forms for creating and editing a model or database table data is a very repetitive part of the job of a developer. They may spend a lot of time in doing this properly (validation, prefilled fields, and so on). With CBV, Django allows a developer to perform CRUD operations for a model in less than 10 minutes. They also have an important advantage: if the model evolves and CBVs were well done, changing the model will automatically change the CRUD operations within the website. In this case, adding a line in our models allows us to save tens or hundreds of lines of code.
CBVs still have a drawback. They are not very easy to customize with advanced features or those that are not provided. In many cases, when you try to perform a CRUD operation that has some peculiarities, it is better to create a new view.
You might ask why we did not directly study them—we could have saved a lot of time, especially when adding a developer in the database. This is because these views are generic. They are suitable for simple operations that do not require a lot of changes. When we need a complex form, CBVs will not be useful and will even extend the duration of programming.
We should use CBVs because they allow us to save a lot of time that would normally be used in running CRUD operations on models.
In this chapter, we will make the most of our TasksManager
application. Indeed, we will enjoy the time savings offered by the CBVs to move quickly with this project. If you do not understand the functioning of CBVs immediately, it doesn't matter. What we have seen so far in previous chapters already allows us to make websites.
In this chapter, we will try to improve our productivity by covering the following topics:
- We will use the
CreateView
CBV to quickly build the page to add projects - We will see later how to display a list of objects and use the paging system
- We will then use the
DetailView
CBV to display the project information - We will then learn how to change the data in a record with the
UpdateView
CBV - We will learn how to change the form generated by a CBV
- We will then create a page to delete a record
- Then, we will eventually create a child class of
UpdateView
to make using it more flexible in our application
The CreateView CBV
The CreateView
CBV allows you to create a view that will automatically generate a form based on a model and automatically save the data in this form. It can be compared to ModelForm, except that we do not need to create a view. Indeed, all the code for this will be placed in the urls.py
file except in special cases.
An example of minimalist usage
We will create a CBV that will allow us to create a project. This example aims to show that you can save even more time than with Django forms. We will be able to use the template used for the creation of forms in the previous chapter's project. Now, we will change our create_project
URL as follows:
url (r'^create_project$', CreateView.as_view(model=Project, template_name="en/public/create_project.html", success_url = 'index'), name="create_project"),
We will add the following lines at the beginning of the urls.py
file:
from django.views.generic import CreateView from TasksManager.models import Project
In our new URL, we used the following new features:
CreateView.as_view
: We call the methodas_view
of the CBVCreateView
. This method returns a complete view to the user. Moreover, we return multiple parameters in this method.model
: This defines the model that will apply the CBV.template_name
: This defines the template that will display the form. As the CBV usesModelForm
, we do not need to change ourcreate_project.html
template.success_url
: This defines the URL to which we will be redirected once the change has been taken into account. This parameter is not very DRY because we cannot use thename
property of the URL. When we extend our CBV, we will see how to use the name of the URL to be redirected.
That's all! The three lines that we have added to the urls.py
file will perform the following actions:
- Generate the form
- Generate the view that sends the form to the template with or without errors
- Data is sent by the user
We just used one of the most interesting features of Django. Indeed, with only three lines, we have been doing what would have taken more than a hundred lines without any framework. We will also write the CBV that will allow us to add a task. Have a look at the following code:
from TasksManager.models import Project, Task url (r'^create_task$', CreateView.as_view(model=Task, template_name="en/public/create_task.html", success_url = 'index'), name="create_task"),
We then need to duplicate the create_project.html
template and change the link in the base.html
template. Our new view is functional, and we used the same template for project creation. This is a common method because it saves a lot of time for the developer, but there is a more rigorous way to proceed.
To test the code, we can add the following link to the end of the article_content
block of the index.htm
l template:
<a href="{% url "create_task" %}">Create task</a>
Working with ListView
ListView
is a CBV that displays a list of records for a given model. The view is generated to send a template object from which we view the list.
An example of minimalist usage
We will look at an example displaying the list of projects and create a link to the details of a project. To do this, we must add the following lines in the urls.py
file:
from TasksManager.models import Project from django.views.generic.list import ListView
Add the following URLs to the file:
url (r'^project_list$', ListView.as_view(model=Project, template_name="en/public/project_list.html"), name="project_list"),
We will create the template that will be used to display the results in a tabular form by adding the following lines in the article_content
block after extending the base.html
template:
<table> <tr> <th>Title</th> <th>Description</th> <th>Client name</th> </tr> {% for project in object_list %} <tr> <td>{{ project.title }}</td> <td>{{ project.description }}</td> <td>{{ project.client_name }}</td> </tr> {% endfor %} </table>
We created the same list as in Chapter 6, Getting a Model's Data with Querysets, about the queryset. The advantage is that we used a lot less lines and we did not use any view to create it. In the next part, we will implement paging by extending this CBV.
Extending ListView
It is possible to extend the possibilities of the ListView CBV and customize them. This allows us to adapt the CBV to the needs of the websites. We can define the same elements as in the parameters in the as_view
method, but it will be more readable and we can also override the methods. Depending on the type of CBV, spreading them allows you to:
- Change the model and template as we did in the URL
- Change the queryset to be executed
- Change the name of the object sent to the template
- Specify the URL that will redirect the user
We will expand our first CBV by modifying the list of projects that we have done. We will make two changes to this list by sorting by title and adding pagination. We will create the ListView.py
file in the views/cbv
module. This file will contain our customized listView
. It is also possible to choose the architecture. For example, we could create a file named project.py
to store all the CBVs concerning the projects. This file will contain the following code:
from django.views.generic.list import ListView
# In this line, we import the ListView
class
from TasksManager.models import Project
class Project_list(ListView):
# In this line, we create a class that extends the ListView class.
model=Project
template_name = 'en/public/project_list.html'
# In this line, we define the template_name the same manner as in the urls.py file.
paginate_by = 5
In this line, we define the number of visible projects on a single page.
def get_queryset(self):
In this line, we override the get_queryset() method to return our queryset.
queryset=Project.objects.all().order_by("title")
return queryset
We could also have set the queryset in the following manner:
queryset=Project.objects.all().order_by("title")
However, it may be useful to create a class that can be adapted to many cases. For the Project_list
class to be interpreted in the URLs, we need to change our imports by adding the following line:
from TasksManager.views.cbv.ListView import Project_list
You must then change the URL. In this urls.py
file, we will use the Project_list
object without any parameters, as shown in the following code snippet; they are all defined in the ListView.py
file:
url (r'^project_list$', Project_list.as_view(), name="project_list"),
From now on, the new page is functional. If we test it, we will realize that only the first five projects are displayed. Indeed, in the Project_list
object, we defined a pagination of five items per page. To navigate through the list, we need to add the following code in the template before the end of the article_content
block:
{% if is_paginated %} <div class="pagination"> <span> {% if page_obj.has_previous %} <a href="{% url "project_list" %}?page={{ page_obj.previous_page_number }}">Previous</a> {% endif %} <span style="margin-left:15px;margin-right:15px;"> Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. </span> {% if page_obj.has_next %} <a href="{% url "project_list" %}?page={{ page_obj.next_page_number }}">Next</a> {% endif %} </span> </div> {% endif %}
This part of the template allows us to create links to the preceding and following pages at the bottom of the page. With this example, we created a sorted list of projects with pagination very quickly. The extending of CBVs can be very convenient and allows us to adapt to more complex uses. After this complete example, we will create a CBV to display a list of developers. This list will be useful later in the book. We must add the following URL after importing the ListView
class:
url (r'^developer_list$', ListView.as_view(model=Developer, template_name="en/public/developer_list.html"), name="developer_list"),
We then use an inherited template of base.html
and put the following code in the article_content
block:
<table> <tr> <td>Name</td> <td>Login</td> <td>Supervisor</td> </tr> {% for dev in object_list %} <tr> <td><a href="">{{ dev.name }}</a></td> <td>{{ dev.login }}</td> <td>{{ dev.supervisor }}</td> </tr> {% endfor %} </table>
We will notice that the name of the developer is an empty link. You should refill it when we create the page that displays the details of the developer. This is what we will do in the next section with DetailView
.
The DetailView CBV
The DetailView
CBV allows us to display information from a registration model. This is the first CBV we will study that has URL parameters. In order to view the details of a record, it will send its ID to the CBV. We will study some examples.
An example of minimalist usage
First, we will create a page that will display the details of a task. For this, we will create the URL by adding these lines in the urls.py
file:
from django.views.generic import DetailView from TasksManager.models import Task url (r'^task_detail_(?P<pk>\d+)$', DetailView.as_view(model=Task, template_name="en/public/task_detail.html"), name="task_detail"),
In this URL, we added the parameter-sending aspect. We have already discussed this type of URL in an earlier chapter when we covered querysets.
Note
This time, we really need to name the parameter pk
; otherwise, the CBV will not work. pk
means primary key, and it will contain the ID of the record you want to view.
Regarding the template, we will create the en/public/task_detail.html
template and place the following code in the article_content
block:
<h4> {{ object.title }} </h4> <table> <tr> <td>Project : {{ object.project }}</td> <td>Developer : {{ object.app_user }}</td> </tr> <tr> <td>Importence : {{ object.importence }}</td> <td>Time elapsed : {{ object.time_elapsed }}</td> </tr> </table> <p> {{ object.description }} </p>
In this code, we refer to the foreign keys Developer
and Project
. Using this syntax in the template, we call the __ unicode__()
of the model in question. This enables the title of the project to be displayed. To test this piece of code, we need to create a link to a parameterized URL. Add this line to your index.html
file:
<a href="{% url "task_detail" "1" %}">Detail first view</a><br />
This line will allow us to see the details of the first task. You can try to create a list of tasks and a link to DetailView
in each row of the table. This is what we will do.
Extending DetailView
We will now create the page that displays the details of a developer and his/her tasks. To get it done, we'll override the DetailView
class by creating a DetailView.py
file in the views/cbv
module and add the following lines of code:
from django.views.generic import DetailView from TasksManager.models import Developer, Task class Developer_detail(DetailView): model=Developer template_name = 'en/public/developer_detail.html' def get_context_data(self, **kwargs): # This overrides the get_context_data() method. context = super(Developer_detail, self).get_context_data(**kwargs) # This allows calling the method of the super class. Without this line we would not have the basic context. tasks_dev = Task.objects.filter(developer = self.object) # This allows us to retrieve the list of developer tasks. We use self.object, which is a Developer type object already defined by the DetailView class. context['tasks_dev'] = tasks_dev # In this line, we add the task list to the context. return context
We need to add the following lines of code to the urls.py
file:
from TasksManager.views.cbv.DetailView import Developer_detail url (r'^developer_detail_(?P<pk>\d+)$', Developer_detail.as_view(), name="developer_detail"),
To see the main data and develop tasks, we create the developer_detail.html
template. After extending from base.html
, we must enter the following lines in the article_content
block:
<h4> {{ object.name }} </h4> <span>Login : {{ object.login }}</span><br /> <span>Email : {{ object.email }}</span> <h3>Tasks</h3> <table> {% for task in tasks_dev %} <tr> <td>{{ task.title }}</td> <td>{{ task.importence }}</td> <td>{{ task.project }}</td> </tr> {% endfor %} </table>
This example has allowed us to see how to send data to the template while using CBVs.
The UpdateView CBV
UpdateView
is the CBV that will create and edit forms easily. This is the CBV that saves more time compared to developing without the MVC pattern. As with DetailView
, we will have to send the logins of the record to the URL. To address UpdateView
, we will discuss two examples:
- Changing a task for the supervisor to be able to edit a task
- Reducing the time spent to perform a task to develop
An example of minimalist usage
This example will show how to create the page that will allow the supervisor to modify a task. As with other CBVs, we will add the following lines in the urls.py
file:
from django.views.generic import UpdateView url (r'^update_task_(?P<pk>\d+)$', UpdateView.as_view(model=Task, template_name="en/public/update_task.html", success_url="index"), name="update_task"),
We will write a very similar template to the one we used for CreateView
. The only difference (except the button text) will be the action
field of the form, which we will define as empty
. We will see how to fill the field at the end of this chapter. For now, we will make use of the fact that browsers submit the form to the current page when the field is empty. It remains visible so users can write the content to include in our article_content
block. Have a look at the following code:
<form method="post" action=""> {% csrf_token %} <table> {{ form.as_table }} </table> <p><input type="submit" value="Update" /></p> </form>
This example is really simple. It could have been more DRY if we entered the name of the URL in the success_url
property.
Extending the UpdateView CBV
In our application, the life cycle of a task is the following:
- The supervisor creates the task without any duration
- When the developer has completed the task, they save their working time
We will work on the latter point, where the developer can only change the duration of the task. In this example, we will override the UpdateView
class. To do this, we will create an UpdateView.py
file in the views/cbv
module. We need to add the following content:
from django.views.generic import UpdateView from TasksManager.models import Task from django.forms import ModelForm from django.core.urlresolvers import reverse class Form_task_time(ModelForm): # In this line, we create a form that extends the ModelForm. The UpdateView and CreateView CBV are based on a ModelForm system. class Meta: model = Task fields = ['time_elapsed'] # This is used to define the fields that appear in the form. Here there will be only one field. class Task_update_time(UpdateView): model = Task template_name = 'en/public/update_task_developer.html' form_class = Form_task_time # In this line, we impose your CBV to use the ModelForm we created. When you do not define this line, Django automatically generates a ModelForm. success_url = 'public_empty' # This line sets the name of the URL that will be seen once the change has been completed. def get_success_url(self): # In this line, when you put the name of a URL in the success_url property, we have to override this method. The reverse() method returns the URL corresponding to a URL name. return reverse(self.success_url)
We may use this CBV with the following URL:
from TasksManager.views.cbv.UpdateView import Task_update_time url (r'^update_task_time_(?P<pk>\d+)$', Task_update_time.as_view(), name = "update_task_time"),
For the update_task_developer.html
template, we just need to duplicate the update_task.html
template and modify its titles.
The DeleteView CBV
The DeleteView
CBV can easily delete a record. It does not save a lot of time compared to a normal view, but it cannot be burdened with unnecessary views. We will show an example of task deletion. For this, we need to create the DeleteView.py
file in the views/cbv
module. Indeed, we need to override it because we will enter the name of the URL that we want to redirect. We can only put the URL in success_url
, but we want our URL to be as DRY as possible. We will add the following code in the DeleteView.py
file:
from django.core.urlresolvers import reverse from django.views.generic import DeleteView from TasksManager.models import Task class Task_delete(DeleteView): model = Task template_name = 'en/public/confirm_delete_task.html' success_url = 'public_empty' def get_success_url(self): return reverse(self.success_url)
In the preceding code, the template will be used to confirm the deletion. Indeed, the DeleteView
CBV will ask for user confirmation before deleting. We will add the following lines in the urls.py
file to add the URL of the deletion:
from TasksManager.views.cbv.DeleteView import Task_delete url(r'task_delete_(?P<pk>\d+)$', Task_delete.as_view(), name="task_delete"),
To finish our task suppression page, we will create the confirm_delete_task.html
template by extending base.html
with the following content in the article_content
block:
<h3>Do you want to delete this object?</h3> <form method="post" action=""> {% csrf_token %} <table> {{ form.as_table }} </table> <p><input type="submit" value="Delete" /></p> </form>
Going further by extending the CBV
CBVs allow us to save a lot of time during page creation by performing CRUD actions with our models. By extending them, it is possible to adapt them to our use and save even more time.
Using a custom class CBV update
To finish our suppression page, in this chapter, we have seen that CBVs allow us to not be burdened with unnecessary views. However, we have created many templates that are similar, and we override the CBV only to use the DRY URLs. We will fix these small imperfections. In this section, we will create a CBV and generic template that will allow us to:
- Use this CBV directly in the
urls.py
file - Enter the
name
property URLs for redirection - Benefit from a template for all uses of these CBVs
Before writing our CBV, we will modify the models.py
file, giving each model a verbose_name
property and verbose_name_plural
. For this, we will use the Meta
class. For example, the Task
model will become the following:
class Task(models.Model): # fields def __str__(self): return self.title class Meta: verbose_name = "task" verbose_name_plural = "tasks"
We will create an UpdateViewCustom.py
file in the views/cbv
folder and add the following code:
from django.views.generic import UpdateView from django.core.urlresolvers import reverse class UpdateViewCustom(UpdateView): template_name = 'en/cbv/UpdateViewCustom.html' # In this line, we define the template that will be used for all the CBVs that extend the UpdateViewCustom class. This template_name field can still be changed if we need it. url_name="" # This line is used to create the url_name property. This property will help us to define the name of the current URL. In this way, we can add the link in the action attribute of the form. def get_success_url(self): # In this line, we override the get_success_url() method by default, this method uses the name URLs. return reverse(self.success_url) def get_context_data(self, **kwargs): # This line is the method we use to send data to the template. context = super(UpdateViewCustom, self).get_context_data(**kwargs) # In this line, we perform the super class method to send normal data from the CBV UpdateView. model_name = self.model._meta.verbose_name.title() # In this line, we get the verbose_name property of the defined model. context['model_name'] = model_name # In this line, we send the verbose_name property to the template. context['url_name'] = self.url_name \ # This line allows us to send the name of our URL to the template. return context
We then need to create the template that displays the form. For this, we need to create the UpdateViewCustom.html
file and add the following content:
{% extends "base.html" %} {% block title_html %} Update a {{ model_name }} <!-- In this line, we show the type of model we want to change here. --> {% endblock %} {% block h1 %} Update a {{ model_name }} {% endblock %} {% block article_content %} <form method="post" action="{% url url_name object.id %}"> <!-- line 2 --> <!-- In this line, we use our url_name property to redirect the form to the current page. --> {% csrf_token %} <table> {{ form.as_table }} </table> <p><input type="submit" value="Update" /></p> </form> {% endblock %}
To test these new CBVs, we will change the update_task
URL in the following manner:
url (r'^update_task_(?P<pk>\d+)$', UpdateViewCustom.as_view(model=Task, url_name="update_task", success_url="public_empty"), name="update_task"),
The following is a screenshot that shows what the CBV will display:

Summary
In this chapter, we have learned how to use one of the most powerful features of Django: CBVs. With them, developers can run efficient CRUD operations.
We also learned how to change CBVs to suit our use by adding pagination on a list of items or displaying the work of a developer on the page that displays the information for this user.
In the next chapter, we will learn how to use session variables. We will explore this with a practical example. In this example, we will modify the task list to show the last task accessed.