pagination 페이지 숫자 범위 제한하기

Django에서는 pagination를 위한 다양한 기능들을이 기본적으로 구현돼있다. 그래서 간단한 설정만으로도 pagination을 쉽게 구현할 수 있다. 그리고 이 설정에 대한 부분은 문서에 자세히 정리되어 있다.

나는 이 pagination 기능을 이용하여 한 페이지당 10개의 객체들을 보여주는 Class-based view를 아래와 같이 구현하였다.

# students/views/list.py

from django.views.generic import ListView

from students.models import Student


class StudentListView(ListView):  
    model = Student
    template_name = "students/list.html"
    context_object_name = 'students'
    paginate_by = 10  # Display 10 objects per page

여기서 문제는 객체가 많아질수록 아래의 페이지 숫자들이 계속 늘어난다는 것이었다. 즉, 객체가 총 500개라고 한다면, 아래에 페이지 숫자들은 Prev 1 2 ... 99 500 Next, 총 50개가 나온다는 의미이다.

하지만 나는 아래의 페이지 숫자들을 항상 5개만 보여주고 싶었다. 예를 들어 1 ~ 5 페이지일 때는 아래의 페이지 숫자가 Prev 1 2 3 4 5 Next로 나오고, 6 ~ 10 페이지일 때는 Prev 6 7 8 9 10 Next로 나오게 하고 싶었다.

그러나 안타깝게도 Django에서는 페이지 숫자 범위를 내 입맛에 맞게 설정하는 기능은 제공해주지 않는 듯 했고(어쩌면 내가 못 찾은 것일 수도 모르지만), 어쩔 수 없이 아래와 같이 직접 구현하였다.

# students/views/list.py

from django.views.generic import ListView  
from django.core.paginator import Paginator

from students.models import Student


class StudentListView(ListView):  
    model = Student
    template_name = "students/list.html"
    context_object_name = 'students'
    paginate_by = 10  # Display 10 objects per page

    def get_context_data(self, **kwargs):
        context = super(StudentListView, self).get_context_data(**kwargs)
        paginator = context['paginator']
        page_numbers_range = 5  # Display only 5 page numbers
        max_index = len(paginator.page_range)

        page = self.request.GET.get('page')
        current_page = int(page) if page else 1

        start_index = int((current_page - 1) / page_numbers_range) * page_numbers_range
        end_index = start_index + page_numbers_range
        if end_index >= max_index:
            end_index = max_index

        page_range = paginator.page_range[start_index:end_index]
        context['page_range'] = page_range
        return context

get_context_data(self, **kwargs) 함수를 override하여 구현하였다. 그리고 현재 페이지에서 내가 원하는 page_range를 template에 context로 넘겨주었다.

아래는 pagination과 관련된 전체 template 코드이다.

<!-- templates/students/list.html -->

{% for student in students %}
<!-- Student objects -->  
{% endfor %}

{# Pagination #}
{% if is_paginated %}
  <nav>
    <ul class="pagination">
      {% if page_obj.has_previous %}
        <li>
          <a href="?page={{ page_obj.previous_page_number }}">
            <span>Prev</span>
          </a>
        </li>
      {% else %}
        <li class="disabled">
          <a href="#">
            <span>Previous</span>
          </a>
        </li>
      {% endif %}

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

      {% if page_obj.has_next %}
        <li>
          <a href="?page={{ page_obj.next_page_number }}">
            <span>Next</span>
          </a>
        </li>
      {% else %}
        <li {% if not page_obj.has_next %}class="disabled"{% endif %}>
          <a href="#">
            <span>Next</span>
          </a>
        </li>
      {% endif %}
    </ul>
  </nav>
{% endif %}

여기서 주목해야할 부분은 이 부분이다.

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

원래 기존에 사용했던 {% for page in paginator.page_range %} 장고 템플릿 태그를 view에서 넘겨받은 context를 이용해 {% for page in page_range %}로 바꿔주었다.