DEV Community

Cover image for Django class-based views with pagination
Ousseynou Diop
Ousseynou Diop

Posted on • Updated on

Django class-based views with pagination

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
Enter fullscreen mode Exit fullscreen mode

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})
Enter fullscreen mode Exit fullscreen mode

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
...
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
<!-- 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>
Enter fullscreen mode Exit fullscreen mode

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)
...
Enter fullscreen mode Exit fullscreen mode

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)
...
Enter fullscreen mode Exit fullscreen mode

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}}"
            >&laquo;</a
          >
        </li>
        {% else %}
        <li class="page-item disabled">
          <a class="page-link" href="#">&laquo;</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"
            >&raquo;</a
          >
        </li>
        {% else %}
        <li class="page-item disabled">
          <a class="page-link">&raquo;</a>
        </li>
        {% endif %}
      </ul>
      {% endif %}
    </nav>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (4)

Collapse
 
xenonrazels profile image
Sudhan Regmi

nice

Collapse
 
xenonrazels profile image
Sudhan Regmi

kjnkn

Collapse
 
kariukijoni profile image
Kariuki

This is cool, thanks

Collapse
 
rakibulislamdigonto profile image
RakibulIslamDigonto

oh, it's good for me