django | 18. 페이징 기능 추가하기

sojung·2021년 8월 20일
0

django

목록 보기
19/21
post-thumbnail

페이징 함수 작성하기

# views.py
...
from django.core.paginator import Paginator
...

def question_list(request):
  questions = Question.objects.order_by('-create_date') # Question 모델 데이터를 작성일시의 역순(-)으로 정렬한다.
  # Paging 기능 구현하기
  page = request.GET.get('page', '1') # GET 방식 요청 URL에서 page값을 가져올 때 사용(?page=1). page 파라미터가 없는 URL을 위해 기본값으로 1을 지정한 것
  paginator = Paginator(questions, 5) # Paginator 클래스는 questions를 페이징 객체 paginator로 변환. 페이지당 5개씩 보여주기
  page_obj = paginator.get_page(page) # page_obj 객체에는 여러 속성이 존재
  context = { 'questions_list' : page_obj } # page_obj를 question_list에 저장한다.
  return render(request, 'home/question_list.html', context)


5개씩 보이도록 설정하여서 5개만 보인다.
page_obj 객체에는 다음과 같은 속성들이 있다.

항목설명
paginator.count전체 게시물 개수
paginator.per_page페이지당 보여줄 게시물 개수
paginator.page_range페이지 범위
number현재 페이지 번호
previous_page_number이전 페이지 번호
next_page_number다음 페이지 번호
has_previous이전 페이지 유무
has_next다음 페이지 유무
start_index현재 페이지 시작 인덱스(1부터 시작)
end_index현재 페이지 끝 인덱스(1부터 시작)

페이징 적용하기

<!-- question_list.html -->

...
</table>
<ul>
  <!-- 이전 페이지가 있으면 -->
  {% if questions_list.has_previous %}
  <a href="?page={{ questions_list.previous_page_number }}">이전</a>
  {% endif %}
  <!-- 페이지 리스트 -->
  {% for page_number in questions_list.paginator.page_range %}
  <!-- 현재 페이지 번호랑 같다면 style을 다르게 줄 수 있다. -->
  {% if page_number == question_list.number %}
  <li>
    <a href="?page={{ page_number }}">{{ page_number }}</a>
  </li>
  {% else %}
  <li>
    <a href="?page={{ page_number }}">{{ page_number }}</a>
  </li>
  {% endif %}
  {% endfor%}
  <!-- 다음 페이지가 있으면 -->
  {% if questions_list.has_next %}
  <a href="?page={{ questions_list.next_page_number }}">다음</a>
  {% endif %}
</ul>
<!-- <a href="{% url 'home:question_create' %}">질문 등록하기</a> -->
<!-- 모델 폼 이용하지 않을 경우 -->
<a href="{% url 'home:question_new' %}">질문 등록하기</a>

{% endblock %}

템플릿에 사용된 {{ questions_list }}가 바로 views.py 파일의 page_obj이다. 다시 말해 템플릿의 {{ questions_list.previous_page_number }}는 `{{ page_obj.previous_page_number }}와 동일하다.

페이지 표시 제한 기능 구현하기

현재 문제

임의의 데이터 300개를 넣어보자.

$ python manage.py shell
>>> from home.models import Question
>>> for i in range(300):
...     Question.objects.create(subject="test data [%03d]" %i, content="test data")  
... 

하면 데이터 300개가 생긴다. 여기서 문제점은 아래 사진과 같다.

페이지 버튼이 너무 많아진다.

해결방법

<!-- question_list.html -->

...
  <!-- 페이지 리스트 -->
  {% for page_number in questions_list.paginator.page_range %}
  <!-- 코드 추가하기 : 페이지 표시 제한 기능 구현하기 -->
  {% if questions_list.number|add:5 >= page_number and page_number >= questions_list.number|add:-5 %}

...

{% if questions_list.number|add:5 >= page_number and page_number >= questions_list.number|add:-5 %} : 현재 페이지 기준으로 좌우 5개씩 보이도록 한다. questions_list.number보다 5만큼 크거나 작은 값만 표시되도록 만든 것이다.
|add:-5는 5만큼 빼라는 의미이고, |add:5는 5만큼 더하라는 의미이다.

현재 페이지가 8페이지라서 페이지 번호는 3부터 13까지 보여진다.

테플릿 필터 직접 만들어 보기

템플릿 필터란 템플릿 태그에서 |문자 뒤에 사용하는 필터를 말한다. 예를 들어 None 대신 공백 문자열을 보여주기 위해 사용했던 default_if_none과 같은 것을 템플릿 필터라고 한다.

{{ form.subject.value|default_if_none:''||

현재 문제


페이지마다 게시물 번호가 항상 1부터 시작되는 문제가 있다.

해결 방법

1. 게시물 번호 공식 만들기

만약 질문 게시물이 12개라면(한 페이지에 10개씩 표시) 1페이지에는 12번째~3번째 게시물이, 2페이지에는 2번째~1번째 게시물이 역순으로 표시되어야 한다. 질문 게시물의 번호를 역순으로 정렬하려면 다음과 같은 공식을 적용해야 한다.

일련번호 = 전체 게시물 개수 - 시작 인덱스 - 현재 인덱스 + 1

시작 인덱스는 페이지당 시작되는 게시물의 시작 번호를 의미한다. 예를 들어 페이지당 게시물을 10건씩 보여준다면 1페이지의 시작 인덱스는 1, 2페이지의 시작 인덱스는 11이 된다. 현재 인덱스는 페이지에 보여지는 게시물 개수만큼 0부터 1씩 증가되는 번호이다.
따라서 전체 게시물의 개수가 12개이고 페이지당 10건씩 게시물을 보여 준다면 1페이지의 일련번호는 12 - 1 - (0 ~ 9 반복) + 1이 되어 12~3까지 표시되고 2페이지의 경우에는 12 - 11 - (0~1 반복) + 1이 되어 2~1이 표시될 것이다.
템플릿에서 이 공식을 적용하려면 빼기 기능이 필요하다. 앞에서 더하기 필터(|add:5)를 사용한 것처럼 빼기 필터가 있으면 좋을 것 같지만 장고에는 빼기 필터가 없다. 그래서 우리는 빼기 필터를 만들 것이다.
|add:-5와 같이 숫자를 직접 입력하면 더하기 필터를 이용하여 원하는 값을 뺀 결과를 화면에 보여줄 수는 있다. 하지만 이 방법은 이곳에는 사용할 수 없다. 왜냐하면 더하기 필터에는 변수를 적용할 수 없기 때문이다.

2. 템플릿 필터 디렉토리 만들기

템플릿 필터 함수는 템플릿 필터 파일을 작성한 다음에 정의해야 한다. 템플릿 필터 파일은 템플릿 파일이나 스태틱 파일과 마찬가지로 home/templatetags 디렉토리를 새로 만들어 저장해야 한다. 또한 templatetags 디렉토리는 반드시 앱 디렉토리 아래에 생성해야 한다.
공식문서에 따르면 디렉토리 안에 __init__.py파일이 필요하다.

3. 템플릿 필터 작성하기

# home_filter.py

from django import template

register = template.Library()

@register.filter
def sub(value, arg):
  return value - arg

템플릿 필터 함수를 만드는 방법은, sub함수에 @register.filter라는 애너테이션을 적용하면 템플릿에서 해당 함수를 필터로 사용할 수 있게 된다. 템플릿 필터 함수 sub는 기존 값 value에서 입력으로 받은 값 arg를 빼서 반환할 것이다.

4. 템플릿 필터 사용하기

우리가 직접 만든 템플릿 필터를 사용하려면 템플릿 파일 맨 위에 {% load home_filter %}와 같이 템플릿 필터 파일을 로드해야 한다. 만약 템플릿 파일 맨 위에 extends 문이 있으면 load 문은 extends 문 다음에 위치해야 한다.

<!-- question_list.html -->

{% extends 'base.html' %}
<!-- 추가 -->
{% load home_filter %} 
{% block content %}

...
  <tbody>
    {% if questions_list %}
    {% for question in questions_list %}
    <tr>
      <!-- 추가 -->
      <td>{{ questions_list.paginator.count|sub:questions_list.start_index|sub:forloop.counter0|add:1 }}</td>
...
공식 요소설명
questions_list.paginator.count전체 게시물 개수
questions_list.start_index시작 인덱스(1부터 시작)
forloop.counter0루프 내의 현재 인덱스(forloop.counter0은 0부터, forloop.counter는 1부터 시작)


문제가 해결되었다.

profile
걸음마코더

0개의 댓글