[Django] 페이지 나누기, Paginator

ElyaSetinal·2022년 5월 11일
0

Django

목록 보기
2/2

페이지 나누기를 찾아서

글의 목록을 표시할 때, Listview를 상속받아 작성하든, request를 사용하여 목록을 받아 표시하든, 글의 갯수가 많아지면 로딩에도 걸리는 시간이 더 늘어나고, 내가 원하는 글을 찾기위해 스크롤을 계속 내려야하는 문제가 발생할 것 같았다.

어떤 사이트를 가던지, 글의 갯수가 많아지면 페이지를 나누어서 보여줬던것이 일반적이니, Django에도 그런 기능이 있지 않을까 해서 찾아본게 시작이였다.


Paginator 사용하기

위의 사진처럼 우리가 일반적으로 보는 모든 사이트에 존재하는 페이지나누기, Pagination을 구현해주는 아주 고맙고도 편리한 기능이 되겠다.

Pagination을 사용하기 위해선 내가 views를 무엇으로 구성했느냐에 따라 조금씩 다른데, Listviewviews를 구성했는가, 아니면 함수를 사용하여 views를 구현했는가로 나뉜다.


ListView에서의 Pagination

class를 이용해 ListView를 상속받어 views를 작성했다면, class 내부에 한줄만 추가하면 된다.

공식문서에서 제시하는 코드는 다음과 같다.

from django.views.generic import ListView

from myapp.models import Contact

class ContactListView(ListView):
    paginate_by = 2 # Pagination
    model = Contact
    ...

기본적으로 ListView는 Pagination을 제공하고, 이를 관장하는 속성은 paginate_by다.

ListView가 상속받는 BaseListView, 그중에서도 MultipleObjectMixin을 보게 되면 아래와 같은 속성이 있다.

    allow_empty = True
    queryset = None
    model = None
    paginate_by = None
    paginate_orphans = 0
    context_object_name = None
    paginator_class = Paginator

속성을 보면 기본적으로 paginate_byNone이고, 내가 작성한 views에 속성을 추가해주지 않으면 Pagination을 하지 않는다. 속성에 특정한 값을 입력해주어야지만 그 숫자만큼 Pagination이 되는 것이라고 보면 되겠다.


함수에서의 Pagination

함수에서는 조금 복잡하다. ListView쪽의 속성을 보면 paginator_class가 있고, 속성값은 Paginator인데 이를 사용해야 한다.

공식문서에서 제시하는 코드는 다음과 같다.

from django.core.paginator import Paginator # 1. Paginator를 import
from django.shortcuts import render

from myapp.models import Contact

def listing(request):
    contact_list = Contact.objects.all() 
    	# 2. Pagination을 시행할 객체를 가져오기
    paginator = Paginator(contact_list, 25) 
    	# 3. 객체를 지정한 갯수만큼 page단위로 저장, 여기서는 25개씩
    page_number = request.GET.get('page') 
    	# 4. request된 page를 저장
    page_obj = paginator.get_page(page_number) 
    	# 5. request된 page의 인덱스를 저장
    return render(request, 'list.html', {'page_obj': page_obj}) 
    	# 6. 레코드를 `template`에 전달

아무래도 기본적으로 제공하는 ListView의 Pagination에 비해선 조금 길다.
그래도 개인적으론 함수를 더 좋아하는게, 뭔가 입맛대로 꾸미기가 쉽기도 하고, 개인적으론 좀더 직관적이다. class에 익숙치 않은것도 있겠지만..


[추가] Paginator 속성

Paginator는 4개의 속성을 가진다.

class Paginator(object_list, per_page, orphans=0, allow_empty_first_page=True)
  • object_list : Pagination을 시행할 객체, 리스트나 튜플, QuerySet등이 들어간다.
  • per_page : 페이지당 표시할 개체 수

이 아래는 옵션이다.

  • orphans : 마지막 페이지가 이 속성의 숫자보다 작다면, 페이지를 별도로 생성하지 않고 그 이전 페이지에 합산하여 표시한다.

    QuerySet에 22개의 데이터가 있고, per_page가 5, orphans가 3이면 페이지의 수는 4가 된다. 1~3페이지는 5개의 데이터가, 4페이지(마지막)에는 7개의 데이터가 표시된다.

  • allow_empty_fires_page=True : 첫 페이지를 비워둘 수 있는지 여부. 이것이 False가 되면 데이터가 없을때 EmptyPage 에러가 발생한다.

template 작성

Views.py에서 Pagination을 완료했지만, 이걸 우리가 보는것은 template, html에서 본다. 나누기는 했지만 나누어진 페이지를 보기 위해선 template를 작성해야 한다.

장고 공식문서에서 제시하는 코드는 아래와 같다.

<div class="Pagination">
<!--이전으로 돌아가기-->
{% if posts.has_previous %}
    <a href="?page=1">first</a>
    <a href="?page={{page_obj.previous_page_number}}">previous</a>
{% endif %}
  
<!--현재-->
  	<span class="current">
	Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
    <span>
      
<!--다음으로 넘어가기-->
{% if page_obj.has_next %}
    <a href="?page={{page_obj.next_page_number}}">next</a>
    <a href="?page={{page_obj.paginator.num_pages}}">last</a>
{% endif %}
</div>

예시 사진과는 조금 다르지만, 이전으로 돌아가기, 현재 페이지, 다음으로 넘어가기 세가지 기능이 들어간다고 보면 되겠다.
이전과 다음의 경우는 이전 페이지가 있느냐, 다음 페이지가 있느냐에 따라서 표시가 되도록 Template Tag를 사용해 if를 넣고, 해당되지 않으면 표기하지 않는 방식이다.

여기서 개인적으로 조금 헷갈렸던 부분이 a태그의 ?였다. Django에서는 주소가 바뀌면 urls.py를 수정해 주었어야 하니 urls.py?page=<??>라는 주소를 추가해야 하는건가 해서 확인해보았는데, 공식 문서에도 관련 내용이 없고, 나보다 먼저 사용한 분들 그 누구도 urls.py를 수정하진 않았다.

검색해보니 a태그의 ?의 의미는 변수로써 저 값을 전달한다는 의미로, template에서 get 요청을 통해 변수로써 page의 번호를 넘겨주게 되고, 이 변수가 Pagination의 결과를 보여준다는 의미였다.


적용해보기

사용하기위해 테스트도 할 겸, 연습해보기로 했다.


테스트용으로 글 몇개를 만들었다. 5개뿐이지만.

QuerySet에 5개의 글을 작성하고, 우선 이를 받아오는 함수를 작성하고

def pagingtest(request):
	test_list = Test.objects.all().order_by('-id')
	context = {'test_list':test_list,}
	return render(request, 'test.html', context)

여기에 함수에서 Paginator를 사용하는 방법을 사용해서, 객체를 나누고, 페이지 번호를 가져온 뒤, 인덱싱 하는 것을 추가했다.

def pagingtest(request):
	test_list = Test.objects.all().order_by('-id')

	pagination = Paginator(test_list, 2) #객체를 2개씩 나누기
	page_num = request.GET.get('page') # 페이지 번호 가져오기
	test_list_index = pagination.get_page(page_num) # 페이지 인덱싱
    
	context = {'test_list':test_list_index,}
	return render(request, 'test.html', context)

그 다음, template인 test.html에 들어가서


<div class="Test_List">
	[전체글 목록]
	{% for object in test_list %}
		<li><a href="">{{ object.title }}</a> - {{object.id}}</li>
	{% endfor %}
</div>

<div class="Pagination">
	{% if test_list.has_previous %}
		<a href="?page=1">First</a>
		<a href="?page={{test_list.previous_page_number}}">Previous</a>
	{% endif %}

	<span>{{test_list.number}} of {{test_list.paginator.num_pages}}</span>

	{% if test_list.has_next %}
		<a href="?page={{test_list.next_page_number}}">Next</a>
		<a href="?page={{test_list.paginator.num_pages}}">Last</a>
	{% endif %}
</div>

test_list의 항목을 for를 이용하여 출력하고, 그 밑에 페이지 이동용 코드를 추가하였다.

이를 저장하고, runserver를 통해서 확인해보면

두개씩 나뉘어서 잘 동작하는 것을 볼 수있다.


Reference

Django Paginator 공식문서
Django Pagination 공식문서
Bale의 Devlog - 페이지 나누기
테리의 일상 - 페이지네이션

profile
꿈을 꾸는 하나의 인생

0개의 댓글