이전 포스팅들에서 글을 쓰고 관리하는 기능에 대해 살펴보고 파이썬을 이용해서 웹 서비스로 만들어 보았다.
웹 서버를 동작시키고 글을 9~10개 정도 추가해보자. 포스팅을 통해 새로운 글들이 계속 추가되면 나중에 게시한 글을 보기 위해 해야하는 화면 스크롤이 길어진다(스크롤 압박이 발생한다).
이런 경우 알맞은 수의 글들을 한 화면에 보여주고, 다음 페이지에 이어지는 글들을 게시할 필요가 생긴다. 예를 들어 한 페이지에 3개씩 글을 보여주고 여러 페이지로 나누어 화면을 구성하는 것이다.
이런 기능을 어디서부터 만들면 좋을까? 다행히도 우리가 사용하는 데이터베이스(DB) ORM(Object Related Model)인 SQLAlchemy에서(해당 주제에 관한 자세한 내용을 '파이썬 웹 프로그래밍 기본: 7. 정보관리 - 7.2.파이썬으로 DB 사용하기' 에서 다루었다) 페이지 단위로 데이터 가져오기 기능을 지원한다. 그러면 어떤 방식으로 데이터를 가져오면 되는지 먼저 간단히 살펴보자.
아래는 Post라고 명명된 SQLAlchemy 모델에서 모든 글을 가져오는 명령이다. 단순히 모든 목록을 가져오기 때문에 Post → query → all 순으로 명령을 수행한다.
posts = Post.query.all()
다시 아래는 SQLAlchemy Post 모델에서 페이지 단위로 글을 가져오는 명령이다. 페이지 단위로 데이터를 가져오는 paginate()함수를 사용하기 위해서는 BaseQuery 객체를 반환하는 order_by() 같은 메서드를 사용해야 한다. Post → query → order_by → paginate 순으로 명령이 실행된다.
posts = Post.query.order_by().paginate(page=page, per_page=3)
order_by() 메서드는 그 이름에서 알 수 있듯이 결과를 정렬할 수 있다. 예를 들면 글이 씌어진 날짜를 내림차순으로(최신 글이 위에 오도록) 정렬해서 가져오려면 아래와 같이 order_by() 메서드에 인자로 desc() 메서드를 전달하면 된다(반대로 올립차순 정렬은 asc() 메서드를 사용하면 된다).
posts = Post.query.order_by(Post.datePosted.desc()).paginate(page = 1, per_page = 3)
paginate 메서드에 전달되는 인자 중 page는 몇 번째 페이지를 가져올 것인가를 지정하고, per_page 인자는 한 page가 몇개의 데이터로 구성되어 있는지 지정한다. 위의 paginate 메서드는 3개의 데이터가 하나의 페이지로 구성되어 있는 페이지 구조에서(per_page = 3) 첫 번째 페이지(page = 1)를 가져온다.
이제 paginate() 메서드를 이용해서 페이지 단위로 글을 게시하도록 웹 서비스를 수정해보자. 06_paginating 이라는 이름으로 새로운 프로젝트를 생성하고 이전 예제 05_posting 프로젝트의 내용을 복사해서 가져오자.
모델 - 뷰 - 로직 순서에 따라 수정해야 할 부분을 생각해보자. 사실 처음 사용하는 paginate() 메서드와 그 결과이기 때문에 예를 먼저 보고 따라해보는 것이 필요하다. 아래와 같이 글이 게시되는 뷰 파일인 home.html 파일을 수정하자.
▶ appmain/templates/home.html
{%extends "layout.html"%}
{%block content%}
<!-- paginate() 반환값은 paginate 객체입니다. 따라서 for 구문에 쓰일 수 없습니다 -->
{#% for post in posts %#}
<!-- paginate.items list를 이용합니다. -->
{% for post in posts.items %}
<h2><a href="/post/{{post.id}}">{{post.title}}</a></h2>
<p>By {{post.author}} on {{post.datePosted}}</p>
<p>{{post.content}}</p>
{% endfor%}
<!-- 페이지 이동을 위한 페이지 번호 링크구현 -->
<!-- paginate.iter_pages()는 전달한 인자를 참조해서 페이지 번호를 반환합니다. -->
{% for pageNum in posts.iter_pages(left_edge = 1, right_edge = 1, left_current = 1, right_current = 2) %}
{% if pageNum %}
<a href="{{url_for('main.home', page = pageNum)}}">{{pageNum}}</a>
{% else %}
...
{% endif %}
{% endfor%}
{%endblock content%}
뷰 파일에서는 페이지 단위로 글을 게시하는 것과 더불어 페이지를 이동할 수 있어야한다. 페이지 이동을 위한 링크를 for 구문을 이용해서 화면의 아랫쪽에 구현하였다.
뷰 파일 안에서 페이지 이동 링크 구현에 사용된 메서드인 paginate 객체의 iter_pages() 메서드는 인자로 주어진 조건에 따라 페이지 번호를 반환한다. 조건에 맞지 않으면 반환값이 없다(None 형식을 반환한다).
...
posts.iter_pages(left_edge = 1, right_edge = 1, left_current = 1, right_current = 2)
...
left_edge | 가장 왼쪽에 몇 개의 페이지 번호를 보여줄 것인가 설정한다. |
right_edge | 가장 오른쪽에 몇 개의 페이지 번호를 보여줄 것인가 설정한다. |
left_current | 현재 페이지 번호의 왼쪽에 몇 개의 페이지를 보여줄 것인가 설정한다. |
right_current | 현재 페이지 번호의 오른쪽에 몇 개의 페이지를 보여줄 것인가 설정한다. |
예를 들어 현재 페이지가 2 페이지이고, 전체 페이지가 6페이지라고 한다면 위와 같은 설정 아래서 아래와 같이 페이지 번호가 표시된다.
1 2 3 4 ... 6
가장 왼쪽에 1개의 페이지(=1), 가장 오른쪽에 1개의 페이지(=6), 현재 페이지 왼쪽에 1개의 페이지(=1), 그리고 현재 페이지 오른쪽에 두 개의 페이지(=3, 4)이기 때문이다. 뷰 파일에서는 if - else 구문을 이용해서 None으로 반환되는 페이지는 말 줄임표 ... 로 화면에 표시되도록 하였다. 그리고 페이지 번호는 링크로 만들어서 페이지 번호를 로직의 home 함수에 전달하도록 하였다.
<a href="{{url_for('main.home', page = pageNum)}}">{{pageNum}}</a>
이제 페이지 번호에 따라 DB에서 데이터를 가져오는 로직을 작성해보자.
페이지를 만들어내는 로직 파일에서는 특별한 조건이 없으면 1 페이지를 사용자에게 제공하면 된다. 그리고 만약 사용자가 뷰 파일에서 페이지 번호 링크를 클릭하면 페이지 번호가 로직(home())에 전달된다. 로직은 전달된 페이지 번호에 해당하는 데이터를 DB에서 가져와 페이지를 생성하여 사용자에게 제공한다.
▶ appmain/routes.py
#### GET 방식으로 페이지 번호를 전달 받기 위해 request 객체를 추가한다.
from flask import render_template, Blueprint, request
from appmain.post.models import Post
main = Blueprint('main', __name__)
@main.route("/")
@main.route("/home")
def home():
#### GET 방식으로 전달된 page변수의 값을 가져온다. 두 번째 인자인 1은 값이 없는 경우 기본값이다.
page = request.args.get('page', 1, type=int)
#### paginate() 메서드를 이용해서 한 페이지 3개의 글을 가져온다.
posts = Post.query.order_by(Post.datePosted.desc()).paginate(page = page, per_page = 3)
#### 만약 별다른 정렬이 필요없다면 order_by() 메서드에 인자를 주지 않아도 된다.
# posts = Post.query.order_by().paginate(page=page, per_page=3)
#### DB에서 가져온 데이터(포스팅된 글들)를 뷰 파일에 전달하고 페이지를 생성한다.
return render_template("home.html", posts=posts)
@main.route("/about")
def about():
return render_template("about.html", title='About')
페이지 번호는 GET 방식(URL에 데이터가 포함되어 전달되는 방식)으로 전달되기 때문에 데이터를 가져오기 위해 플라스크의 request 객체를 사용하였다. 아래와 같은 형식으로 GET 방식으로 전달되는 데이터를 가져올 수 있다.
request.args.get('URL에 포함된 변수 이름', 변수가 없는 경우 기본 값, 변수의 형식)
프로젝트를 저장하고 실행시키자. 프로젝트를 실행시키고 웹 브라우저를 통해 서비스에 접근하면 아래와 같이 페이지 단위로 구분된 화면을 볼 수 있다.
페이지 번호 링크를 클릭하면 해당 페이지로 이동할 수 있다. 아래는 2 페이지 화면이다.
■
'파이썬 웹 서비스 만들기' 카테고리의 다른 글
9. 이메일 보내기 - 9.2. 파이썬 flask mail 패키지 (0) | 2020.11.29 |
---|---|
9. 데이터를 안전하게 주고 받기 - 9.1. 파이썬 itsdangerous 패키지 (0) | 2020.11.28 |
7. 컨텐츠 포스팅 및 관리기능 - 7.3. 컨텐츠(글) 삭제기능 (0) | 2020.11.17 |
7. 컨텐츠 포스팅 및 관리기능 - 7.2. 컨텐츠(글) 수정기능 (0) | 2020.11.11 |
7. 컨텐츠 포스팅 및 관리기능 - 7.1. 포스팅 (0) | 2020.10.30 |
댓글