Ousseynou Diop
Posted on May 4, 2020
Originally posted on my blog
Introduction
The final project, I am using a phonebook application – Github repo
Applications grow in terms of complexity and we will end up repeating certain functions or patterns again and again.
Django tries to take away some of that monotonous at the model and template layers, but Web developers also experience this boredom at the view level.
Built-in class-based generic views
Django’s generic views were developed to ease that pain. They take certain common idioms and patterns found in view development and abstract them so that you can quickly write common views of data without having to write too much code.
We can recognize certain common tasks, like displaying a list of objects and write code that displays a list of any object. Then the model in question can be passed as an extra argument to the URLconf.
Django ships with generic views to do the following:
- Display list and detail pages for a single object. If we were creating an application to manage conferences then a TalkListView and a RegisteredUserListView would be examples of list views. A single talk page is an example of what we call a « detail » view.
- Present date-based objects in year/month/day archive pages, associated detail, and « latest » pages.
- Allow users to create, update, and delete objects – with or without authorization.
Taken together, these views provide easy interfaces to perform the most common tasks developers encounter.
Generic views of objects
TemplateView certainly is useful, but Django’s generic views really shine when it comes to presenting views of your database content. Because it’s such a common task, Django comes with a handful of built-in generic views that make generating list and detail views of objects incredibly easy.
Let’s start by looking at some examples of showing a list of objects or an individual object.
Here is our models:
# models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Contact(models.Model):
first_name = models.CharField(max_length=150)
last_name = models.CharField(max_length=150)
phone = models.CharField(max_length=150)
email = models.CharField(max_length=150, blank=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.phone
Here are our function-based views, we'll change it later
# views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, get_object_or_404, redirect
from .models import Contact
# Contact list
def contact_list(request):
user = request.user
contacts = Contact.objects.filter(created_by=None)
if user.is_authenticated:
contacts = Contact.objects.filter(created_by=user)
return render(request, "contact/contact_list.html", {"contacts": contacts})
You see that these views do one thing display a list of Phonebook
To change this into a class-based views, add this
# views.py
...
from django.views.generic import ListView
# Contact list
class ContactList(ListView):
model = Contact
...
You don't need to change the template name.
To display the data we need to loop throught a viraibale called object_list.
<!-- contact-list.html -->
...
<div class="row">
<div class="col-md-6 m-auto">
<div class="card card-body">
{% if object_list %}
<ul class="list-group list-group-flush">
{% for contact in object_list %}
<li class="list-group-item">
<a href="{% url 'details' contact.id %}"> {{ contact.email }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No contact</p>
{% endif %}
</div>
</div>
</div>
Making « friendly » template contexts
Instead to use the variable object_list we can use a friendly name contacts.
The context_object_name attribute on a generic view specifies the context variable to use:
# views.py
class ContactList(ListView):
model = Contact
context_object_name = "contacts"
<!-- contact-list.html -->
...
<div class="row">
<div class="col-md-6 m-auto">
<div class="card card-body">
{% if contacts %}
<ul class="list-group list-group-flush">
{% for contact in contacts %}
<li class="list-group-item">
<a href="{% url 'details' contact.id %}"> {{ contact.email }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No contact</p>
{% endif %}
</div>
</div>
</div>
Providing a useful context_object_name is always a good idea. Your coworkers who design templates will thank you.
Filtering
Another common need is filtering, if we wanted, we could use self.request.user to filter using the current user, or other more complex logic.
# views.py
...
class ContactList(ListView):
context_object_name = "contacts"
def get_queryset(self):
user = self.request.user
if user.is_authenticated:
return Contact.objects.filter(created_by=self.request.user)
return Contact.objects.filter(created_by=None)
...
Adding pagination
For some performance reason, we need to paginate our app, that will make it load faster.
Fortunately, Django comes with built-in pagination classes for managing paginating data of your application.
# views.py
...
class ContactList(ListView):
context_object_name = "contacts"
paginate_by = 4 # add this
def get_queryset(self):
user = self.request.user
if user.is_authenticated:
return Contact.objects.filter(created_by=self.request.user)
return Contact.objects.filter(created_by=None)
...
Our template
<!-- contact-list.html -->
...
<div class="row">
<div class="col-md-6 m-auto">
<div class="card card-body">
{% if contacts %}
<ul class="list-group list-group-flush">
{% for contact in contacts %}
<li class="list-group-item">
<a href="{% url 'details' contact.id %}"> {{ contact.email }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No contact</p>
{% endif %}
</div>
<nav aria-label="Page navigation example">
{% if is_paginated %}
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{page_obj.previous_page_number}}"
>«</a
>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#">«</a>
</li>
{% endif %} {% for i in paginator.page_range %} {% if page_obj.number ==
i %}
<li class="page-item"><a class="page-link active">{{ i }}</a></li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ i }}">{{ i }}</a>
</li>
{% endif %} {% endfor %} {% if page_obj.has_next %}
<li class="page-item">
<a href="?page={{page_obj.next_page_number}}" class="page-link"
>»</a
>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link">»</a>
</li>
{% endif %}
</ul>
{% endif %}
</nav>
</div>
</div>
Now run the server and visit http://127.0.0.1:8000/ you should see the page navigation buttons below the contacts.
python manage.py runserver
Conclusion
In this tutorial, we've learned how to work with django class-based views.
You can do more stuff with like Form submitting, we'll cover them next.
Thanks for reading, see you next.
Posted on May 4, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.