Adding AJAX pagination with infinite scroll in Django and jquery

This tutorial will teach you how to implement AJAX pagination to build infinite scroll functionality on the blog home page. 

Infinite scroll is achieved by loading the next page automatically when the user scrolls to the bottom of the page.

Step 1. Add Post model

Suppose we have the Post model.

class Post(models.Model):
    title = models.CharField(max_length=256)
    slug = models.SlugField(unique=True)
    text = models.TextField()
    image = models.ImageField(upload_to='blog/%Y-%m/')

    is_active = models.BooleanField(default=True)
    created_dt = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

The model has a field created_dt, we need this field to sort posts in descending order by created time. So visitors will get fresher posts first.

Step 2. Add view to serve our home page

Open up views.py and add post_list view as below:

views.py

from django.shortcuts import render
from django.core import paginator
from django.template.loader import render_to_string

from . import models

NEWS_COUNT_PER_PAGE = 9

def post_list(request):
    page = int(request.GET.get('page', 1))
    posts = models.Post.objects.filter(is_active=True).order_by('-created_dt')
    p = paginator.Paginator(posts,
                            NEWS_COUNT_PER_PAGE)
    try:
        post_page = p.page(page)
    except paginator.EmptyPage:
        post_page = paginator.Page([], page, p)

    if not request.is_ajax():
        context = {
            'posts': post_page,
        }
        return render(request,
                      'blog/posts.html',
                      context)
    else:
        content = ''
        for post in post_page:
            content += render_to_string('blog/post-item.html',
                                        {'post': post},
                                        request=request)
        return JsonResponse({
            "content": content,
            "end_pagination": True if page >= p.num_pages else False,
        })

The same view will handle the initial page load and the following AJAX requests. When the user initially loads the post list page, we will display the first page of posts. When the user scrolls to the bottom of the page an AJAX request will be sent to load the next page. Each page will contain 9 posts.

In this view, we create a QuerySet to return all posts from the database. Then, we build a Paginator object to paginate the results, retrieving nine posts per page. We get an EmptyPage exception if the requested page is out of range. In that case, we will create an empty page instance of the class django.core.paginator.Page with an empty list: post_page = paginator.Page([], page, p).

We will need to create templates posts.html and post-item.html to render posts.

  1. The template posts.html will extend base.html and it will render posts using post-item.html to avoid repeating code.
  2. Template post-item.html will contain an HTML template for displaying each post. This template will be used in two places:
  • Included in posts.html
  • Used in our view function.

We will use render_to_string function to render a post item with the given context and append the rendered HTML to the content variable. 

Finally, the view post_list will return JSON response with payload: content and end_pagination. The variable end_pagination holds a boolean value, which shows whether it's the last page or not. 

blog/post-item.html

{% load thumbnail %}


<div class="col-md-3 mb-4">
    <div class="card">
        <img src="{% thumbnail post.image '370x250' crop='center' upscale=True %}" class="card-img-top" alt="...">
        <div class="card-body">
            <h5 class="card-title">{{ post.title }}</h5>
            <p class="card-text">
                {{ post.text|striptags|slice:'0:120' }}...
            </p>
            <a href="#" class="btn btn-primary">Read More</a>
        </div>
    </div>
</div>

Step 3. Write client logic using the jquery library for lazy loading

The template posts.html will contain JavaScript code for loading additional pages when scrolling to the bottom.

blog/posts.html

{% extends 'base.html' %}
{% load static %}

{% block title %} Posts {% endblock %}

{% block content %}

    <div class="mt-3">
        <div class="container">
            <div class="row news-list">
                {% for post in posts %}
                    {% include 'blog/post-item.html' %}
                {% endfor %}
            </div>
        </div>
    </div>

    <script>

        $(document).ready(function () {
            window.news_index = '{% url 'blog:posts' %}';

            var page = 1;
            var block_request = false;
            var end_pagination = false;

            $(window).scroll(function () {
                var margin = $(document).height() - $(window).height() - 200;

                if ($(window).scrollTop() > margin && end_pagination === false && block_request === false) {
                    block_request = true;
                    page += 1;

                    $.ajax({
                        type: 'GET',
                        url: window.news_index,
                        data: {
                            "page": page
                        },
                        success: function (data) {
                            if (data.end_pagination === true) {
                                end_pagination = true;
                            } else {
                                block_request = false;
                            }
                            $('.news-list').append(data.content);
                        }
                    })
                }
            });
        })
    </script>
{% endblock %}

In JavaScript code we define the following variables:

  1. page: Stores the current page number. Initially, it equals 1.
  2. block_request: Prevents us from sending additional requests while an AJAX request is in progress.
  3. end_pagination: Allows us to know whether the user is on the last page. If we get data["end_pagination"] as true in the AJAX response, we change this variable's value to true. And it will stop sending additional AJAX requests because we will assume that there are no more results.

We will use the event  $(window).scroll() and define the handler function for it.

We send AJAX request only if: 

  • The user is closer than 200 pixels to the bottom of the page
  • There is no other AJAX request is being sent (block_request  is false)
  • We did not reach the last page (end_pagination is false).

Then we increment the page count (page += 1) and set block_request to true.

When we get the response from the server there are two scenarios:

  1. We loaded the last page (data.end_pagination is true). Then we will set the variable end_pagination to true to prevent the following AJAX request from being sent.
  2. data.end_pagination is false. Then we will set block_request to false.

After that, we will append response content to the end of the container with the class .news-list.

$('.news-list').append(data.content);

 

Conclusion

In this tutorial, we showed how to implement endless scroll functionality with Django's awesome built-in paginator and jquery AJAX requests. As you can see it is simple.

Thanks for reading!