Advertisement
  1. Code
  2. Python

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

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 5
Creating a Web App From Scratch Using Python Flask and MySQL: Part 7

() 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&hellip; <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>&copy; Company 2015</p>
118
        </footer>
119
120
    </div>
121
</body>
122
123
</html>

변경 사항을 저장하고 서버를 다시 시작합니다. 로그인한 후 Add Wish 링크를 클릭하면 수정된 희망사항 추가 페이지를 볼 수 있을 것입니다.

Add Wish Page with Image UploadAdd Wish Page with Image UploadAdd Wish Page with Image Upload

업로드 기능 구현하기

여기서는 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>

파일 업로드 성공 콜백에서 #imgUploadsrc를 업로드된 이미지로 업데이트합니다.

1
$('#imgUpload').attr('src','static/Uploads/'+response.filename);

위의 변경 사항을 저장하고 서버를 다시 시작합니다. 애플리케이션에 로그인해서 새 이미지 파일을 업로드하면 업로드된 이미지를 볼 수 있을 것입니다.

Add Wish Page With UploadAdd Wish Page With UploadAdd Wish Page With Upload

이번에는 새로운 세 개의 필드를 포함하도록 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_addWishsp_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을 열고 titledescription 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&hellip; <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
}

변경 사항을 저장하고 서버를 다시 시작합니다. 유효한 로그인 정보로 로그인한 후 사용자 홈 페이지의 희망사항 목록에서 희망사항을 하나 수정합니다. 편집 팝업에 데이터가 채워질 것입니다.

Edit Pop Up With Additional FieldsEdit Pop Up With Additional FieldsEdit Pop Up With Additional Fields

이제 희망사항 추가 페이지에서 했던 것과 비슷하게 userHome.htmljQuery-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부에서는 사용자가 달성한 희망사항들을 애플리케이션 홈 페이지에서 보여주고, 희망사항에 '좋아요'를 표시하는 기능을 추가하겠습니다.

아래 댓글로 여러분의 생각이나 오류, 제안하는 바가 있다면 알려주세요. 이 튜토리얼의 소스코드는 깃허브에서 확인하실 수 있습니다.

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.