Wessi Wessi - 3 months ago 27
Python Question

Django paginator with many pages

I have implemented a paginator with the generic ListView. My problem is that for list with many pages it displays all the page numbers instead of for example five pages before and after the current page. Is there an easy way to fix this?

in the views.py:

class CarList(LoginRequiredMixin, ListView):
model = Car
paginate_by = 20


in the html:

{% if is_paginated %}
<ul class="pagination pagination-centered">
{% if page_obj.has_previous %}
<li><a href="/car?ordering={{ current_order }}&page=1"><<</a></li>
<li><a href="/car?ordering={{ current_order }}&page={{ page_obj.previous_page_number }}"><</a></li>
{% endif %}

{% for i in paginator.page_range %}
<li {% if page_obj.number == i %} class="active" {% endif %}><a href="/car?ordering={{ current_order }}&page={{i}}">{{i}}</a></li>
{% endfor %}

{% if page_obj.has_next %}
<li><a href="/car?ordering={{ current_order }}&page={{ page_obj.next_page_number }}">></a></li>
<li><a href="/car?ordering={{ current_order }}&page={{ page_obj.paginator.num_pages }}">>></a></li>
{% endif %}
</ul>
{% endif %}

Answer

The algorithm for that is not too complicated, and can be further simplified if we assume that if there are more than 11 pages (current, 5 before, 5 after) we are always going to show 11 links. Now we have 4 cases:

  1. Number of pages < 11: show all pages;
  2. Current page <= 6: show first 11 pages;
  3. Current page > 6 and < (number of pages - 6): show current page, 5 before and 5 after;
  4. Current page >= (number of pages -6): show the last 11 pages.

With the above in mind, let's modify your view, create a variable to hold the numbers of pages to show and put it in the context:

class CarList(LoginRequiredMixin, ListView):
    model = Car
    paginate_by = 20

    def get_context_data(self, **kwargs):
        context = super(CarList, self).get_context_data(**kwargs)
        if not context.get('is_paginated', False):
            return context

        paginator = context.get('paginator')
        num_pages = paginator.num_pages
        current_page = context.get('page_obj')
        page_no = current_page.number

        if num_pages <= 11 or page_no <= 6:  # case 1 and 2
            pages = [x for x in range(1, min(num_pages + 1, 12))]
        elif page_no > num_pages - 6:  # case 4
            pages = [x for x in range(num_pages - 10, num_pages + 1)]
        else:  # case 3
            pages = [x for x in range(page_no - 5, page_no + 6)]

        context.update({'pages': pages})
        return context

Now you can simply use the new variable in your template to create page links:

            (...)
            {% for i in pages %}
            <li {% if page_obj.number == i %} class="active" {% endif %}><a href="/car?ordering={{ current_order }}&page={{i}}">{{i}}</a></li>
            {% endfor %}
            (...)