Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 %}
like image 575
Wessi Avatar asked Aug 22 '16 21:08

Wessi


People also ask

Does pagination improve performance Django?

Additionally, switching to keyset pagination will improve the performance of page lookups and make them work in constant time. Django makes it easy to alter its default configuration, giving you the power to build a performant solution for pagination in Django.

How do I paginate a list in Django?

Example. Note that you can give Paginator a list/tuple, a Django QuerySet , or any other object with a count() or __len__() method. When determining the number of objects contained in the passed object, Paginator will first try calling count() , then fallback to using len() if the passed object has no count() method.


1 Answers

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 %}
            (...)
like image 190
rafalmp Avatar answered Nov 01 '22 00:11

rafalmp