11.-2 HomeView - paginator

조재훈·2022년 7월 25일
0

Clone_Airbnb

목록 보기
24/31
post-thumbnail

1) Paginator

앞서 파이썬으로 코딩했던걸 장고의 paginator라는걸 이용해서 재구성할 것이다.
https://docs.djangoproject.com/en/4.0/ref/paginator/

제법 많은 부분이 바뀐다.
rooms - views.py (변경 전)

from math import ceil
from django.shortcuts import render
from . import models


def all_rooms(request):
    page = request.GET.get("page", 1)
    page = int(page or 1)
    page_size = 10
    limit = page_size * page
    offset = limit - page_size
    all_rooms = models.Room.objects.all()[offset:limit]
    page_count = ceil(models.Room.objects.count() / page_size)
    return render(
        request,
        "rooms/home.html",
        context={
            "rooms": all_rooms,
            "page": page,
            "page_count": page_count,
            "page_range": range(1, page_count),
        },
    )

rooms - views.py (변경 후)

한가지 기억할 것은 쿼리셋이 생성되는 즉시 불러와지진 않는다. (게으르다) 만약 생성하고서 바로 print(~~)처럼 불러오는게 아니면 실제로 호출하게 될 때 불러오게 된다.

일단 변경되는 코드는 저렇고 paginator로 가져오는 rooms를 vars를 이용하여 살펴보자.

    print(vars(rooms))

보면 Rooms의 인스턴스들 10개가 줄줄이 뜬다. number가 1이라는걸 보아하니 첫 번째 페이지에 해당하는 내용인듯하다. 또한 paginator 인스턴스도 있다.

paginator 인스턴스도 살펴보자. object_list에 모든 Room 인스턴스가 들어있고 per_page는 10개, orphans는 없고 총 갯수(count)는 102개고 페이지 갯수(num_pages)는 11이다.

    ...
    
    print(vars(rooms.paginator))
    
    ...


일단은 뜨긴 잘 뜬다. 근데 전체 몇 페이지 중 현재가 몇 페이지인지는 모른다.
views 파일에 작성된 코드들을 보면

    page = request.GET.get("page")
    room_list = models.Room.objects.all()
    paginator = Paginator(room_list, 10)
    rooms = paginator.get_page(page)

django-core-paginator.py 파일을 보면 Paginator와 Page 두 클래스가 정의되어있다.
우선 Paginator 클래스를 보면 사용자가 입력해주어야 하는 변수가 2개 있는데, object_list와 per_page 이다.

object_list로 room_list를 넣어주었고 per_page로 10을 입력해주었다. 이렇게 paginator 클래스를 생성했다.
이 paginator 클래스를 보면 여러 메소드들이 있는데 그 중 get_page라는게 있다.

숫자를 인수로 받는데 여기에다가 우리는 page 변수를 입력해줄 것이다. 이 page 변수는 url에 입력되는 주소 중 "page" 키값에 해당하는 숫자를 GET.get으로 가져오는 것이다. 그 숫자에 해당하는 page를 반환하게 된다.

참고로 저기 return하는 self.page(number)도 계속 따라가다보면 결국 Page 클래스를 반환하는 것을 알 수 있다.

그러고 열어보면 밑에 page가 아직까지는 잘 안써져있다.

템플릿을 마저 고쳐보자.
콘솔에서 vars(rooms)를 찾아보면 아래와 같이 Paginator를 포함하는 것으로 출력이 된다. 이미 rooms라는 Page 클래스가 room_list를 10개씩 쪼개놓은 Paginator라는 클래스를 상속받았으므로 여기에 포함되는 속성들을 그대로 물려받았다고 볼 수 있다. number는 url에 입력된 숫자를 가져오므로 예시에서 해당되는 11을 그대로 가져왔다.

for문에서 rooms.object_list를 보면 rooms는 views에서 넘어온 특정 숫자의 Page 인스턴스이다. Page 인스턴스는 object_list를 가지는데 이는 room_list와 동일하다.

전체 페이지 갯수는 paginator가 Page클래스에 포함되므로 rooms에서 paginator를 한번 타고 들어가서 전부 합하면 몇 페이지인지 확인한다.

그리고 이전 페이지가 있는지 다음 페이지가 있는지 여부를 확인해주는 메소드가 Page 클래스에 정의되어있다. 각각 has_previous와 has_next인데 이를 적용해서 1페이지에서는 previous를 없애고 마지막 페이지에선 next를 없애주자.

templates - rooms

{% extends "base.html" %}


{% block page_name %}
    Home
{% endblock page_name %}

{% block content %}

    {% for room in rooms.object_list %}
        <h1>{{room.name}} / ${{room.price}}</h1>
    {% endfor %}

    <h5>
        {% if rooms.has_previous %}
            <a href="?page={{rooms.number|add:-1}}">Previous</a>
        {% endif %}
        
        Page {{rooms.number}} of {{rooms.paginator.num_pages}}

        {% if rooms.has_next %}
            <a href="?page={{rooms.number|add:1}}">Next</a>
        {% endif %}
    </h5>

{% endblock content %}

그러고나서 보면 잘 나온다.



조금 더 수정해보자.
우선 views.py에서 context로 넘겨주는 변수명을 page로 바꾸자. 이렇게 하면 html에서 작성할 때 지금 쓰고있는게 page클래스인지 paginator클래스인지가 더 명확해진다.

...

    return render(
        request,
        "rooms/home.html",
        context={"page":rooms},
    )

그리고 home.html도 고친다. 기존의 rooms를 page로 바꾸고 이전 페이지나 다음 페이지 여부도 page.previous_page_number와 page.next_page_number로 바꾼다.

{% block content %}

    {% for room in page.object_list %}
        <h1>{{room.name}} / ${{room.price}}</h1>
    {% endfor %}

    <h5>
        {% if page.has_previous %}
            <a href="?page={{page.previous_page_number}}">Previous</a>
        {% endif %}
        
        Page {{page.number}} of {{page.paginator.num_pages}}

        {% if page.has_next %}
            <a href="?page={{page.next_page_number}}">Next</a>
        {% endif %}
    </h5>

{% endblock content %}

2) orphans

마지막 페이지를 보면 목록이 2개밖에 없다. 10개씩 차있어야 하는데 2개밖에 없고 이것때문에 페이지가 하나 더 생긴 것이다.

위와 같은 경우를 orphans 이라고 부른다. orphans로 지정해놓은 숫자보다 적은 수가 남으면 이전 페이지로 합쳐버리고 orphans보다 높은 수가 있으면 독립된 페이지로 한다.


rooms - views.py


    ...

def all_rooms(request):
    page = request.GET.get("page", 1)
    room_list = models.Room.objects.all()
    paginator = Paginator(room_list, 10, orphans=5)
    rooms = paginator.get_page(page)
    return render(
        request,
        "rooms/home.html",
        context={"page": rooms},
    )

새로고침해보면 마지막 페이지가 10번째로 바뀌고 대신 요소는 12개로 늘어난 것을 볼 수 있다. 우리는 orphans를 5개로 설정했다.

참고로 Paginator.page를 보면 발생할 수 있는 오류가 명시되어있다.

강의와 다른 점이 있는데, 강의에서는 존재하지 않는 페이지나 이상한 문자를 page에 입력하면 에러가 발생했다. EmptyPage나 등등.
그런데 내가 쓰고 있는 장고 최신버전은 이에 대한게 미리 보정이 되었는지, url에 page에다가 문자열이나 char를 입력하면 1번째 페이지로 자동으로 넘어가고
만약 말도 안되게 큰 숫자나 0 이하의 숫자를 입력하면 자동으로 마지막 페이지로 넘어간다.


3) exception

이건 사실 최신 버전 장고에서는 자동으로 해결되는 듯 하다. 강의 내용은 참고로만 기록.
rooms - views.py

from math import ceil
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from . import models


def all_rooms(request):
    page = request.GET.get("page", 1)
    room_list = models.Room.objects.all()
    paginator = Paginator(room_list, 10, orphans=5)
    try:
        rooms = paginator.page(page)
        return render(
            request,
            "rooms/home.html",
            context={"page": rooms},
        )
    except (EmptyPage, PageNotAnInteger):
        return redirect("/")

결과는 아래와 같이 어떤 URL 입력해도 메인으로 redirect됨


만약 모든 에러에 대해서 통제하고 싶다면 except뒤에 Exception 을 쓰면 된다. 하지만 기왕이면 다른 에러들마다 구분을 해놓는게 나중에 에러가 났을 때 디버깅하기가 더 좋다.

profile
맨땅에 헤딩. 인생은 실전.

0개의 댓글