Django ajax pagination couldn’t run properly

I would like to implement pagination in django using jquery ajax. I am trying in a way, but it;s not working. Here is my views.py like that:

def post_list(request):
posts = Post.published.all()

results_per_page = 3
paginator = Paginator(posts, results_per_page)

page_number = request.GET.get('page')
posts = paginator.get_page(page_number)
if request.is_ajax():
    posts_html = render_to_string('posts.html',
                {'posts': posts}
    )
    data_dict = {
        'posts_html': posts_html
    }
    return JsonResponse(data=data_dict)
    
return render(request, 'post_list.html', {'posts':posts})

And my pagination.py is like following:

<nav id="pagination">
<ul class="pagination">
{% if posts.has_previous %}
<li class="page-item">
  <a class="page-link" href="{{ request.path }}?page={{ posts.previous_page_number }}" aria-label="Previous">
    <span aria-hidden="true">&laquo;</span>
    <span class="sr-only">Previous</span>
  </a>
</li>
{% endif %}
{% for i in posts.paginator.page_range %}
{% if posts.number == i %}
<li class="page-item active">
  <a class="page-link" href="{{ request.path }}?page={{ i }}" >{{ i }}</a>
</li>
{% else %}
<li class="page-item">
  <a class="page-link" href="{{ request.path }}?page={{ i }}" >{{ i }}</a>
</li>
{% endif %}
{% endfor %}
{% if posts.has_next %}
<li class="page-item">
  <a class="page-link" href="{{ request.path }}?page={{ posts.next_page_number }}" aria-label="Next">
    <span aria-hidden="true">&raquo;</span>
    <span class="sr-only">Next</span>
  </a>
</li>
{% endif %}

And my js file:

{% block js %}
<script type="text/javascript">
  function ajaxPagination() {
    $('#pagination a.page-link').each((index, el) => {
        $(el).click(function(e) {
            e.preventDefault()
            let page_url = $(el).attr('href')
            console.log(page_url)
            $.ajax({
                type: 'GET',
                url: page_url,
                dataType: 'json',
                success: function(response) {
                    $('#posts').html(response['posts_html'])
                }
            })
        })
    })
}  
$(document).ready(function() {
    ajaxPagination()
})

$(document).ajaxStop(function() {
    ajaxPagination()
})
</script>
{% endblock js %}

I could not find any broad step to step tutorial/guide about on this matter.
Devs! Please tell me how to implement pagination in django using jquery ajax?

Thanks in advance.

Answer

After I posted an Question here, but I haven’t get any answer. I was tried deferent way to apply but none is working for me. Django built in Pagination is easy to use for sync view. But how about with Json response to make a call async? It is much more confusing for beginner like me. And it’s made me panic for a few days. Finally, I found a easy way of implementations with Jquery. Although my original question is ‘Django ajax pagination couldn’t run properly’, I know devs! I just show you how we can achieve the same thing in deferent way. So, I would like to share my experience:

views.py

def post_list(request):
    posts = Post.objects.all()
    context = {
        'posts': posts,
    }
    return render(request, 'post_list.html', context)

post_list.html

{% block page_content %}
<div id="page">
    {% include 'posts.html' %}

    <div class="container">
        <nav aria-label=...>
            <ul class=pagination>
                <li id="previous-page"><a class="page-link" href="javascript:void(0)" aria-label=Previous><span aria-hidden=true>&laquo;</span></a></li>
            </ul>
        </nav>
    </div>
</div>
{% endblock page_content %}

{% block js %}
<script>
    $(document).ready(function(){
       var numberOfItems = $('#page .post-col').length; // Get total number of the items that should be paginated
var limitPerPage = 4; // Limit of items per each page
$("#page .post-col:gt(" + (limitPerPage - 1) + ")").hide(); // Hide all items over page limits (e.g., 5th item, 6th item, etc.)
var totalPages = Math.round(numberOfItems / limitPerPage); // Get number of pages
$(".pagination").append("<li class='page-item active'><a class='page-link' href='javascript:void(0)'>" + 1 + "</a></li>"); // Add first page marker
// Loop to insert page number for each sets of items equal to page limit (e.g., limit of 4 with 20 total items = insert 5 pages)
for (var i = 2; i <= totalPages; i++) {
  $(".pagination").append("<li class='page-item'><a class='page-link' href='javascript:void(0)'>" + i + "</a></li>"); // Insert page number into pagination tabs
}
    
// Add next button after all the page numbers 
$(".pagination").append("<li id='next-page'><a class='page-link' href='javascript:void(0)' aria-label=Next><span aria-hidden=true>&raquo;</span></a></li>")
// Function that displays new items based on page number that was clicked
$('.pagination li.page-item').on('click',  function() {
  // Check if page number that was clicked on is the current page that is being displayed
    if ($(this).hasClass('active')) {
        return false;
    } else {
        var currentPage = $(this).index(); // Get the current page number
        $('.pagination li').removeClass('active'); // Remove the 'active' class status from the page that is currently being displayed
        $(this).addClass('active'); // Add the 'active' class status to the page that was clicked o
        $('#page .post-col').hide(); // Hide all items in loop, this case, all the list groups
        
        var grandTotal = limitPerPage * currentPage; // Get the total number of items up to the page number that was clicked o
        // Loop through total items, selecting a new set of items based on page number
        for (var i = grandTotal - limitPerPage; i < grandTotal; i++) {
            $("#page .post-col:eq(" + i + ")").show(); // Show items from the new page that was selected
        }   
    }
  });
  // Function to navigate to the next page when users click on the next-page id (next page button)
  $('#next-page').on('click', function() {
      var currentPage = $('.pagination li.active').index(); // Identify the current active page
      // Check to make sure that navigating to the next page will not exceed the total number of pages
      if (currentPage === totalPages) {
          return false; // Return false (i.e., cannot navigate any further, since it would exceed the maximum number of pages)
      } else {
          currentPage++; // Increment the page by one
          $('.pagination li').removeClass('active'); // Remove the 'active' class status from the current page
          $('#page .post-col').hide(); // Hide all items in the pagination loop
          var grandTotal = limitPerPage * currentPage; // Get the total number of items up to the page that was selected
          // Loop through total items, selecting a new set of items based on page number
          for (var i = grandTotal - limitPerPage; i < grandTotal; i++) {
              $("#page .post-col:eq(" + i + ")").show(); // Show items from the new page that was selected
          }
          $(".pagination li.page-item:eq(" + (currentPage - 1) + ")").addClass('active'); // Make new page number the 'active' page
      }
      
});
$('#previous-page').on('click', function() {
    var currentPage = $('.pagination li.active').index();
    if (currentPage === 1) {
        return false;
    } else {
        currentPage--;
        $('.pagination li').removeClass('active');
        $('#page .post-col').hide()
        var grandTotal = limitPerPage * currentPage
        for (var i = grandTotal - limitPerPage; i < grandTotal; i++) {
            $("#page .post-col:eq(" + i + ")").show();
        }
        $(".pagination li.page-item:eq(" + (currentPage - 1) + ")").addClass('active')
    }
        
   });
 });
</script>
{% endblock js %}

posts.html

<div class="row">
  {% for post in posts %}
  <div class="col-md-6 post-col">  
    <div class="card flex-md-row mb-4 shadow-sm h-md-250">
        {% if post.main_image %}
        <img class="card-img-left flex-auto d-none d-lg-block" data-src="holder.js/200x250?theme=thumb" alt="Thumbnail [200x250]" style="width: 200px; height: 250px;" src="{{ post.main_image.url }}" data-holder-rendered="true">
        {% endif %}
        <div class="card-body d-flex flex-column align-items-start">
          <strong class="d-inline-block mb-2 text-primary">World</strong>
          <h3 class="mb-0">
            <a class="text-dark" href="{% url 'post_detail' post.slug %}">{{post.title}}</a>
          </h3>
          <div class="mb-1 text-muted">Nov 12</div>
          <p class="card-text mb-auto">{{post.summary}}</p>
          <a href="{% url 'post_detail' post.slug %}">Continue reading</a>
        </div>
    </div>
  </div>
  {% endfor %}
</div>

Hope. This will be helpful for beginners. Have any idea? Please share with me. 🙂