
페이징 기능은 데이터를 페이지 단위로 나누어 표시하고, 페이지 번호를 통해 다른 페이지로 이동할 수 있는 기능이다.
프로젝트 디렉토리(proj)에 여러 앱에서 사용할만한 기능들을 모아서 넣어놓을 수 있는 'common' 디렉토리를 생성하면 좋다.
'common' 디렉토리 안에 기능 함수를 정의할 'CommonUtil.py' 파일을 생성했다.
# common/CommonUtil.py
import math
def dictfetchall(cursor):
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
class CommonPage:
def __init__(self, totalCnt=1, curPage=0, pageSize=10):
#
self.curPage = curPage # 현재 페이지
self.totalCnt = totalCnt # 전체 데이터 개수
self.pageSize = pageSize # 한 페이지에 보여줄 데이터 개수
self.totalPage = math.ceil(totalCnt/pageSize) - 1 # 전체 페이지 수
# 10개씩 그룹을 나눔
self.start = (self.curPage//self.pageSize) *10
self.end = self.start + 10
# 0~9 : self.start = 0, self.end = 10
# 10~19 : self.start = 1, self.end = 20
# 전체 페이지 수 보다 더 많은 페이징 되는 것을 막기 위해
if self.end > self.totalPage: # 마지막 그룹에서 전체페이지수보다 end가 더 크면
self.end = self.totalPage + 1 # end를 전체페이지수 + 1로
if self.curPage > 0: # 앞으로 이동가능
self.isPrev = True
self.prev_page = self.curPage - 1
else: # 앞으로 이동 불가능
self.isPrev = False
self.prev_page = 0
if self.curPage < self.totalPage: # 뒤로 이동가능
self.isNext = True
self.next_page = self.curPage + 1
else: # 뒤로 이동 불가능
self.isNext = False
self.next_page = self.curPage
self.page_range = range(self.start, self.end)
페이징 부분을 따로 템플릿 파일로 만들어 두면 여러 html에서 include 템플릿 코드를 통해 사용할 수 있습니다.
{% load mathfilters %}
<div class="container mt-5">
<!-- mt : margin-top의 줄임말 -->
<!-- 페이징 처리 시작 -->
<ul class="pagination justify-content-center">
<!-- 첫 페이지 -->
<li class="page-item"><a class="page-link" href="javascript:goPage(0)">First</a></li>
<!-- 이전 페이지 -->
{% if commonPage.isPrev %}
<li class="page-item"><a class="page-link" href="javascript:goPage({{commonPage.prev_page}})">Previous</a></li>
{% endif %}
<!-- 페이지 리스트 -->
{% for pg in commonPage.page_range %}
{% if pg == commonPage.curPage %}
<li class="page-item **active**"><a class="page-link" href="javascript:goPage({{pg}})">{{pg|add:1}}</a></li>
<!-- 페이지들(1 2 3 4 ... 10)에서 해당 페이지를 활성화시킴 -->
{% else %}
<li class="page-item"><a class="page-link" href="javascript:goPage({{pg}})">{{pg|add:1}}</a></li>
{% endif %}
{% endfor %}
<!-- 다음 페이지 -->
{% if commonPage.isNext %}
<li class="page-item"><a class="page-link" href="javascript:goPage({{commonPage.next_page}})">Next</a></li>
{% endif %}
<!-- 마지막 페이지 -->
<li class="page-item"><a class="page-link" href="javascript:goPage({{commonPage.totalPage}})">Last</a></li>
</ul>
</div>
a태그의 href="javascript:goPage(파라미터)" : 페이징을 추가한 템플릿 파일의 script태그에서 goPage함수 정의하여 페이지 이동 (밑에 나옴){% load mathfilters %} 를 사용하기 위해서 해야 하는 작업이 2가지 있습니다.
1. 터미널(/cmd)에서pip install django-mathfilters명령어를 통해 모듈 install
2. config/settings.py 파일에서 mathfilters를 앱등록 부분에 추가INSTALLED_APPS = [ "app1.apps.App1Config", "app2.apps.App2Config", "app3.apps.App3Config", 'mathfilters', (생략) ]
데이터 목록 페이지에 페이징 기능 추가하기!!
<!-- templates/app2/book_list.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
li{
display: inline;
}
</style>
</head>
<body>
<h1>Book list</h1>
<button><a href="/app3/books/create/">추가</a></button>
<table>
<tr>
<th>제목</th>
<th>저자</th>
<th>마지막 수정일</th>
<th>리뷰</th>
</tr>
{% for book in books %}
<tr>
<td><a href="/app3/books/{{book.id}}/">{{book.title}}</a></td>
<td>{{book.author}}</td>
<td>{{book.date}}</td>
<td>{{book.review}}</td>
</tr>
{% endfor %}
</table>
{% include 'paging.html' %}
<script>
function goPage(pg){
// 자바스립트로 페이지 이동 - get 방식으로 간단히 이동할 때
location.href = "/app3/books/" + pg;
// 이게 아닐 경우에는 form태그 참조를 가져와서 사용
}
</script>
</body>
</html>
{% include 'paging.html' %} : 페이징 기능을 구현한 템플릿 파일 추가<script>function goPage(pg){location.href="app3/boos/"+pg; : 클릭하면 해당 페이지로 이동하도록 자바스크립트 함수 구현# proj/app3/urls.py
from django.urls import path
from . import views
app_name = 'app3' # 앱 이름 등록
urlpatterns = [
################# 3. 직접 쿼리 실행 방식 #################
path('books/<int:pg>', views.queryList, name='query-list'),
path('books/<int:pk>/', views.queryDetail, name='query-detail'),
path('books/create/', views.queryCreate, name='query-create'),
path('books/<int:pk>/update/', views.queryUpdate, name='query-update'),
path('books/<int:pk>/delete/', views.queryDelete, name='query-delete'),
]
이전에 작성했던 데이터베이스 접근 방식의 직접 쿼리 방식을 변경해서 사용하였다. 모델, 폼, 템플릿 파일들도 데이터 접근 방식의 템플릿 파일과 유사하니 참고!! (데이터베이스 접근 방식)
변경한 부분은 데이터 목록을 담당하는 queryList함수만 변경하였다.
테이블의 전체 데이터 개수를 알아내고, CommonPage()함수를 통해 페이징 기능에 필요한 정보를 템플릿 파일에 함께 보냈다. (밑의 코드에서는 cp변수에 저장하였다.)
또한, 페이지당 10개씩 출력할 것이기 때문에 그에 맞게 sql코드를 짜서 그 페이지에 해당하는 데이터들을 불러와서 템플릿 파일에 보냈다.
# proj/app3/views.py
from django.shortcuts import render, redirect
from django.db import connection
from common.CommonUtil import dictfetchall, CommonPage
def queryList(request, pg):
cursor = connection.cursor()
# cursor객체 : db에 데이터 읽고 쓰기 담당
# sql = 'select * from app3_book order by id desc'
# 전체 데이터 건수를 알아야 페이징이 가능하다
# pg = 1
sql = "SELECT count(*) FROM app3_book"
cursor.execute(sql)
totalCnt = int(cursor.fetchone()[0]) # 전체 데이터 개수
cp = CommonPage(totalCnt, pg, 10)
sql = f"""
SELECT A.id, A.title, A.author, A.review, strftime('%Y-%m-%d', A.date) AS wdate, num
FROM (
SELECT id, title, author, date, review,
row_number() OVER (ORDER BY id DESC) AS num,
CAST((row_number() OVER (ORDER BY id DESC) - 1) / 10 AS INTEGER) AS pg
FROM app3_book
) AS A
WHERE A.pg = {pg}
"""
cursor.execute(sql)
# data = cursor.fetchall()
# print(data)
dataList = dictfetchall(cursor)
print(cp)
return render(request, "app3/book_list.html", {'books':dataList, 'commonPage':cp})
def queryDetail(request, pk):
with connection.cursor() as cursor:
cursor.execute(f"SELECT * FROM app3_book WHERE id={pk}")
data = dictfetchall(cursor)
return render(request, 'app3/book_detail.html', {'book': data[0]})
def queryCreate(request):
if request.method == 'POST':
title = request.POST.get('title')
author = request.POST.get('author')
review = request.POST.get('review')
with connection.cursor() as cursor:
cursor.execute("INSERT INTO app3_book (title, author, review, date) VALUES (%s, %s, %s, datetime('now','localtime'))", [title, author, review])
return redirect('app3:query-list')
else:
success_url = '/app3/books/create/'
return render(request, 'app3/book_create.html', {'success_url':success_url})
def queryUpdate(request, pk):
if request.method == 'POST':
title = request.POST.get('title')
author = request.POST.get('author')
review = request.POST.get('review')
with connection.cursor() as cursor:
cursor.execute("UPDATE app3_book SET title = %s, author = %s, review = %s WHERE id = %s", [title,author,review, pk])
return redirect('app3:query-detail', pk=pk)
else:
with connection.cursor() as cursor:
cursor.execute(f"SELECT * FROM app3_book WHERE id={pk}")
data = dictfetchall(cursor)
success_url = f'/app3/books/{pk}/update/'
return render(request, 'app3/book_update.html', {'book':data[0], 'success_url':success_url})
def queryDelete(request, pk):
if request.method == 'POST':
with connection.cursor() as cursor:
cursor.execute("DELETE FROM app3_book WHERE id = %s", [pk])
return redirect('app3:query-list')
success_url = f'/app3/books/{pk}/delete/'
return render(request, 'app3/book_confirm_delete.html', {'success_url':success_url})

데이터를 2개밖에 추가를 안 해서 첫 페이지밖에 나오지 않지만 페이지 기능이 추가된 것을 확인할 수 있다