Advertisement
  1. Code
  2. Coding Fundamentals
  3. Databases & SQL

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

Scroll to top
Read Time: 9 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 6
Creating a Web App From Scratch Using Python Flask and MySQL: Part 8

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

이 연재 기사의 6부에서는 희망사항을 추가할 때 사용자가 이미지를 업로드하는 기능을 구현했습니다. 또한 희망사항 추가 페이지에서 사용자의 희망사항과 관련된 몇 가지 옵션을 추가했습니다. 이번 튜토리얼에서는 특정 희망사항에 '좋아요'를 표시하는 기능을 구현하는 것으로 다음 단계로 넘어가겠습니다.

시작하기

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

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

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

1
cd PythonFlaskMySQLApp_Part6
2
python app.py

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

대시보드 UI 만들기

이번에는 다른 사용자의 모든 희망사항이 표시되는 dashboard라는 새로운 페이지를 만들겠습니다. 모든 사용자는 대시보드에 표시된 희망사항에 '좋아요'를 표시하거나 댓글을 달 수 있습니다. 그럼 템플릿 폴더로 이동한 후 dashboard.html이라는 파일을 만듭니다. dashboard.html을 열고 다음 HTML 코드를 추가합니다.

1
<!DOCTYPE html>
2
<html lang="en">
3
4
<head>
5
    <title>Python Flask Bucket List App</title>
6
7
8
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
9
10
    <link href="http://getbootstrap.com/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet">
11
12
  
13
14
</head>
15
16
<body>
17
18
    <div class="container">
19
        <div class="header">
20
            <nav>
21
                <ul class="nav nav-pills pull-right">
22
                    <li role="presentation" class="active"><a href="#">Dashboard</a></li>
23
                    <li role="presentation"><a href="/userHome">My List</a></li>
24
                    <li role="presentation"><a href="/showAddWish">Add Item</a></li>
25
                    <li role="presentation"><a href="/logout">Logout</a></li>
26
                </ul>
27
            </nav>
28
            <h3 class="text-muted">Python Flask App</h3>
29
        </div>
30
31
        <div class="well">
32
            <div class="row">
33
                <div class="col-sm-4 col-md-4">
34
                    <div class="thumbnail">
35
                        <img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;">
36
                        <div class="caption">
37
                            <h3>Bungee Jumping</h3>
38
                            <p>vehicula ut id elit.</p>
39
                            <p>
40
                                <button type="button" class="btn btn-danger btn-sm">
41
                                    <span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
42
                                </button>
43
                            </p>
44
                        </div>
45
                    </div>
46
                </div>
47
                <div class="col-sm-4 col-md-4">
48
                    <div class="thumbnail">
49
                        <img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;">
50
                        <div class="caption">
51
                            <h3>Bungee Jumping</h3>
52
                            <p>vehicula ut id elit.</p>
53
                            <p>
54
                                <button type="button" class="btn btn-danger btn-sm">
55
                                    <span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
56
                                </button>
57
                            </p>
58
                        </div>
59
                    </div>
60
                </div>
61
                <div class="col-sm-4 col-md-4">
62
                    <div class="thumbnail">
63
                        <img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;">
64
                        <div class="caption">
65
                            <h3>Bungee Jumping</h3>
66
                            <p>vehicula ut id elit.</p>
67
                            <p>
68
                                <button type="button" class="btn btn-danger btn-sm">
69
                                    <span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
70
                                </button>
71
                            </p>
72
                        </div>
73
                    </div>
74
                </div>
75
76
77
                <div class="row">
78
                    <div class="col-sm-4 col-md-4">
79
                        <div class="thumbnail">
80
                            <img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;">
81
                            <div class="caption">
82
                                <h3>Bungee Jumping</h3>
83
                                <p>vehicula ut id elit.</p>
84
                                <p>
85
                                    <button type="button" class="btn btn-danger btn-sm">
86
                                        <span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
87
                                    </button>
88
                                </p>
89
                            </div>
90
                        </div>
91
                    </div>
92
                    <div class="col-sm-4 col-md-4">
93
                        <div class="thumbnail">
94
                            <img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;">
95
                            <div class="caption">
96
                                <h3>Bungee Jumping</h3>
97
                                <p>vehicula ut id elit.</p>
98
                                <p>
99
                                    <button type="button" class="btn btn-danger btn-sm">
100
                                        <span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
101
                                    </button>
102
                                </p>
103
                            </div>
104
                        </div>
105
                    </div>
106
                    <div class="col-sm-4 col-md-4">
107
                        <div class="thumbnail">
108
                            <img alt="100%x200" src="static/Uploads/bucketList.png" data-holder-rendered="true" style="height: 150px; width: 150px; display: block;">
109
                            <div class="caption">
110
                                <h3>Bungee Jumping</h3>
111
                                <p>vehicula ut id elit.</p>
112
                                <p>
113
                                    <button type="button" class="btn btn-danger btn-sm">
114
                                        <span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
115
                                    </button>
116
                                </p>
117
                            </div>
118
                        </div>
119
                    </div>
120
                </div>
121
122
            </div>
123
124
            <footer class="footer">
125
                <p>&copy; Company 2015</p>
126
            </footer>
127
128
        </div>
129
</body>
130
131
</html>

app.py를 열고 /showDashboard라는 새로운 라우트를 만듭니다. 이 라우트를 이용해 대시보드 페이지를 렌더링하겠습니다.

1
@app.route('/showDashboard')
2
def showDashboard():
3
    return render_template('dashboard.html')

/validateLogin 메서드를 수정해서 사용자가 로그인에 성공햇을 때 사용자 홈 페이지 대신 대시보드 페이지로 리디렉션하도록 만들겠습니다.

1
return redirect('/showDashboard')

위 변경 사항을 저장하고 서버를 다시 시작합니다. 브라우저에서 http://localhost:5002/로 이동한 후 유효한 이메일 주소와 비밀번호를 이용해 로그인합니다. 로그인하고 나면 대시보드 페이지를 볼 수 있을 것입니다.

Dashboard PageDashboard PageDashboard Page

위의 이미지에서 볼 수 있듯이 다른 사용자가 만든 모든 희망사항을 볼 수 있고 다른 사용자가 '좋아요'를 표시하게 할 수 있습니다.

대시보드 채우기

먼저 대시보드를 채우기 위해 데이터베이스에서 데이터를 가져와야 합니다. 따라서 사용자가 만든 희망사항을 가져오는 저장 프로시저를 만들겠습니다.

1
USE `BucketList`;
2
DROP procedure IF EXISTS `sp_GetAllWishes`;
3
4
DELIMITER $$
5
USE `BucketList`$$
6
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetAllWishes`()
7
BEGIN
8
    select wish_id,wish_title,wish_description,wish_file_path from tbl_wish where wish_private = 0;
9
END$$
10
11
DELIMITER ;
12

위의 저장 프로시저는 비공개로 표시되지 않은 모든 희망사항을 tbl_wish에서 가져옵니다.

다음으로 sp_GetAllWishes 저장 프로시저를 호출하는 새로운 파이썬 메서드를 작성하겠습니다. app.py를 열고 getAllWishes 메서드에 다음 코드를 추가합니다.

1
@app.route('/getAllWishes')
2
def getAllWishes():
3
    try:
4
        if session.get('user'):
5
            
6
            conn = mysql.connect()
7
            cursor = conn.cursor()
8
            cursor.callproc('sp_GetAllWishes')
9
            result = cursor.fetchall()
10
        
11
12
      
13
            wishes_dict = []
14
            for wish in result:
15
                wish_dict = {
16
                        'Id': wish[0],
17
                        'Title': wish[1],
18
                        'Description': wish[2],
19
                        'FilePath': wish[3]}
20
                wishes_dict.append(wish_dict)		
21
22
           
23
24
            return json.dumps(wishes_dict)
25
        else:
26
            return render_template('error.html', error = 'Unauthorized Access')
27
    except Exception as e:
28
        return render_template('error.html',error = str(e))

위 메서드에서는 먼저 유효한 사용자 세션을 확인한 다음 MySQL 연결을 생성합니다. MySQL 연결인 conn을 생성한 후 커서를 통해 필요한 데이터를 가져오는 sp_GetAllWishes 저장 프로시저를 호출했습니다. 데이터를 가져온 후 결과를 파싱하고 적절한 JSON 문자열을 반환했습니다.

대시보드 페이지가 로드될 때 앞에서 작성한 /getAllWishes 메서드를 호출할 것입니다. dashboard.html을 열고 jQuery AJAX를 사용해 document.ready에서 /getAllWishes를 호출합니다.

1
$(function() {
2
    $.ajax({
3
        url: '/getAllWishes',
4
        type: 'GET',
5
        success: function(response) {
6
            console.log(response);
7
        },
8
        error: function(error) {
9
            console.log(error);
10
        }
11
    });
12
})

위의 변경 사항을 저장하고 서버를 다시 시작합니다. 애플리케이션에 로그인한 후 브라우저 콘솔을 확인하면 데이터베이스에서 가져온 데이터를 볼 수 있을 것입니다.

1
[{
2
    "Description": "Bungee Jumping",
3
    "FilePath": "static/Uploads/de5f8a10-54ea-49f4-80ce-35626277047e.jpg",
4
    "Id": 10,
5
    "Title": "Bungee Jumping"
6
}, {
7
    "Description": "Mount Everest climb",
8
    "FilePath": "static/Uploads/e3e8f7fa-6cb9-4cc3-9989-a80e5089546f.png",
9
    "Id": 11,
10
    "Title": "Mount Everest climb"
11
}, {
12
    "Description": "River Rafting",
13
    "FilePath": "static/Uploads/dff3a64c-5193-42b5-9cdb-9d67a7bbacab.png",
14
    "Id": 14,
15
    "Title": "River Rafting"
16
}, {
17
    "Description": "Deep Sea Diving",
18
    "FilePath": "static/Uploads/b0656759-c038-46b4-9529-c208aaa6bfb7.png",
19
    "Id": 15,
20
    "Title": "Deep Sea Diving"
21
}]

이번에는 응답에 담긴 데이터를 사용해 대시보드 페이지를 채우겠습니다. 먼저, dashboard.html에서 .well 클래스가 지정된 div 사이의 HTML 코드를 제거합니다.

1
<div class="well">
2
3
<!-- We'll populate this dynamically -->
4
5
</div>

AJAX 호출의 성공 콜백에서는 response를 자바스크립트 객체로 파싱합니다.

1
var data = JSON.parse(response);

여기서는 각 행마다 세 가지 희망사항 각각에 대해 jQuery를 사용해 동적으로 섬네일 HTML 코드를 생성해야 합니다. 그럼 먼저 HTML 코드를 동적으로 작성하는 자바스크립트 함수를 작성해 봅시다. 다음은 jQuery를 사용해 동적으로 생성할 HTML 코드입니다.

1
<div class="col-sm-4 col-md-4">
2
    <div class="thumbnail"><img src="static/Uploads/de5f8a10-54ea-49f4-80ce-35626277047e.jpg" data-holder-rendered="true" style="height: 150px; width: 150px; display: block">
3
        <div class="caption">
4
            <h3>Testing App</h3>
5
            <p>hello</p>
6
            <p>
7
                <button type="button" class="btn btn-danger btn-sm"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span></button>
8
            </p>
9
        </div>
10
    </div>
11
</div>

자바스크립트 함수의 이름은 CreateThumb로 명명하겠습니다. 이 함수에서는 HTML 요소를 만들고 해당 요소를 부모 요소에 추가해서 위에 표시된 HTML 코드를 만들겠습니다.

1
function CreateThumb(id,title, desc, filepath) {
2
  
3
    var mainDiv = $('<div>').attr('class', 'col-sm-4 col-md-4');
4
  
5
    var thumbNail = $('<div>').attr('class', 'thumbnail');
6
                                    
7
    var img = $('<img>').attr({
8
        'src': filepath,
9
        'data-holder-rendered': true,
10
        'style': 'height: 150px; width: 150px; display: block'
11
    });
12
  
13
    var caption = $('<div>').attr('class', 'caption');
14
  
15
    var title = $('<h3>').text(title);
16
  
17
    var desc = $('<p>').text(desc);
18
19
20
    var p = $('<p>');
21
  
22
    var btn = $('<button>').attr({
23
        'id': 'btn_' + id, 
24
        'type': 'button',
25
        'class': 'btn btn-danger btn-sm'
26
    });
27
  
28
    var span = $('<span>').attr({
29
        'class': 'glyphicon glyphicon-thumbs-up',
30
        'aria-hidden': 'true'
31
    });
32
33
    p.append(btn.append(span));
34
35
36
37
    caption.append(title);
38
    caption.append(desc);
39
    caption.append(p);
40
41
    thumbNail.append(img);
42
    thumbNail.append(caption);
43
    mainDiv.append(thumbNail);
44
    return mainDiv;
45
46
47
}

위의 코드는 상당히 직관적이므로 자세한 내용은 다루지 않겠습니다.

다음 단계로는 파싱된 JSON 응답을 순회하고, 그 과정에서 CreateThumb 함수를 통해 HTML을 생성하겠습니다. 여기서는 각 행마다 세 가지 희망사항을 표시할 계획입니다. 그럼 이를 확인하고 세 개의 희망사항마다 새로운 행을 하나씩 만들겠습니다. dashboard.html에서 AJAX 호출의 success 콜백에 다음 코드를 추가합니다.

1
var itemsPerRow = 0;
2
var div = $('<div>').attr('class', 'row');
3
for (var i = 0; i < data.length; i++) {
4
    
5
6
    if (itemsPerRow < 3) {
7
      
8
        if (i == data.length - 1) {
9
            div.append(CreateThumb(data[i].Id,data[i].Title, data[i].Description, data[i].FilePath));
10
            $('.well').append(div);
11
        } else {
12
            div.append(CreateThumb(data[i].Id,data[i].Title, data[i].Description, data[i].FilePath));
13
            itemsPerRow++;
14
        }
15
    } else {
16
        $('.well').append(div);
17
        div = $('<div>').attr('class', 'row');
18
        div.append(CreateThumb(data[i].Id,data[i].Title, data[i].Description, data[i].FilePath));
19
        if (i == data.length - 1) {
20
            $('.well').append(div);
21
        }
22
        itemsPerRow = 1;
23
    }
24
    
25
}

변경 사항을 저장하고 서버를 다시 시작합니다. 애플리케이션에 로그인한 후 대시보드 페이지로 이동하면 다른 사용자가 추가한 희망사항을 비롯해 각 희망사항에 '좋아요' 표시를 할 수 있는 화면을 볼 수 있을 것입니다.

다음으로, 희망사항 섬네일 아래의 '좋아요' 버튼에 click 이벤트를 추가해 보겠습니다. 앞에서 버튼을 동적으로 생성했으므로 jQuery on 메서드를 사용해 버튼에 클릭 이벤트를 첨부해야 할 것입니다.

1
$(document).on('click', '[id^="btn_"]', function() {
2
    // Event function can be added here

3
});

'좋아요' 기능 구현하기

특정 희망사항이 받은 '좋아요'를 추적하는 테이블을 만드는 것으로 시작하겠습니다. tbl_likes라는 테이블을 만듭니다.

1
CREATE TABLE `BucketList`.`tbl_likes` (
2
  `wish_id` INT NOT NULL,
3
  `like_id` INT NOT NULL AUTO_INCREMENT,
4
  `user_id` INT NULL,
5
  `wish_like` INT NULL DEFAULT 0 ;
6
  PRIMARY KEY (`like_id`));

이제 사용자가 특정 희망사항에 '좋아요'를 표시하거나 표시를 취소할 때마다 이 테이블을 업데이트하겠습니다. 위 테이블을 업데이트하는 MySQL 저장 프로시저를 만들어 봅시다.

1
DELIMITER $$
2
3
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_AddUpdateLikes`(
4
    p_wish_id int,
5
	p_user_id int,
6
	p_like int
7
)
8
BEGIN
9
	if (select exists (select 1 from tbl_likes where wish_id = p_wish_id and user_id = p_user_id)) then
10
11
		update tbl_likes set wish_like = p_like where wish_id = p_wish_id and user_id = p_user_id;
12
		
13
	else
14
		
15
		insert into tbl_likes(
16
			wish_id,
17
			user_id,
18
			wish_like
19
		)
20
		values(
21
			p_wish_id,
22
			p_user_id,
23
			p_like
24
		);
25
26
	end if;
27
END

이 저장 프로시저에서는 어떤 사용자가 이미 희망사항에 '좋아요'를 표시했는지 여부를 간단히 확인했습니다. 해당 사용자가 이미 '좋아요'를 표시했다면 해당 '좋아요' 항목을 업데이트하거나 새로운 '좋아요'를 추가합니다.

이번에는 위의 저장 프로시저를 호출하는 파이썬 메서드를 만들어 봅시다.

1
@app.route('/addUpdateLike',methods=['POST'])
2
def addUpdateLike():
3
    try:
4
        if session.get('user'):
5
            _wishId = request.form['wish']
6
            _like = request.form['like']
7
            _user = session.get('user')
8
           
9
10
            conn = mysql.connect()
11
            cursor = conn.cursor()
12
            cursor.callproc('sp_AddUpdateLikes',(_wishId,_user,_like))
13
            data = cursor.fetchall()
14
15
            if len(data) is 0:
16
                conn.commit()
17
                return json.dumps({'status':'OK'})
18
            else:
19
                return render_template('error.html',error = 'An error occurred!')
20
21
        else:
22
            return render_template('error.html',error = 'Unauthorized Access')
23
    except Exception as e:
24
        return render_template('error.html',error = str(e))
25
    finally:
26
        cursor.close()
27
        conn.close()

이것은 sp_AddUpdateLikes 저장 프로시저를 호출하는 파이썬 메서드입니다. 이 메서드에서는 유효한 사용자 세션을 확인한 다음 희망사항 IDlike 상태를 업데이트하는 저장 프로시저에 전달합니다. 사용자가 '좋아요' 버튼을 클릭하면 /addUpdateLike 파이썬 메서드를 호출해야 합니다. 따라서 dashboard.htmllike 버튼 클릭 이벤트 함수에 다음 코드를 추가합니다.

1
$(document).on('click', '[id^="btn_"]', function() {
2
    $.ajax({
3
        url: '/addUpdateLike',
4
        method: 'POST',
5
        data: {
6
            wish: $(this).attr('id').split('_')[1],
7
            like: 1
8
        },
9
        success: function(response) {
10
            console.log(response);
11
        },
12
        error: function(error) {
13
            console.log(error);
14
        }
15
    });
16
});

당분간 위의 함수 호출처럼 값을 하드코딩하겠습니다. 그럼 변경 사항을 저장하고 서버를 다시 시작합니다. 애플리케이션에 로그인하고 아무 희망사항 섬네일 아래에 있는 '좋아요' 버튼을 클릭합니다. 이제 tbl_likes를 확인해 보면 해당 항목을 볼 수 있을 것입니다.

정리

이번 연재 기사에서는 다양한 사용자가 작성한 희망사항이 담긴 애플리케이션의 대시보드 페이지를 채웠습니다. 또한 사용자가 특정 희망사항에 '좋아요'를 표시할 수 있도록 각각에 '좋아요' 버튼을 추가했습니다. 다음 8부에서는 '좋아요' 표시를 바꾸는 방법을 살펴보고 특정 희망사항이 받은 전체 '좋아요' 개수를 표시하는 방법을 살펴보겠습니다.

제안사항 또는 정정해야 할 오류가 있다면 아래 댓글로 알려주세요. 이 튜토리얼의 소스코드는 깃허브에서 확인하실 수 있습니다.

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.