() translation by (you can also view the original English article)
이 연재 기사의 5부에서는 사용자 홈 페이지의 희망사항에 대한 페이징을 구현했습니다. 이번 6부에서는 사용자가 희망사항을 나타내는 이미지를 업로드하는 옵션과 희망사항을 달성한 것으로 표시하는 옵션, 그리고 개인정보 보호를 설정하는 옵션을 구현하겠습니다.
시작하기
먼저 깃허브(GitHub)에서 튜토리얼의 이전 내용을 복제합니다.
1 |
git clone https://github.com/jay3dec/PythonFlaskMySQLApp_Part5.git |
소스코드가 복제되면 프로젝트 디렉터리로 이동한 후 웹 서버를 구동합니다.
1 |
cd PythonFlaskMySQLApp_Part5
|
2 |
python app.py |
브라우저에서 http://localhost:5002/로 이동하면 실행 중인 애플리케이션을 볼 수 있을 것입니다.
사용자 인터페이스 수정하기
먼저 "희망사항 추가" 페이지에 이미지를 업로드하는 옵션을 포함하도록 수정하겠습니다. templates/addWish.html
로 이동합니다. addWish.html
의 폼은 상당히 작아 보이므로 폼을 세로로 만들기 위해 부트스트랩 HTML 코드를 수정해 봅시다.
먼저 form-horizontal
을 수직 폼으로 수정할 것이므로 form-horizontal
클래스를 폼에서 제거합니다. 또한 사진을 업로드하는 파일 업로드 컨트롤과 희망사항을 비공개로 표시하는 체크박스, 희망사항을 완료된 것으로 표시하는 또 한 개의 체크박스로 세 가지 컨트롤도 새로 추가할 것입니다. 다음은 수정된 addWish.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 |
<script src="../static/js/jquery-1.11.2.js"></script> |
13 |
<style>
|
14 |
.btn-file { |
15 |
position: relative; |
16 |
overflow: hidden; |
17 |
}
|
18 |
|
19 |
.btn-file input[type=file] { |
20 |
position: absolute; |
21 |
top: 0; |
22 |
right: 0; |
23 |
min-width: 100%; |
24 |
min-height: 100%; |
25 |
font-size: 100px; |
26 |
text-align: right; |
27 |
filter: alpha(opacity=0); |
28 |
opacity: 0; |
29 |
outline: none; |
30 |
background: white; |
31 |
cursor: inherit; |
32 |
display: block; |
33 |
}
|
34 |
</style>
|
35 |
|
36 |
</head>
|
37 |
|
38 |
<body>
|
39 |
|
40 |
<div class="container"> |
41 |
<div class="header"> |
42 |
<nav>
|
43 |
<ul class="nav nav-pills pull-right"> |
44 |
<li role="presentation" class="active"><a href="#">Add Item</a> |
45 |
</li>
|
46 |
<li role="presentation"><a href="/logout">Logout</a> |
47 |
</li>
|
48 |
</ul>
|
49 |
</nav>
|
50 |
<h3 class="text-muted">Python Flask App</h3> |
51 |
</div>
|
52 |
|
53 |
<form role="form" method="post" action="/addWish"> |
54 |
|
55 |
|
56 |
<!-- Form Name -->
|
57 |
<legend>Create Your Wish</legend> |
58 |
|
59 |
<!-- Text input-->
|
60 |
<div class="form-group"> |
61 |
<label for="txtTitle">Title</label> |
62 |
|
63 |
<input id="txtTitle" name="inputTitle" type="text" placeholder="placeholder" class="form-control input-md"> |
64 |
|
65 |
</div>
|
66 |
|
67 |
<!-- Textarea -->
|
68 |
<div class="form-group"> |
69 |
<label for="txtPost">Description</label> |
70 |
|
71 |
<textarea class="form-control" id="txtPost" name="inputDescription"></textarea> |
72 |
|
73 |
</div>
|
74 |
|
75 |
|
76 |
<div class="form-group"> |
77 |
<label for="txtPost">Photos</label> |
78 |
|
79 |
<div class="input-group"> |
80 |
<span class="input-group-btn"> |
81 |
<span class="btn btn-primary btn-file"> |
82 |
Browse… <input type="file" id="fileupload" name="file" multiple> |
83 |
</span>
|
84 |
</span>
|
85 |
<input type="text" class="form-control" readonly> |
86 |
</div>
|
87 |
|
88 |
</div>
|
89 |
|
90 |
<div class="form-group"> |
91 |
<label>Mark this as private and not visible to others.</label> |
92 |
<br/>
|
93 |
<input type="checkbox"> Mark as Private <span class="glyphicon glyphicon-lock" aria-hidden="true"></span> |
94 |
</div>
|
95 |
|
96 |
<div class="form-group"> |
97 |
<label>Have you already accomplished this?</label> |
98 |
<br/>
|
99 |
<input type="checkbox"> Mark as Done <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> |
100 |
</div>
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
<!-- Button -->
|
106 |
<div class="form-group"> |
107 |
|
108 |
<p class="text-center"> |
109 |
<input id="singlebutton" name="singlebutton" class="btn btn-primary" type="submit" value="Publish" /> |
110 |
</p>
|
111 |
</div>
|
112 |
|
113 |
|
114 |
</form>
|
115 |
|
116 |
<footer class="footer"> |
117 |
<p>© Company 2015</p> |
118 |
</footer>
|
119 |
|
120 |
</div>
|
121 |
</body>
|
122 |
|
123 |
</html>
|
변경 사항을 저장하고 서버를 다시 시작합니다. 로그인한 후 Add Wish 링크를 클릭하면 수정된 희망사항 추가 페이지를 볼 수 있을 것입니다.



업로드 기능 구현하기
여기서는 blueimp jQuery-File-Upload를 이용해 파일 업로드 기능을 구현하겠습니다. 깃허브에서 필요한 파일을 다운로드합니다. 소스의 압축을 풀고 addWish.html
에 다음과 같은 스크립트 참조를 추가합니다.
1 |
<script src="../static/js/jquery-1.11.2.js"></script> |
2 |
|
3 |
<script src="../static/js/jquery.ui.widget.js"></script> |
4 |
|
5 |
<script type="text/javascript" src="../static/js/jquery.fileupload.js"></script> |
6 |
|
7 |
<script type="text/javascript" src="../static/js/jquery.fileupload-process.js"></script> |
8 |
|
9 |
<script type="text/javascript" src="../static/js/jquery.fileupload-ui.js"></script> |
addWish.html
페이지 로드에 대해 파일 플러그인을 시작하는 코드를 파일 업로드 버튼 클릭에 추가합니다.
1 |
$(function() { |
2 |
$('#fileupload').fileupload({ |
3 |
url: 'upload', |
4 |
dataType: 'json', |
5 |
add: function(e, data) { |
6 |
data.submit(); |
7 |
},
|
8 |
success: function(response, status) { |
9 |
console.log(response); |
10 |
},
|
11 |
error: function(error) { |
12 |
console.log(error); |
13 |
}
|
14 |
});
|
15 |
})
|
위 코드에서 볼 수 있듯이 파일 업로드 플러그인을 #fileupload
버튼에 첨부했습니다. 파일 업로드 플러그인은 파일을 /upload
요청 핸들러에 게시하며, 이 핸들러는 파이썬 코드에서 정의할 것입니다. 또한 데이터를 제출하는 add
함수를 비롯해 업로드 성공과 실패를 처리하는 success
콜백과 failure
콜백도 정의했습니다.
다음으로 app.py
에 upload
파이썬 파일 업로드 핸들러를 정의해 봅시다. /upload
라우트를 다음과 같이 정의합니다.
1 |
@app.route('/upload', methods=['GET', 'POST']) |
2 |
def upload(): |
3 |
# file upload handler code will be here
|
요청이 POST
요청인지 확인한 후 POST 요청일 경우 요청으로부터 파일을 읽습니다.
1 |
if request.method == 'POST': |
2 |
file = request.files['file'] |
파일을 저장하려면 이미지 파일의 확장자도 가져와야 할 것입니다. 따라서 os
를 임포트한 다음, 파일명에서 확장자명을 분리합니다.
1 |
extension = os.path.splitext(file.filename)[1] |
파일 확장자를 확보하고 나면 uuid
를 이용해 새로운 고유한 파일명을 만들 것입니다. uuid
를 임포트해서 파일명을 만듭니다.
1 |
f_name = str(uuid.uuid4()) + extension |
정적 폴더에 Uploads
라는 폴더를 만듭니다. 이곳이 바로 업로드된 이미지를 보관할 곳입니다. 앱 설정에서 Upload 폴더의 경로를 추가합니다.
1 |
app.config['UPLOAD_FOLDER'] = 'static/Uploads' |
이제 게시된 파일을 UPLOAD_FOLDER
위치에 저장하고 파일명을 응답으로 반환합니다.
1 |
file.save(os.path.join(app.config['UPLOAD_FOLDER'], f_name)) |
2 |
return json.dumps({'filename':f_name}) |
변경 사항을 저장하고 서버를 다시 시작합니다. 브라우저에서 http://localhost:5002/로 이동한 후 유효한 로그인 정보를 이용해 로그인합니다. 찾아보기 버튼을 사용해 이미지를 업로드하고, 업로드가 완료되면 브라우저 콘솔을 확인합니다. 업로드된 파일의 이름이 반환된 것을 볼 수 있을 것입니다.
읽기 전용 입력 텍스트 필드 대신 이미지 요소를 추가해서 업로드된 이미지를 표시해 봅시다. 따라서 읽기 전용 입력 텍스트 필드를 다음 HTML 코드로 바꿉니다.
1 |
<div class="pull-right"> |
2 |
<img id="imgUpload" style="width: 140px; height: 140px;" class="img-thumbnail"> |
3 |
</div>
|
파일 업로드 성공 콜백에서 #imgUpload
의 src
를 업로드된 이미지로 업데이트합니다.
1 |
$('#imgUpload').attr('src','static/Uploads/'+response.filename); |
위의 변경 사항을 저장하고 서버를 다시 시작합니다. 애플리케이션에 로그인해서 새 이미지 파일을 업로드하면 업로드된 이미지를 볼 수 있을 것입니다.



이번에는 새로운 세 개의 필드를 포함하도록 tbl_wish
테이블 구조를 수정해야 합니다. 아래 그림과 같이 tbl_wish
를 변경합니다.
1 |
ALTER TABLE `BucketList`.`tbl_wish` |
2 |
ADD COLUMN `wish_file_path` VARCHAR(200) NULL AFTER `wish_date`, |
3 |
ADD COLUMN `wish_accomplished` INT NULL DEFAULT 0 AFTER `wish_file_path`, |
4 |
ADD COLUMN `wish_private` INT NULL DEFAULT 0 AFTER `wish_accomplished`; |
다음으로 새로 추가된 필드를 데이터베이스에 포함하도록 sp_addWish
와 sp_updateWish
저장 프로시저를 수정해 봅시다.
새로 추가된 세 개의 필드를 포함하도록 sp_addWish
저장 프로시저를 수정합니다.
1 |
USE `BucketList`; |
2 |
DROP procedure IF EXISTS `sp_addWish`; |
3 |
|
4 |
DELIMITER $$ |
5 |
USE `BucketList`$$ |
6 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_addWish`( |
7 |
IN p_title varchar(45), |
8 |
IN p_description varchar(1000), |
9 |
IN p_user_id bigint, |
10 |
IN p_file_path varchar(200), |
11 |
IN p_is_private int, |
12 |
IN p_is_done int |
13 |
)
|
14 |
BEGIN
|
15 |
insert into tbl_wish( |
16 |
wish_title, |
17 |
wish_description, |
18 |
wish_user_id, |
19 |
wish_date, |
20 |
wish_file_path, |
21 |
wish_private, |
22 |
wish_accomplished
|
23 |
)
|
24 |
values
|
25 |
(
|
26 |
p_title, |
27 |
p_description, |
28 |
p_user_id, |
29 |
NOW(), |
30 |
p_file_path, |
31 |
p_is_private, |
32 |
p_is_done
|
33 |
);
|
34 |
END$$ |
35 |
|
36 |
DELIMITER ; |
37 |
마찬가지로 새로 추가된 세 개의 필드를 포함하도록 sp_updateWish
저장 프로시저도 수정합니다.
1 |
USE `BucketList`; |
2 |
DROP procedure IF EXISTS `sp_updateWish`; |
3 |
|
4 |
DELIMITER $$ |
5 |
USE `BucketList`$$ |
6 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_updateWish`( |
7 |
IN p_title varchar(45), |
8 |
IN p_description varchar(1000), |
9 |
IN p_wish_id bigint, |
10 |
In p_user_id bigint, |
11 |
IN p_file_path varchar(200), |
12 |
IN p_is_private int, |
13 |
IN p_is_done int |
14 |
)
|
15 |
BEGIN
|
16 |
update tbl_wish set |
17 |
wish_title = p_title, |
18 |
wish_description = p_description, |
19 |
wish_file_path = p_file_path, |
20 |
wish_private = p_is_private, |
21 |
wish_accomplished = p_is_done |
22 |
where wish_id = p_wish_id and wish_user_id = p_user_id; |
23 |
END$$ |
24 |
|
25 |
DELIMITER ; |
26 |
다음으로 /addWish
요청 핸들러의 메서드에서 새로 게시된 필드를 읽어 저장 프로시저에 전달하도록 수정합니다.
1 |
if request.form.get('filePath') is None: |
2 |
_filePath = '' |
3 |
else: |
4 |
_filePath = request.form.get('filePath') |
5 |
|
6 |
if request.form.get('private') is None: |
7 |
_private = 0 |
8 |
else: |
9 |
_private = 1 |
10 |
|
11 |
if request.form.get('done') is None: |
12 |
_done = 0 |
13 |
else: |
14 |
_done = 1 |
값을 읽고 나면 그것들을 MySQL 저장 프로시저 호출로 전달할 것입니다.
1 |
cursor.callproc('sp_addWish',(_title,_description,_user,_filePath,_private,_done)) |
addWish.html
페이지에서는 게시할 요소의 name
속성을 설정해야 합니다. 따라서 새로 추가된 두 체크박스에 모두 name
을 추가합니다.
1 |
<input name="private" type="checkbox"> Mark as Private <span class="glyphicon glyphicon-lock" aria-hidden="true"></span> |
2 |
|
3 |
<input name="done" type="checkbox"> Mark as Done <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> |
이제 업로드 파일 경로도 전달해야 합니다. 따라서 숨겨진 입력 필드를 만들고 파일 업로드 성공 콜백에서 값을 설정합니다.
1 |
<input type="hidden" name="filePath" id="filePath"></input> |
파일 업로드 성공 콜백에서 해당 값을 설정합니다.
1 |
success: function(response, status) { |
2 |
|
3 |
var filePath = 'static/Uploads/' + response.filename; |
4 |
$('#imgUpload').attr('src', filePath); |
5 |
|
6 |
$('#filePath').val(filePath); |
7 |
|
8 |
}
|
위의 변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 로그인 정보로 로그인한 후 필요한 모든 세부사항이 포함된 새로운 희망사항을 추가합니다. 희망사항이 성공적으로 추가되면 사용자 홈 페이지에 나열될 것입니다.
희망사항 편집 구현 수정
먼저 세 가지 새로운 필드에 대해 HTML 코드를 추가해야 합니다. 따라서 userHome.html
을 열고 title
과 description
HTML 뒤에 다음과 같은 HTML 코드를 추가합니다.
1 |
<div class="form-group"> |
2 |
<label for="txtPost">Photos</label> |
3 |
|
4 |
<div class="input-group"> |
5 |
<span class="input-group-btn"> |
6 |
<span class="btn btn-primary btn-file"> |
7 |
Browse… <input type="file" id="fileupload" name="file" multiple> |
8 |
</span>
|
9 |
</span>
|
10 |
<div class="pull-right"> |
11 |
<img id="imgUpload" style="width: 140px; height: 140px;" class="img-thumbnail"> |
12 |
<input type="hidden" name="filePath" id="filePath"></input> |
13 |
</div>
|
14 |
</div>
|
15 |
|
16 |
</div>
|
17 |
|
18 |
<div class="form-group"> |
19 |
<label>Mark this as private and not visible to others.</label> |
20 |
<br/>
|
21 |
<input id="chkPrivate" name="private" type="checkbox"> Mark as Private <span class="glyphicon glyphicon-lock" aria-hidden="true"></span> |
22 |
</div>
|
23 |
|
24 |
<div class="form-group"> |
25 |
<label>Have you already accomplished this?</label> |
26 |
<br/>
|
27 |
<input id="chkDone" name="done" type="checkbox"> Mark as Done <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> |
28 |
</div>
|
희망사항을 편집할 때 위의 필드를 채우는 데 필요한 데이터를 가져와야 합니다. 따라서 다음과 같이 별도의 필드를 포함하도록 sp_GetWishById
저장 프로시저를 수정하겠습니다.
1 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_GetWishById`( |
2 |
IN p_wish_id bigint, |
3 |
In p_user_id bigint |
4 |
)
|
5 |
BEGIN
|
6 |
select wish_id,wish_title,wish_description,wish_file_path,wish_private,wish_accomplished from tbl_wish where wish_id = p_wish_id and wish_user_id = p_user_id; |
7 |
END
|
그런 다음 /getWishById
라우트 메서드의 JSON
문자열에 새 필드가 포함되도록 수정해야 합니다. /getWishById
에서 다음과 같이 희망사항 목록을 수정합니다.
1 |
wish.append({'Id':result[0][0],'Title':result[0][1],'Description':result[0][2],'FilePath':result[0][3],'Private':result[0][4],'Done':result[0][5]}) |
결과를 렌더링하려면 userHome.html
에서 Edit
자바스크립트 함수의 성공 콜백에서 받은 데이터를 파싱해야 합니다.
1 |
success: function(res) { |
2 |
|
3 |
var data = JSON.parse(res); |
4 |
|
5 |
$('#editTitle').val(data[0]['Title']); |
6 |
|
7 |
$('#editDescription').val(data[0]['Description']); |
8 |
|
9 |
$('#imgUpload').attr('src', data[0]['FilePath']); |
10 |
|
11 |
if (data[0]['Private'] == "1") { |
12 |
$('#chkPrivate').attr('checked', 'checked'); |
13 |
}
|
14 |
|
15 |
if (data[0]['Done'] == "1") { |
16 |
$('#chkDone').attr('checked', 'checked'); |
17 |
}
|
18 |
|
19 |
$('#editModal').modal(); |
20 |
|
21 |
}
|
변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 로그인 정보로 로그인한 후 사용자 홈 페이지의 희망사항 목록에서 희망사항을 하나 수정합니다. 편집 팝업에 데이터가 채워질 것입니다.



이제 희망사항 추가 페이지에서 했던 것과 비슷하게 userHome.html
에 jQuery-File-Upload 스크립트 참조를 추가합니다.
1 |
<script src="../static/js/jquery-1.11.2.js"></script> |
2 |
|
3 |
<script src="../static/js/jquery.ui.widget.js"></script> |
4 |
|
5 |
<script type="text/javascript" src="../static/js/jquery.fileupload.js"></script> |
6 |
|
7 |
<script type="text/javascript" src="../static/js/jquery.fileupload-process.js"></script> |
8 |
|
9 |
<script type="text/javascript" src="../static/js/jquery.fileupload-ui.js"></script> |
희망사항 추가 페이지에서 사용한 것과 동일한 코드를 사용해 편집 팝업에서 파일 업로드 컨트롤을 초기화합니다.
1 |
$(function() { |
2 |
$('#fileupload').fileupload({ |
3 |
url: 'upload', |
4 |
dataType: 'json', |
5 |
add: function(e, data) { |
6 |
data.submit(); |
7 |
},
|
8 |
success: function(response, status) { |
9 |
|
10 |
var filePath = 'static/Uploads/' + response.filename; |
11 |
$('#imgUpload').attr('src', filePath); |
12 |
$('#filePath').val(filePath); |
13 |
|
14 |
},
|
15 |
error: function(error) { |
16 |
console.log(error); |
17 |
}
|
18 |
});
|
19 |
})
|
다음으로 편집 팝업의 수정 버튼을 클릭했을 때 추가된 별도의 필드를 포함하도록 수정해야 합니다. 따라서 btnUpdate
버튼 클릭에서 다음과 같이 세 개의 새로운 필드를 포함하도록 전달된 데이터 매개변수를 수정합니다.
1 |
data : {title:$('#editTitle').val(),description:$('#editDescription').val(),id:localStorage.getItem('editId'),filePath:$('#imgUpload').attr('src'),isPrivate:$('#chkPrivate').is(':checked')?1:0,isDone:$('#chkDone').is(':checked')?1:0} |
app.py
를 열고 /updateWish
요청 핸들러 메서드에서 새로 추가된 필드를 파싱하도록 수정합니다.
1 |
_filePath = request.form['filePath'] |
2 |
_isPrivate = request.form['isPrivate'] |
3 |
_isDone = request.form['isDone'] |
추가 매개변수를 포함하도록 프로시저 호출 메서드를 수정합니다.
1 |
cursor.callproc('sp_updateWish',(_title,_description,_wish_id,_user,_filePath,_isPrivate,_isDone)) |
이제 sp_updateWish
를 열고 새로 추가된 필드를 포함하도록 수정합니다.
1 |
DELIMITER $$ |
2 |
|
3 |
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_updateWish`( |
4 |
IN p_title varchar(45), |
5 |
IN p_description varchar(1000), |
6 |
IN p_wish_id bigint, |
7 |
In p_user_id bigint, |
8 |
IN p_file_path varchar(200), |
9 |
IN p_is_private int, |
10 |
IN p_is_done int |
11 |
)
|
12 |
BEGIN
|
13 |
update tbl_wish set |
14 |
wish_title = p_title, |
15 |
wish_description = p_description, |
16 |
wish_file_path = p_file_path, |
17 |
wish_private = p_is_private, |
18 |
wish_accomplished = p_is_done |
19 |
where wish_id = p_wish_id and wish_user_id = p_user_id; |
20 |
END
|
모든 변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 로그인 정보로 로그인한 후 기존 항목을 편집하고 업데이트해보십시오.
정리
이번 연재 기사에서는 blueimp jQuery-File-Upload 플러그인을 통해 파이썬 플라스크 애플리케이션에서 이미지를 업로드하는 방법을 살펴봤습니다. 다음 7부에서는 사용자가 달성한 희망사항들을 애플리케이션 홈 페이지에서 보여주고, 희망사항에 '좋아요'를 표시하는 기능을 추가하겠습니다.
아래 댓글로 여러분의 생각이나 오류, 제안하는 바가 있다면 알려주세요. 이 튜토리얼의 소스코드는 깃허브에서 확인하실 수 있습니다.