Advertisement
  1. Code
  2. Python

파이썬 플라스크와 MySQL을 이용한 웹 앱 개발: 5부

Scroll to top
Read Time: 10 min
This post is part of a series called Creating a Web App From Scratch Using Python Flask and MySQL.
Creating a Web App From Scratch Using Python Flask and MySQL: Part 4
Creating a Web App From Scratch Using Python Flask and MySQL: Part 6

() translation by (you can also view the original English article)

이 연재 기사의 4부에서는 버킷 리스트 애플리케이션의 희망사항 EditDelete 기능을 구현하는 방법을 살펴봤습니다. 이번 5부에서는 사용자 홈 리스트의 페이징 기능을 구현하겠습니다.

시작하기

먼저 깃허브(GitHub)에서 튜토리얼의 이전 내용을 복제합니다.

1
git clone https://github.com/jay3dec/PythonFlaskMySQLApp_Part4.git

소스코드가 복제되면 프로젝트 디렉터리로 이동한 후 웹 서버를 구동합니다.

1
cd PythonFlaskMySQLApp_Part4
2
python app.py

브라우저에서 http://localhost:5002/로 이동하면 실행 중인 애플리케이션을 볼 수 있을 것입니다.

페이징 구현하기

사용자 홈 페이지의 희망사항 목록이 늘어나면 페이지 아래로 스크롤됩니다. 따라서 페이징을 구현하는 것이 중요합니다. 여기서는 페이지에 표시되는 항목의 수를 특정 숫자로 제한할 것입니다.

희망사항 조회 프로시저 수정

먼저 sp_GetWishByUser 프로시저에서 limitoffset 값을 기반으로 한 결과를 반환하도록 수정하겠습니다. 이번에는 저장 프로시저 문장을 동적으로 생성해서 limit 및 offset 값을 기반으로 결과 집합을 반환하겠습니다. 다음은 수정된 sp_GetWishByUser MySQL 저장 프로시저입니다.

1
USE `BucketList`;
2
DROP procedure IF EXISTS `sp_GetWishByUser`;
3
4
DELIMITER $$
5
USE `BucketList`$$
6
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetWishByUser`(
7
IN p_user_id bigint,
8
IN p_limit int,
9
IN p_offset int
10
)
11
BEGIN
12
    SET @t1 = CONCAT( 'select * from tbl_wish where wish_user_id = ', p_user_id, ' order by wish_date desc limit ',p_limit,' offset ',p_offset);
13
  PREPARE stmt FROM @t1;
14
	EXECUTE stmt;
15
	DEALLOCATE PREPARE stmt1;
16
END$$
17
18
DELIMITER ;
19

위의 저장 프로시저에서 볼 수 있듯이 여기서는 동적 SQL 쿼리를 생성하고 이를 실행해 offsetlimit 매개변수에 따라 희망사항 목록을 가져옵니다.

UI에 페이징 추가하기

먼저 몇 가지 기본 설정을 정의해 봅시다. app.py에서 페이지 제한에 대한 변수를 추가합니다.

1
# Default setting

2
pageLimit = 2

getWish 파이썬 메서드가 POST 요청을 받게 합니다.

1
@app.route('/getWish',methods=['POST'])

getWish 메서드 내에서 offsetlimit을 읽고 이를 sp_GetWishByUserMySQL 저장 프로시저를 호출할 때 전달합니다.

1
 _limit = pageLimit
2
 _offset = request.form['offset']
3
4
5
con = mysql.connect()
6
cursor = con.cursor()
7
cursor.callproc('sp_GetWishByUser',(_user,_limit,_offset))
8
wishes = cursor.fetchall()
9
10

userHome.htmlGetWishes 자바스크립트 함수에서는 POST 요청을 만들고 offset 값을 전달하도록 수정합니다.

1
function GetWishes() {
2
    $.ajax({
3
        url: '/getWish',
4
        type: 'POST',
5
        data: {
6
            offset: 0
7
        },
8
        success: function(res) {
9
10
            var wishObj = JSON.parse(res);
11
            $('#ulist').empty();
12
            $('#listTemplate').tmpl(wishObj).appendTo('#ulist');
13
14
        },
15
        error: function(error) {
16
            console.log(error);
17
        }
18
    });
19
}

모든 변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 이메일 주소와 비밀번호로 로그인하면 화면에 두 개의 레코드만 표시될 것입니다.

User Home with Limited recordsUser Home with Limited recordsUser Home with Limited records

따라서 데이터베이스 부분은 잘 작동하고 있습니다. 다음으로 사용자 홈 페이지에 페이징 UI를 추가해야 합니다. 그러고 나면 사용자가 데이터를 열람할 수 있을 것입니다.

여기서는 부트스트랩 페이징 컴포넌트를 사용하겠습니다. userHome.html을 열고 #ulist UL 다음에 다음과 같은 HTML 코드를 추가합니다.

1
<nav>
2
    <ul class="pagination">
3
        <li>
4
            <a href="#" aria-label="Previous">
5
                <span aria-hidden="true">&laquo;</span>
6
            </a>
7
        </li>
8
        <li><a href="#">1</a>
9
        </li>
10
        <li><a href="#">2</a>
11
        </li>
12
        <li><a href="#">3</a>
13
        </li>
14
        <li><a href="#">4</a>
15
        </li>
16
        <li><a href="#">5</a>
17
        </li>
18
        <li>
19
            <a href="#" aria-label="Next">
20
                <span aria-hidden="true">&raquo;</span>
21
            </a>
22
        </li>
23
    </ul>
24
</nav>

변경사항을 저장하고 서버를 다시 시작합니다. 로그인에 성공하면 희망사항 하단에 페이징된 모습을 볼 수 있습니다.

Pagination in User Home PagePagination in User Home PagePagination in User Home Page

페이징을 동적으로 만들기

위의 페이징은 페이징이 어떻게 표시될지 보여줍니다. 그러나 페이징이 기능하게 만들려면 데이터베이스의 레코드 수를 기반으로 동적으로 페이징을 만들어야 합니다.

페이징을 만들려면 데이터베이스에서 이용 가능한 총 레코드 수가 필요합니다. 따라서 sp_GetWishByUser MySQL 저장 프로시저에서 사용 가능한 총 레코드 수를 출력 매개변수로 반환하도록 수정해 봅시다.

1
USE `BucketList`;
2
DROP procedure IF EXISTS `sp_GetWishByUser`;
3
4
DELIMITER $$
5
USE `BucketList`$$
6
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetWishByUser`(
7
IN p_user_id bigint,
8
IN p_limit int,
9
IN p_offset int,
10
out p_total bigint
11
)
12
BEGIN
13
    
14
	select count(*) into p_total from tbl_wish where wish_user_id = p_user_id;
15
16
	SET @t1 = CONCAT( 'select * from tbl_wish where wish_user_id = ', p_user_id, ' order by wish_date desc limit ',p_limit,' offset ',p_offset);
17
	PREPARE stmt FROM @t1;
18
	EXECUTE stmt;
19
	DEALLOCATE PREPARE stmt;
20
END$$
21
22
DELIMITER ;

수정된 저장 프로시저에서 볼 수 있듯이 p_total이라는 새 출력 매개변수를 추가하고 사용자 ID를 기반으로 희망사항의 총 개수를 선택했습니다.

또한 getWish 파이썬 메서드에서 출력 매개변수를 전달하도록 수정합니다.

1
 _limit = pageLimit
2
 _offset = request.form['offset']
3
 _total_records = 0
4
5
6
con = mysql.connect()
7
cursor = con.cursor()
8
cursor.callproc('sp_GetWishByUser',(_user,_limit,_offset,_total_records))
9
wishes = cursor.fetchall()
10
11
cursor.close()
12
13
cursor = con.cursor()
14
cursor.execute('SELECT @_sp_GetWishByUser_3');
15
16
outParam = cursor.fetchall()

위 코드에서 볼 수 있듯이 저장 프로시저를 호출하면 커서를 닫고 새 커서를 열어 반환된 매개변수를 선택합니다.

앞에서는 파이썬 메서드에서 희망사항 목록을 반환했습니다. 이제 반환된 JSON에도 총 레코드 수를 포함해야 합니다. 따라서 희망사항 사전을 또 다른 리스트에다 만들고 메인 리스트에 해당 희망사항과 레코드 수를 추가할 것입니다. 다음은 수정된 getWish 파이썬 메서드입니다.

1
response = []
2
wishes_dict = []
3
4
for wish in wishes:
5
    wish_dict = {
6
        'Id': wish[0],
7
        'Title': wish[1],
8
        'Description': wish[2],
9
        'Date': wish[4]}
10
    wishes_dict.append(wish_dict)
11
    
12
response.append(wishes_dict)
13
response.append({'total':outParam[0][0]}) 
14
15
return json.dumps(response)

GetWishes 자바스크립트 함수에서는 성공 콜백 내부에 콘솔 로그를 추가합니다.

1
console.log(res);

모든 변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 이메일 주소와 비밀번호로 로그인한 후 사용자 홈페이지에서 브라우저 콘솔을 확인합니다. 그럼 아래에 표시된 것과 비슷한 응답을 볼 수 있을 것입니다.

1
[
2
    [{
3
        "Date": "Sun, 15 Feb 2015 15:10:45 GMT",
4
        "Description": "wwe",
5
        "Id": 5,
6
        "Title": "wwe"
7
    }, {
8
        "Date": "Sat, 24 Jan 2015 00:13:50 GMT",
9
        "Description": "Travel to Spain",
10
        "Id": 4,
11
        "Title": "Spain"
12
    }], {
13
        "total": 5
14
    }
15
]

응답에서 받은 총 개수를 사용해 총 페이지 수를 구할 수 있습니다.

1
var total = wishObj[1]['total'];
2
var pageCount = total/itemsPerPage;

총 항목 수를 itemsPerPage 수로 나누면 필요한 페이지 수가 나옵니다. 그런데 이것은 total이 itemsPerPage의 배수일 때만 유효합니다. 그렇지 않은 경우에는 이를 확인하고 이에 따라 페이지 수를 처리해야 합니다.

1
var pageRem = total%itemsPerPage;
2
if(pageRem !=0 ){
3
	pageCount = Math.floor(pageCount)+1;
4
}

그러면 올바른 페이지 수를 구할 수 있습니다.

이제 총 페이지 수를 구했으므로 페이징 HTML을 동적으로 생성하겠습니다. 앞에서 추가한 페이징 HTML에서 LI 요소를 제거합니다.

1
<nav>
2
    <ul class="pagination">
3
        // li we'll create dynamically

4
    </ul>

5
</nav>

GetWishes 성공 콜백에서는 jQuery를 이용해 이전 링크를 동적으로 만들어 보겠습니다.

1
var prevLink = $('<li/>').append($('<a/>').attr({
2
        'href': '#'
3
    }, {
4
        'aria-label': 'Previous'
5
    })
6
    .append($('<span/>').attr('aria-hidden', 'true').html('&laquo;')));
7
8
$('.pagination').append(prevLink);

위의 코드에서는 이전 버튼 링크를 만들어 페이징 UL에 추가했습니다.

위의 변경 사항을 저장하고 서버를 다시 시작합니다. 로그인에 성공하면 리스트 하단에 놓인 이전 링크를 볼 수 있을 것입니다.

Previous link in the PaginationPrevious link in the PaginationPrevious link in the Pagination

이와 마찬가지로 페이지 수에 따라 각 페이지에 추가합니다.

1
for (var i = 0; i < pageCount; i++) {
2
    var page = $('<li/>').append($('<a/>').attr('href', '#').text(i + 1));
3
    $('.pagination').append(page);
4
}

페이지 링크가 추가된 후 다음(Next) 링크도 추가합시다.

1
var nextLink = $('<li/>').append($('<a/>').attr({
2
        'href': '#'
3
    }, {
4
        'aria-label': 'Next'
5
    })
6
    .append($('<span/>').attr('aria-hidden', 'true').html('&raquo;')));
7
8
$('.pagination').append(nextLink);

변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 이메일 주소와 비밀번호로 로그인하고 사용자 홈 페이지로 이동하면 페이징을 볼 수 있을 것입니다.

Pagination in User Home PagePagination in User Home PagePagination in User Home Page

페이지 번호에 클릭 이벤트 연결하기

이제 페이징을 동작하게 만들 주요 로직이 나옵니다. 각 페이지 인덱스에 GetWishes 자바스크립트 함수를 호출하는 클릭 이벤트 호출을 추가하려고 합니다. 먼저 페이지 번호를 표시하는 앵커 요소에 클릭 이벤트를 추가하겠습니다.

1
for (var i = 0; i < pageCount; i++) {
2
3
    var aPage = $('<a/>').attr('href', '#').text(i + 1);
4
  
5
    $(aPage).click(function() {
6
        
7
    });
8
  
9
    var page = $('<li/>').append(aPage);
10
    $('.pagination').append(page);
11
12
}

페이지 앵커에 onclick 이벤트를 추가했습니다. 따라서 클릭할 때마다 GetWishes 함수를 호출하고 offset을 전달할 것입니다. 따라서 for 루프 외부에 offset을 선언합니다.

1
var offset = 0;

클릭 이벤트 호출 내에서 GetWishes 함수를 호출합니다.

1
GetWishes(offset);

또한 표시된 레코드 수에 따라 offset도 증가시킵니다.

1
offset = offset + 2;

그러나 GetWishes 함수가 호출될 때마다 offset의 값은 항상 마지막으로 설정될 것입니다. 따라서 GetWishes 함수에 올바른 오프셋을 전달하기 위해 자바스크립트 클로저(JavaScript Closure)를 사용하겠습니다.

1
var offset = 0;
2
3
for (var i = 0; i < pageCount; i++) {
4
5
    var aPage = $('<a/>').attr('href', '#').text(i + 1);
6
  
7
    $(aPage).click(function(offset) {
8
        return function() {
9
            GetWishes(offset);
10
        }
11
    }(offset));
12
  
13
    var page = $('<li/>').append(aPage);
14
    $('.pagination').append(page);
15
    offset = offset + itemsPerPage;
16
17
}

위의 모든 변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 로그인 정보로 로그인하고 사용자 홈 페이지로 이동한 후 페이징 UL의 페이지를 클릭해 보십시오.

다음으로 이전 페이지 링크와 다음 페이지 링크를 구현하겠습니다. 조금 복잡해 보일 수도 있으므로 구현을 시작하기 전에 조금 설명하겠습니다.

여기서는 한 번에 5개의 페이지를 표시하겠습니다. 다음 및 이전 링크를 통해 사용자는 다음 5개 및 이전 5개 페이지로 각각 이동할 수 있습니다. 시작 페이지와 끝 페이지의 값을 저장하고 다음 버튼 클릭과 이전 버튼 클릭에 대해 계속 업데이트합니다. 먼저 userHome.html 페이지에 두 개의 숨김(hidden) 필드를 추가해 보겠습니다.

1
<input type="hidden" id="hdnStart" value="1" />
2
<input type="hidden" id="hdnEnd" value="5"/>

GetWishes 성공 콜백에서는 .pagination UL을 비운 후에 다음 코드를 추가해서 최신 시작 페이지와 끝 페이지를 가져옵니다.

1
$('.pagination').empty();
2
3
var pageStart = $('#hdnStart').val();
4
var pageEnd = $('#hdnEnd').val();

1~5페이지를 표시할 때는 이전 버튼 링크가 표시되지 않습니다. 표시된 페이지가 5보다 크면 이전 버튼 링크가 표시됩니다.

1
if (pageStart > 5) {
2
    var aPrev = $('<a/>').attr({
3
            'href': '#'
4
        }, {
5
            'aria-label': 'Previous'
6
        })
7
        .append($('<span/>').attr('aria-hidden', 'true').html('&laquo;'));
8
9
    $(aPrev).click(function() {
10
        // Previous button logic

11
    });
12
13
    var prevLink = $('<li/>').append(aPrev);
14
    $('.pagination').append(prevLink);
15
}

사용자가 이전 버튼을 클릭하면 hdnStarthdnEnd 값을 재설정한 후 GetWishes 자바스크립트 함수를 호출합니다.

1
$(aPrev).click(function() {
2
    $('#hdnStart').val(Number(pageStart) - 5);
3
    $('#hdnEnd').val(Number(pageStart) - 5 + 4);
4
    GetWishes(Number(pageStart) - 5);
5
});

다음으로, 시작 페이지와 끝 페이지를 기반으로 페이지 링크를 반복 생성해서 .pagination UL을 추가합니다.

1
for (var i = Number(pageStart); i <= Number(pageEnd); i++) {
2
3
    if (i > pageCount) {
4
        break;
5
    }
6
7
8
    var aPage = $('<a/>').attr('href', '#').text(i);
9
    
10
    // Attach the page click event

11
    $(aPage).click(function(i) {
12
        return function() {
13
            GetWishes(i);
14
        }
15
    }(i));
16
    
17
    var page = $('<li/>').append(aPage);
18
19
    // Attach the active page class

20
    if ((_page) == i) {
21
        $(page).attr('class', 'active');
22
    }
23
24
    $('.pagination').append(page);
25
26
27
}

총 페이지 수와 페이지 시작 값을 비교해서 다음 버튼 링크를 표시할지 결정합니다.

1
if ((Number(pageStart) + 5) <= pageCount) {
2
    var nextLink = $('<li/>').append($('<a/>').attr({
3
            'href': '#'
4
        }, {
5
            'aria-label': 'Next'
6
        })
7
        .append($('<span/>').attr('aria-hidden', 'true').html('&raquo;').click(function() {
8
            $('#hdnStart').val(Number(pageStart) + 5);
9
            $('#hdnEnd').val(Number(pageStart) + 5 + 4);
10
            GetWishes(Number(pageStart) + 5);
11
12
        })));
13
    $('.pagination').append(nextLink);
14
}

위의 코드에서 볼 수 있듯이 다음 버튼을 클릭할 때 hdnStarthdnEnd 버튼 값을 재설정하고 GetWishes 자바스크립트 함수를 호출합니다.

다음은 최종 GetWishes 자바스크립트 함수입니다.

1
function GetWishes(_page) {
2
3
    var _offset = (_page - 1) * 2;
4
  
5
    $.ajax({
6
        url: '/getWish',
7
        type: 'POST',
8
        data: {
9
            offset: _offset
10
        },
11
        success: function(res) {
12
13
            var itemsPerPage = 2;
14
15
            var wishObj = JSON.parse(res);
16
17
            $('#ulist').empty();
18
            $('#listTemplate').tmpl(wishObj[0]).appendTo('#ulist');
19
20
            var total = wishObj[1]['total'];
21
            var pageCount = total / itemsPerPage;
22
            var pageRem = total % itemsPerPage;
23
            if (pageRem != 0) {
24
                pageCount = Math.floor(pageCount) + 1;
25
            }
26
27
28
            $('.pagination').empty();
29
30
            var pageStart = $('#hdnStart').val();
31
            var pageEnd = $('#hdnEnd').val();
32
33
34
35
36
            if (pageStart > 5) {
37
                var aPrev = $('<a/>').attr({
38
                        'href': '#'
39
                    }, {
40
                        'aria-label': 'Previous'
41
                    })
42
                    .append($('<span/>').attr('aria-hidden', 'true').html('&laquo;'));
43
44
                $(aPrev).click(function() {
45
                    $('#hdnStart').val(Number(pageStart) - 5);
46
                    $('#hdnEnd').val(Number(pageStart) - 5 + 4);
47
                    GetWishes(Number(pageStart) - 5);
48
                });
49
50
                var prevLink = $('<li/>').append(aPrev);
51
                $('.pagination').append(prevLink);
52
            }
53
54
55
56
            for (var i = Number(pageStart); i <= Number(pageEnd); i++) {
57
58
                if (i > pageCount) {
59
                    break;
60
                }
61
62
63
                var aPage = $('<a/>').attr('href', '#').text(i);
64
65
                $(aPage).click(function(i) {
66
                    return function() {
67
                        GetWishes(i);
68
                    }
69
                }(i));
70
                var page = $('<li/>').append(aPage);
71
72
                if ((_page) == i) {
73
                    $(page).attr('class', 'active');
74
                }
75
76
                $('.pagination').append(page);
77
78
79
            }
80
            if ((Number(pageStart) + 5) <= pageCount) {
81
                var nextLink = $('<li/>').append($('<a/>').attr({
82
                        'href': '#'
83
                    }, {
84
                        'aria-label': 'Next'
85
                    })
86
                    .append($('<span/>').attr('aria-hidden', 'true').html('&raquo;').click(function() {
87
                        $('#hdnStart').val(Number(pageStart) + 5);
88
                        $('#hdnEnd').val(Number(pageStart) + 5 + 4);
89
                        GetWishes(Number(pageStart) + 5);
90
91
                    })));
92
                $('.pagination').append(nextLink);
93
            }
94
95
96
97
98
        },
99
        error: function(error) {
100
            console.log(error);
101
        }
102
    });
103
}

모든 변경사항을 저장하고 서버를 다시 시작합니다. 유효한 이메일 주소와 비밀번호를 사용해서 로그인합니다. 사용자 희망사항 목록에 대해 페이징이 완전히 기능하는 모습을 볼 수 있을 것입니다.

정리

이번 연재 기사에서는 사용자 홈 페이지의 희망사항 목록에 대한 페이징 기능을 구현했습니다. MySQL 저장 프로시저를 이용해 데이터를 조회하고 해당 데이터와 jQuery, 부트스트랩을 이용해 페이징을 만드는 방법을 살펴봤습니다.

다음 6부에서는 애플리케이션에 파일 업로드 기능을 구현하겠습니다.

이 튜토리얼의 소스코드는 깃허브에서 확인하실 수 있습니다.

아래 댓글로 여러분의 생각을 알려주세요!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.