Pagination in Django

Pagination in Django

Find code here.

Django Constructs

When implementing pagination in Django, rather than re-inventing the logic required for pagination, you’ll work with the following constructs:

  1. Paginator – splits a Django QuerySet or list into chunks of Page objects.
  2. Page – holds the actual paginated data along with pagination metadata.
  • Paginator – splits a Django QuerySet or list into chunks of Page objects.
  • Page – holds the actual paginated data along with pagination metadata.
  • Let’s look at some quick examples.

    Paginator

    from django.contrib.auth.models import User
    
    
    for num in range(43):
    User.objects.create(username=f"{num}")
    

    Here, we created 43 User objects.

    Next, we’ll import the Paginator class and create a new instance:

    from django.contrib.auth.models import User
    from django.core.paginator import Paginator
    
    users = User.objects.all()
    
    paginator = Paginator(users, 10)
    
    print(paginator.num_pages)  # => 5
    

    The Paginator class takes four parameters:

    1. object_list – any object with a count() or __len__() method, like a list, tuple, or QuerySet
    2. per_page – maximum number of items to include on a page
    3. orphans (optional) – used to prevent the last page from having very few items, defaults to 0
    4. allow_empty_first_page (optional) – as implied by the name, you can raise an EmtpyPage error if you disallow the first page from being empty by setting the argument to False, defaults to True
  • object_list – any object with a count() or __len__() method, like a list, tuple, or QuerySet
  • per_page – maximum number of items to include on a page
  • orphans (optional) – used to prevent the last page from having very few items, defaults to 0
  • allow_empty_first_page (optional) – as implied by the name, you can raise an EmtpyPage error if you disallow the first page from being empty by setting the argument to False, defaults to True
  • So, in the above example, we sliced the users into pages (or chunks) of ten. The first four pages will have ten users while the last page will have three.

    The Paginator class has the following attributes:

    1. count – total number of objects
    2. num_pages – total number of pages
    3. page_range – range iterator of page numbers
  • count – total number of objects
  • num_pages – total number of pages
  • page_range – range iterator of page numbers
  • For consistent paginated results, the QuerySet or the model should be ordered.

    If you’d prefer to not have just three users on the final page, you can use the orphans argument like so to add the final three users to the previous page:

    from django.contrib.auth.models import User
    from django.core.paginator import Paginator
    
    users = User.objects.all()
    
    paginator = Paginator(users, 10, orphans=3)
    
    print(paginator.num_pages)  # => 4
    

    So, when the number of remaining objects for the last page is less than or equal to the value of orphans, those objects will be added to the previous page.

    Page

    After the Django QuerySet has been broken up into Page objects. We can then use the page() method to access the data for each page by passing the page number to it:

    from django.contrib.auth.models import User
    from django.core.paginator import Paginator
    
    users = User.objects.all()
    
    paginator = Paginator(users, 10)
    
    page_obj = paginator.page(1)
    
    print(page_obj)  # => <Page 1 of 5>
    

    Here, page_obj gives us a page object that represents the first page of results. This can then be used in your templates.

    Note that we didn’t literally create a Page instance. Instead, we obtained the instance from the Paginator class.

    What happens if the page doesn’t exist?

    from django.contrib.auth.models import User
    from django.core.paginator import Paginator
    
    users = User.objects.all()
    
    paginator = Paginator(users, 10)
    
    page_obj = paginator.page(99)
    

    You should see:

        raise EmptyPage(_('That page contains no results'))
    django.core.paginator.EmptyPage: That page contains no results
    

    Thus, it’s a good idea to catch an EmptyPage exception like so:

    from django.contrib.auth.models import User
    from django.core.paginator import EmptyPage, Paginator
    
    users = User.objects.all()
    
    paginator = Paginator(users, 10)
    
    try:
    page_obj = paginator.page(99)
    except EmptyPage:
    # Do something
    pass
    

    You may want to catch a PageNotAnInteger exception as well.

    For more on this, review the Exceptions section from the Paginator documentation.

    That said, if you’d prefer not to deal with the EmptyPage or PageNotAnInteger exceptions explicitly, you could use get_page() method instead of page():

    from django.contrib.auth.models import User
    from django.core.paginator import Paginator
    
    users = User.objects.all()
    
    paginator = Paginator(users, 10)
    
    page_obj = paginator.get_page(99)
    
    print(page_obj)  # => <Page 5 of 5>
    

    So, even though the number 99 is beyond the range, the last page will be returned.

    Also, if the page isn’t a valid number get_page() will return, by default, the first page:

    from django.contrib.auth.models import User
    from django.core.paginator import Paginator
    
    users = User.objects.all()
    
    paginator = Paginator(users, 10)
    
    page_obj = paginator.get_page('foo')
    
    print(page_obj)  # => <Page 1 of 5>
    

    Therefore, both methods — page() or get_page() — can be used depending on your preferences. The examples shown in this article will use page().

    The Page object has several attributes and methods that can be used while constructing your template:

    1. number – shows the page number for a given page
    2. paginator – displays the associated Paginator object
    3. has_next() – returns True if there’s a next page
    4. has_previous() – – returns True if there’s a previous page
    5. next_page_number() – returns the number of the next page
    6. previous_page_number() – returns the number of the previous page
  • number – shows the page number for a given page
  • paginator – displays the associated Paginator object
  • has_next() – returns True if there’s a next page
  • has_previous() – – returns True if there’s a previous page
  • next_page_number() – returns the number of the next page
  • previous_page_number() – returns the number of the previous page
  • Function-based Views

    Next, let’s look at how to work with pagination in function-based views:

    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    from django.shortcuts import render
    
    from . models import Employee
    
    
    def index(request):
    object_list = Employee.objects.all()
    page_num = request.GET.get('page', 1)
    
    paginator = Paginator(object_list, 6) # 6 employees per page
    
    
    try:
    page_obj = paginator.page(page_num)
    except PageNotAnInteger:
    # if page is not an integer, deliver the first page
    page_obj = paginator.page(1)
    except EmptyPage:
    # if the page is out of range, deliver the last page
    page_obj = paginator.page(paginator.num_pages)
    
    return render(request, 'index.html', {'page_obj': page_obj})
    

    Here, we:

    1. Defined a page_num variable from the URL.
    2. Instantiated the Paginator class passing it the required parameters, the employees QuerySet and the number of employees to be included on each page.
    3. Generated a page object called page_obj, which contains the paginated employee data along with metadata for navigating to the previous and next pages.
  • Defined a page_num variable from the URL.
  • Instantiated the Paginator class passing it the required parameters, the employees QuerySet and the number of employees to be included on each page.
  • Generated a page object called page_obj, which contains the paginated employee data along with metadata for navigating to the previous and next pages.
  • Class-based Views

    Example of implementing pagination in a class-based view:

    from django.views.generic import ListView
    
    from . models import Employee
    
    
    class Index(ListView):
    model = Employee
    context_object_name = 'employees'
    paginate_by = 6
    template_name = 'index.html'
    

    Templates

    Working with pagination in the template is where things start to get interesting, as there are several different implementations. In this article, we’ll look at three different implementations, each showing a different way in which to navigate to the previous and next pages.

    So, in this example, we have “Previous” and “Next” links that the end-user can click to move from page to page.

    index.html:

    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <title>Pagination in Django</title>
    </head>
    <body>
    <div class="container">
    <h1 class="text-center">List of Employees</h1>
    <hr>
    
    <ul class="list-group list-group-flush">
    {% for employee in page_obj %}
    <li class="list-group-item">{{ employee }}</li>
    {% endfor %}
    </ul>
    
    <br><hr>
    
    {% include "pagination.html" %}
    </div>
    </body>
    </html>
    

    pagination.html:

    <div>
    <span>
    {% if page_obj.has_previous %}
    <a href="?page={{ page_obj.previous_page_number }}">Previous</a>
    {% endif %}
    <span>
    Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
    </span>
    {% if page_obj.has_next %}
    <a href="?page={{ page_obj.next_page_number }}">Next</a>
    {% endif %}
    </span>
    </div>
    

    Keep in mind that the pagination.html template can be reused across many templates.

    Flavor 2

    pagination.html:

    {% if page_obj.has_previous %}
    <a href="?page={{ page_obj.previous_page_number }}">Previous</a>
    {% else %}
    <a>Previous</a>
    {% endif %}
    
    {% for i in page_obj.paginator.page_range %}
    {% if page_obj.number == i %}
    <a href="#">{{ i }} </a>
    {% else %}
    <a href="?page={{ i }}">{{ i }}</a>
    {% endif %}
    {% endfor %}
    
    {% if page_obj.has_next %}
    <a href="?page={{ page_obj.next_page_number }}">Next</a>
    {% else %}
    <a>Next</a>
    {% endif %}
    

    This flavor presents all the page numbers in the UI, making it easier to navigate to different pages.

    Flavor 3

    pagination.html:

    {% if page_obj.has_previous %}
    <a href="?page={{ page_obj.previous_page_number }}">« Previous page</a>
    
    {% if page_obj.number > 3 %}
    <a href="?page=1">1</a>
    {% if page_obj.number > 4 %}
    <span>...</span>
    {% endif %}
    {% endif %}
    {% endif %}
    
    {% for num in page_obj.paginator.page_range %}
    {% if page_obj.number == num %}
    <a href="?page={{ num }}">{{ num }}</a>
    {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
    <a href="?page={{ num }}">{{ num }}</a>
    {% endif %}
    {% endfor %}
    
    {% if page_obj.has_next %}
    {% if page_obj.number < page_obj.paginator.num_pages|add:'-3' %}
    <span>...</span>
    <a href="?page={{ page_obj.paginator.num_pages }}">{{ page_obj.paginator.num_pages }}</a>
    {% elif page_obj.number < page_obj.paginator.num_pages|add:'-2' %}
    <a href="?page={{ page_obj.paginator.num_pages }}">{{ page_obj.paginator.num_pages }}</a>
    {% endif %}
    
    <a href="?page={{ page_obj.next_page_number }}">Next Page »</a>
    {% endif %}
    

    If you have a large number of pages, you may want to look at this third and final flavor.

    Similar Posts

    Leave a Reply

    Your email address will not be published. Required fields are marked *