# 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부터 시작되는 문제가 있다.
만약 질문 게시물이 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
와 같이 숫자를 직접 입력하면 더하기 필터를 이용하여 원하는 값을 뺀 결과를 화면에 보여줄 수는 있다. 하지만 이 방법은 이곳에는 사용할 수 없다. 왜냐하면 더하기 필터에는 변수를 적용할 수 없기 때문이다.
템플릿 필터 함수는 템플릿 필터 파일을 작성한 다음에 정의해야 한다. 템플릿 필터 파일은 템플릿 파일이나 스태틱 파일과 마찬가지로 home/templatetags 디렉토리를 새로 만들어 저장해야 한다. 또한 templatetags 디렉토리는 반드시 앱 디렉토리 아래에 생성해야 한다.
공식문서에 따르면 디렉토리 안에 __init__.py
파일이 필요하다.
# 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를 빼서 반환할 것이다.
우리가 직접 만든 템플릿 필터를 사용하려면 템플릿 파일 맨 위에 {% 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부터 시작) |
문제가 해결되었다.