1) request.GET
- urls에서 "/?key=values"의 값은 request.GET을 통해 Dict 형태로 가져올 수 있어요.
- "http://127.0.0.1:8000/" url로 서버에 요청하면, 콘솔에 "<QueryDict: {}>"이 출력되는데요, 이는 url 뒤에 파라미터가 붙어있지 않기 때문이에요.
- "http://127.0.0.1:8000/?page=1" 로 접근하면, <QueryDict: {'page': ['1']}> 딕셔너리 형태로 나타나는 것을 볼 수 있어요.
- dir()을 통해 GET을 살펴보면 아래와 같이 다양한 매서드를 볼 수 있는데요,, key()와 values()를 사용하면 key값와 value값도 받아볼 수 있답니다.
from django.shortcuts import render
from . import models
def all_rooms(request):
print(request.GET)
print(request.GET.get('page'))
all_rooms = models.Room.objects.all()[:10]
context = {"rooms": all_rooms}
return render(request, "rooms/home.html", context)
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)
context = {
"rooms": all_rooms,
"page": page,
"page_count": page_count,
}
return render(request, "rooms/home.html", context)
2) Navigation 추가
- page를 클릭으로 이동할 수 있도록 Navigation을 추가해도록 하죠. 우선 페이지 갯수 만큼의 배열이 필요한데요,, 템플릿에서는 range함수를 사용할 수 없어서 View를 통해 배열을 전달해볼께요.
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)
context = {
"rooms": all_rooms,
"page": page,
"page_count": page_count,
"page_range": range(1, page_count),
}
return render(request, "rooms/home.html", context)
- 페이지 번호를 나열하기 위해 템플릿을 전달 받은 page_range를 for문을 통해 풀어주고, a태그를 통해 링크를 연결해주었어요:)
{% extends "base.html" %}
{% block page_name %}
Home
{% endblock page_name %}
{% block content %}
{% for room in potato %}
<h1>{{room.name}} / ${{room.price}}</h1>
{% endfor %}
<h5> Page {{page}} of {{page_count}}</h5>
{% for page in page_range %}
<a href="?page={{page}}">{{page}}</a>
{% endfor %}
{% endblock content %}
- 페이지 번호를 나열하는 방법도 있지만, 이전 페이지와 다음 페이지만 링크 나타나게 할 수도 있어요. 단 여기서 주의할 점은 맨 처음 페이지에서는 이전 페이지 버튼을 숨겨주고, 맨 마지막 페이지에서는 다음 페이지 버튼을 숨겨줘야해요!
- a태그 안에 특이한 문법(|add:)이 보이는데요,,, 이것은 변수에 덧셈을해주는 템플릿 filter에요.
- 템플릿에서 if문을 통해 현재 페이지(page)가 1보다 작아지거나, 최대 페이지 수(page_count)를 넘어서면 Prev, Next 버튼을 나타나지 않게 할 수 있어요:)
{% extends "base.html" %}
{% block page_title %}
Home
{% endblock page_title %}
{% block content %}
{% for room in rooms %}
<h1>{{room.name}} / ${{room.price}}</h1>
{% endfor %}
<h5>
{% if page is not 1 %}
<a href="?page={{page|add:-1}}">Prev</a>
{% endif %}
Page {{page}} of {{page_count}}
{% if page is not page_count %}
<a href="?page={{page|add:1}}">Next</a>
{% endif %}
</h5>
{% endblock content %}
- Django가 제공하는 Pagination을 사용하면, 이 기능을 더 손쉽게 만들 수 있어요. Django의 Paginator는 아래 위치에 있답니다.
- 🔎 from django.core.paginator import Paginator
- url 정보에 있는 QueryDict 값("http://127.0.0.1:8000/?page=값")을 얻기 위해 request 정보를 가져오는 것은 필요하지만, Default는 필요없어요! Pagination이 알아서 처리해준답니다.
- 🔎 page = request.GET.get("page")
- 객실의 모든 정보를 가져오는 QuerySet을 생성 후, 이를 page 당 제한할 갯수와 함께 Pagination에 전달해 줄꺼에요.
- 🔎 room_list = models.Room.objects.all()
- 🔎 pagination = Pagination(room_list, 10) 👈 10개씩 보여줄꺼에요:)
- 현재 페이지에 대한 url 정보(QueryDict 값)를 get_page를 통해 pagination에 전달해주어요!
- 🔎 rooms = paginator.get_page(page)
from django.shortcuts import render
from django.core.paginator import Paginator
from . import models
def all_rooms(request):
page = request.GET.get("page")
room_list = models.Room.objects.all()
paginator = Paginator(room_list, 10)
rooms = paginator.get_page(page)
context = {
"page": rooms,
}
return render(request, "rooms/home.html", context)
2) object_list
- 위에서
print(vars(rooms))
로 출력된 내용을 살펴보면, 10개씩 제한된 객실정보가 "object_list"를 key값으로 가지는 QuerySet으로 나타나네요.
- number가 1로 나타나는 것은 현재 페이지를 의미하는데요,, "http://127.0.0.1:8000/"로 접근했기 때문이죠. url정보를 가져올 때 Default값을 주지않았는데도 1로 설정된걸 볼 수 있어요:)
print(vars(rooms.paginator))
에서는 전체 객실 갯수(count)와, 최대 페이지 숫자(num_pages)도 알 수 있어요.
{
'object_list': <QuerySet [<Room: 84420 Justin Village Apt. 616
Lake Ronaldtown, ME 06980>, <Room: 24793 Sanchez Land
Dominguezshire, KY 99190>, <Room: 538 Traci Mount Suite 066
Mariachester, WV 68670>, <Room: 005 Amber Junctions
Lake Kellystad, WA 17428>, <Room: 822 Chandler Fort
Lowerybury, LA 17857>, <Room: 7255 Romero Rest Suite 871
Port James, KS 99016>, <Room: 698 Gonzales Pass
Griffinbury, AZ 62361>, <Room: 4258 Shelton Fort
New Tina, CO 27801>, <Room: 7931 Perez Mall
West Tylermouth, PA 01138>, <Room: 5577 Samantha Falls Apt. 555
Byrdbury, MS 80327>]>,
'number': 1,
'paginator': <django.core.paginator.Paginator object at 0x7f80013c03a0>
}
- 모든 정보를 갖고 있는 "rooms"을 "page"로 템플릿에 render하였으니 이를 활용해볼텐데요, 우선 객실에 대한 객체가 "object_list"라는 key값에 QuerySet으로 담겨 있어요. for문을 이용하여 템플리셍 나타나게할 수 있어요.
- url에서 가져온 현재 페이지 정보는 page.number 에서 가지고 있고, 최대 페이지는 page.paginator.num_pages에 담겨 있군요.
- 이전 페이지와 다음 페이지 element를 만들 때, 현재페이지 번호(page.number)와 최대 페이지 번호(page.paginator.num_pages)를 사용해 처리해줄 수 있지만, Django의 pagination에서는 has_privious와 has_next도 제공한답니다:)
- 🔎
{% if page.has_previous %}
= {% if page.number is not 1 %}
- 🔎
{% if page.has_next %}
= {% if page.number is not rooms.paginator.num_pages %}
- 뿐만아니라 이전 페이지 번호("previous_page_number"), 다음 페이지 번호("next_page_number")도 제공하기 때문에 간편하게 도움을 받을 수 있답니다.
- 🔎
<a href="?page={{page.previous_page_number}}">Prev</a>
= <a href="?page={{page.number|add:-1}}">Prev</a>
- 🔎
<a href="?page={{page.next_page_number}}">Next</a>
= <a href="?page={{page.number|add:1}}">Next</a>
{% extends "base.html" %}
{% block page_title %}
Home
{% endblock page_title %}
{% block content %}
{% for room in page.object_list %} 👈 page.object_list에 QuerySet이 존재해요:)
<h1>{{room.name}} / ${{room.price}}</h1>
{% endfor %}
<h5>
{% if page.has_previous %} 👈 이전 페이지가 있는지 check해줘요:)
<a href="?page={{page.previous_page_number}}">Prev</a>
{% endif %}
Page {{page.number}} of {{page.paginator.num_pages}}
{% if page.has_next %} 👈 다음 페이지가 있는지 check해줘요:)
<a href="?page={{page.next_page_number}}">Next</a>
{% endif %}
</h5>
{% endblock content %}
3) orphans
print(vars(rooms.paginator))
이 살펴보면, orphans가 0으로 지정된 것이 나타나는데요,, 이는 orphans를 설정하지 않았기 때문에 default 값으로 0을 갖고 있는 거에요.
- 예를 들어 객실 object가 104개 있을 때 페이지 당 10개씩 나타내면, 총 11페이지 중에서 마지막 페이지에는 4개의 object가 존재할텐데요,, 마지막 페이지에 남은 자투리를 object를 숨기고 싶을 때 사용하는 것이 orphans에요.
- 즉, 자투리 처리법이죠:)
- orphans는 Paginator의 속성이기 때문에 아래 처럼 처리해줄 수 있어요.
- 🔎 paginator = Paginator(room_list, 10, orphans=5) 👈 room_list의 QuerySet을 10개씩 제한하고, 자투리 5개까지는 페이지로 남겨주지 않아요. 6개부터는 마지막 페이지에 남겨준답니다:)
{
'object_list': <QuerySet [<Room: 84420 Justin Village Apt. 616
Lake Ronaldtown, ME 06980>, <Room: 24793 Sanchez Land
Dominguezshire, KY 99190>, <Room: 538 Traci Mount Suite 066
Mariachester, WV 68670>, <Room: 005 Amber Junctions
Lake Kellystad, WA 17428>, <Room: 822 Chandler Fort
Lowerybury, LA 17857>, <Room: 7255 Romero Rest Suite 871
Port James, KS 99016>, <Room: 698 Gonzales Pass
Griffinbury, AZ 62361>, <Room: 4258 Shelton Fort
New Tina, CO 27801>, <Room: 7931 Perez Mall
West Tylermouth, PA 01138>, <Room: 5577 Samantha Falls Apt. 555
Byrdbury, MS 80327>, <Room: 482 Walls Corner
West Maureenmouth, ND 32132>, <Room: 214 Marshall Cliffs Suite 753
North Sarah, MA 05509>, <Room: 40360 Cynthia Manor Suite 158
Port Ashleyville, OR 15798>, <Room: 24343 Cardenas Lake
Angieview, NM 37258>, <Room: 1037 Shirley Village
Jennifershire, CA 14875>, <Room: 488 Tonya Fall
Allenport, NH 71146>, <Room: 5804 Bennett Junctions Suite 741
Mariobury, AL 43417>, <Room: 26046 Melissa Divide Suite 336
Collinsmouth, HI 32032>, <Room: 8016 Melissa Throughway
West Chelsea, MA 16404>, <Room: USNS Mcintyre
FPO AE 36529>, '...(remaining elements truncated)...']>,
'per_page': 10,
'orphans': 0,
'allow_empty_first_page': True,
'count': 100,
'num_pages': 10
}
- Paginator 생성한 클래스 객체의 정보를 가져올 때, 위에서는 get_page() 매서드를 사용했는데요,, page() 메서드도 존재합니다.
- get_page()는 url에서 가져온 page값이 없을 땐, Default를 주지않아도 자동으로 1페이지를 가져오고 request의 page값이 전체 페이지 값보다 크다면 마지막 페지이를 반환해 주기 때문에 편리하죠.
- 하지만, url이 매우 지저분하다는 단점도 있어요. 예를들어 10페이지 밖에 없는 상황에서 엄청 큰 페이지 번호를 요청해 했을 때, 오류가나지 않지만 url이 엉망이죠.
- page() 매서드는 Default값을 주거나 'allow_empty_first_page'를 False로 설정할 수도 있죠. 또한 존재하지 않는 page 값이 요청되면 자동으로 처리해주는게 아니라 "EmptyPage" 에러를 발생시켜 줍니다. 이때 try~except 구문으로 예외처리를 해주면, Paginator를 보다 잘 컨트롤 할 수 있겟죠:)
- 참고로,, page() 매서드는 int형으로 url의 파라미터 값을 전달해줘야 합니다.
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage
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(int(page))
context = {
"page": rooms,
}
return render(request, "rooms/home.html", context)
except EmptyPage:
return redirect("/")
1) as_view()
- urls.py에서 url 경로와 view.py의 함수를 매핑해줄 때, 함수만 가능하기 때문에 Class를 view로 이용하는 경우에는 as_view()를 붙여줘야해요. 그래야 Django가 Class를 View로써 인식할 수 있답니다.
- 🔎 path([url경로], [파일 경로].[Class명].as_view())
- "http://127.0.0.1:8000/" 로 접근하면, all_rooms() 함수가 작동되게끔 매핑되었던 것을 이제 HomeView라는 CBV가 호출될 수 있도록 as_view()를 붙여줍니다.
from django.urls import path
from rooms import views as room_views
urlpatterns = [
path("", room_views.HomeView.as_view())
]
2) ListView
- 상세 보기를 CBV로 만들 때, Django에서 제공하는 ListView를 상속받아 사용하면 편리합니다.
from django.views.generic import ListView
class HomeView(ListView):
"""HomeView Definition"""
pass
3) "model = "
from django.views.generic import ListView
from . import models
class HomeView(ListView):
"""HomeView Definition"""
model = models.Room
- QuerySet으로 사용할 Model을 지정하고 다시 url을 접근해보니, rooms 디렉토리에 room_list.html 파일을 찾을 수 없다는 에러가 발생합니다. 분명 request.Get.get(), return render() 등을 해준 적이 없는데도 불구하구요!
class HomeView(ListView):
"""HomeView Definition"""
model = models.Room
- 위에서 만든 home.html을 room_list.html으로 이름을 수정해 볼텐데요,, 아래와 같이 페이지가 render되는 것을 볼 수 있습니다.
4) object_list
- 공식문서를 살펴보면, object_list를 사용하라 되어있는데요,, context로 QuerySet을 전달하지 않았는데 불구하고 모든 Object가 화면에 출력되는걸 볼 수 있습니다.
- 즉, Model을 지정하고 템플릿에서 object_list를 사용하면, Django가 알아서 해당 모델의 Object를 리스트화하기 때문이에요!
{% extends "base.html" %}
{% block page_title %}
Home
{% endblock page_title %}
{% block content %}
{% for room in object_list %}
<h1>{{room.name}} / ${{room.price}}</h1>
{% endfor %}
{% endblock content %}
5) page_obj
- 아래 사이트에는 ListView가 갖고 있는 여러 속성 및 매서드가 잘 정리되었는데요,, 손쉽게 페이지 수를 제한할 수 있고, 정렬도 가능하답니다.
from django.views.generic import ListView
from . import models
class HomeView(ListView):
"""HomeView Definition"""
model = models.Room
paginate_by = 10
paginate_orphans = 5
ordering = "created"
page_kwarg = "page"
- 모든 Object는 "object_list"에 담고 있는데요, 페이징 정보는 "page_obj"에 담겨있다고 하네요:) 다른 이름으로 템플렛 변수를 사용하고 싶다면 "context_object_name"을 지정해주면 됩니다.
- 기존에 "page":rooms를 통해 render하여 사용했던 부분을 모두 "page_obj"로 바꿔볼께요. 모두 다 잘 작동됩니다.
{% extends "base.html" %}
{% block page_title %}
Home
{% endblock page_title %}
{% block content %}
{% for room in object_list %}
<h1>{{room.name}} / ${{room.price}}</h1>
{% endfor %}
<h5>
{% if page_obj.has_previous %}
<a href="?page={{page_obj.previous_page_number}}">Prev</a>
{% endif %}
Page {{page_obj.number}} of {{page_obj.paginator.num_pages}}
{% if page_obj.has_next %}
<a href="?page={{page_obj.next_page_number}}">Next</a>
{% endif %}
</h5>
{% endblock content %}
6) get_context_data()
- CBV와 FBV에 대해 여러 논쟁이 존재한다고 해요. CBV는 너무 마법처럼 간단하게 해결해준다는 장점이 있지만 그 로직이 정확히 드러나지 않거든요. 또한 CBV에 속성이나 매서드로 존재하지 않는 것들은 결국 만들어야하는 측면도 있어요. 뭐가 더 좋고 나쁘기 보다는 필요할 때 적재적소로 사용하면 좋을 것 같습니다:)
- 위에서 Model의 모든 Object를 가져오기 위해 object_list를 사용했는데, 이 또한 원하는 이름으로 바꿀 수 있어요.
- 🔎 context_object_name = "사용할 이름"
- FBV에서는 변수를 context로 전달했는데요,, 필요한 기능이 CBV에 이미 만들어져있지 않는다면 FBV에서 처럼 만들어 변수로 전달하면 좋겠죠. 이는 "get_context_data()" 매서드를 통해 가능합니다.
- 현재 시간을 화면에 출력하고자 해요,, 기존에 화면에 나타났던 Object를 모두 잃어버리면 안되겠죠? "get_context_data()"에서 기존에 CBV에서 갖고 있던 속성과 메서드를 가지고 옵니다.
- 🔎
context = super().get_context_data(**kwargs)
- context는 FVC에서도 Dict 형태로 전달해줬는데요, 이미 상속받은 context에 현재 시간을 추가해볼 께요.
from django.utils import timezone
from django.views.generic import ListView
from . import models
class HomeView(ListView):
"""HomeView Definition"""
model = models.Room
paginate_by = 10
paginate_orphans = 5
ordering = "created"
page_kwarg = "page"
context_object_name = "rooms"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
now = timezone.now()
context["now"] = now
return context
- 이제 템플릿에서 템플릿 변수(
{{now}}
)를 통해 현재 시간을 원하는 위치에 나타낼 수 있어요:)
저만의 pagination 구현을 위해 여러 사이트, 게시글들 찾아 다니며 공부하며 고생했는데, 여기서 깨닫고 갑니다! 설명이 정말 간단하면서 디테일하네요!
정말 감사합니다!