[Localhost] 나의 버킷리스트!
응? 이건.. 모지? ㅎㅎㅎㅎ
오?
오...
데샷?

마지막으로 장난감 하나를 더 들고 왔다.
목적은 역시나 GET / POST API 프로세스에 익숙해지기 위함이다.
문제는.. HTML 코드만 다를 뿐이지 사실상 똑같은 장난감을 벌써 네 번씩이나 우려먹었다는 것이다.
...
하지만!
이번에는 살짝 느낌이 다를 것이다.
기본적인 틀은 대동소이 하지만 기출문제를 약간 변형 시킨 느낌의 장난감이기 때문이다.
부담 갖지 않고 필자를 차근차근 따라온다면 꽤 재밌는 내용이니까 이번 시간에도 힘내주기 바란다.
그럼... 시~~~~작!
0. Project Setting & Procets
언제나처럼 조감도를 먼저 살펴봐야 하지 않겠는가?
지겹겠지만 비싼 돈 주고 사서 쓰는 편리한 IDE인 만큼 패키지를 빼먹기 쉽다.
이번에도 여러분 눈 아프게 찾지 말라고 따로 정리해주겠다.
1. Flask
2. pymongo
3. dnsPython
앞으로 어느 프로젝트를 진행하던지 간에..
저 세 놈은 모조건적으로 임포트 하고 시작할 것이다.
간만에 맥도날드를 또 예로 들어볼까?
만약 패키지까지 빠지지 않고 임포트 했다면..
여러분은 지금 모든 제반사항이 완벽하게 구비된 주방을 하나 뚝딱! 하고 만든 것이다.
필요한 재료는 냉동고에서 꺼내다가 쓰면 될 것이다.
그리고 우리는 이러한 방식으로 접근하는 개념을 'Framework' 라고 했다.
1. POST (CREATE)
요 기능을 구현해보겠다는 것이다.
아직까지는 간단하지?
반복해서 다룬 내용이다.
이제 와서 이해가 안된다고 하면 심히 곤란하다.. ㅡㅡ
질질 끌어서 무엇 하겠는가?
바로 설계도면부터 파고 들어가보자.
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
from pymongo import MongoClient
client = MongoClient('mongodb+srv://admin:<password>cluster0.n5ejj.mongodb.net/?retryWrites=true&w=majority')
db = client.dbsparta
# Applicable Variable
# 1. 버킷 리스트: bucket
# 1-1) 리스트 넘버: num
# 1-2) 리스트 갱신: done
이전에 임포트 해준대로 flak와 pymongo 패키지를 모두 써먹고 있지?
패키지 임포트는 순식간에 모든 것이 구비된 주방 하나를 만들어낸 것으로 비유할 수 있고..
우리는 방금 필요한 재료를 냉동실에서 꺼내온 것이다.
뭐 더 설명이 필요한가?
@app.route("/bucket", methods=["POST"])
def bucket_post():
# CREATE TABLE
bucket_receive = request.form['bucket_give']
(생략..)
return jsonify({'msg': '버킷리스트가 등록되었습니다. 화이팅!'})
이제 재료를 손질할 차례이다.
# CREATE TABLE은 이제 눈에 익을 정도로 익숙해질 때가 됐다.
클라이언트 단의 ajax 콜에 응답하기 위해 만들어놓은 놈.
receive / give는
response / request로 보아도 무방하다.
마찬가지로..
Server / Client로 역할을 나눠서 볼 수도 있고 말이다.
function save_bucket() {
let bucket = $('#bucket').val()
$.ajax({
type: "POST",
url: "/bucket",
data: {bucket_give: bucket},
success: function (response) {
alert(response["msg"])
window.location.reload()
}
});
}
내친 김에 프론트 단도 같이 비교 해가면서 봐라.
여기서는 data 타입 정도만 눈 여겨서 보면 되겠네.
# num++
bucket_list = list(db.buckets.find({}, {'_id': False}))
count = len(bucket_list) + 1
# INSERT INTO BUCKETS ('','','') VALUES (?,?,?)
doc = {
'bucket': bucket_receive,
'num': count,
'done': 0
}
db.buckets.insert_one(doc)
두 스코프를 한꺼번에 다뤄야 좀 이해가 용이할 것이다.
테이블에 데이터를 추가해야 하는 건 알겠는데..
갑자기 왠 카운트??
서두르지 말고 한 줄씩 차그근 살펴보자..
bucket_list = list(db.buckets.find({}, {'_id': False}))
DB 테이블을 한꺼번에 조회할 때 쓰는 SELECT문 같은 녀석이다.
조회한 모든 데이터를 배열 형식으로 bucket_list 변수에 저장하고 있네?
그래서 그게 뭐?
count = len(bucket_list) + 1
len()은 파이썬에서 +1씩 추가하고자 할 때 쓰는 함수타입이다.
자바에서 '++' 와 같은 식으로 표현하던 것과 똑같은 기능을 수행한다.
사용자가 버킷리스트를 하나씩 추가할 때마다 갯수를 하나씩 늘려주겠다는 것인데..
'num': count
바로 아래에 대응하고 있는 녀석이 'num'이라고 한다.
이런 식으로 컬럼 별로 Id, 내지 번호를 지정해주고 있는 것이다.
데이터를 수정하거나 삭제하는 등의 관리를 용이하게 해주기 위해서이다.
어짜피 MongoDB도 RDBMS의 일환이지 않은가?
'done': 0
이 놈은 뭔데 근본도 없이 0부터 박고 시작함?
하.. 기열..
단순히 이 기능을 구현하고자 했을 뿐이다.
그 위에 아직 완료하지 않은 목표인 '티바트 탐방'은 done 값이 0으로 지정되어 있는데
조금전에 쓰다듬은 '페이몬 배' 는 done 값이 1로 처리되어 있지?
이진법에서 0은 거짓, 1인 참을 표현하는 것에서 착안한 것이다.
db.buckets.insert_one(doc)
이 놈은 이제 익숙할 것이다.
buckets 테이블에 데이터를 하나씩 밀어넣어 주겠다는 뜻이다.
INSERT INTO BUCKETS ('BUCKET', 'NUM', 'DONE') VALUES (?, ?, ?)
요 SQL 문과 동일한 기능을 수행하는 pymongo 만의 문법인 셈이다.
내용이 다소 헷갈릴 수 있기 때문에 최대한 상세하게 리뷰하기 위해 노력했다.
필자로서는 최선을 다했으니 그래도 이해가 안된다면 유감이다..
2. GET (READ)
이렇게 버킷리스트를 한꺼번에 모아서 보기 위한 기능을 구현하려는 것이다.
데이터를 CREATE / UPDATE 하는 것 없이 단순 조회만 하려는 것이다.
어려울 것 없으니 집중해서 보도록 하자.
@app.route("/bucket", methods=["GET"])
def bucket_get():
# SELECT * FROM BUCKETS
buckets_list = list(db.buckets.find({}, {'_id': False}))
return jsonify({'buckets': buckets_list})
사실 좀 전에 'num++' 기능을 구현하면서 한 번 다룬 적이 있는 내용이다.
# SELECT * FROM BUCKETS
buckets_list = list(db.buckets.find({}, {'_id': False}))
buckets 테이블에 등록된 모든 데이터를 조회하겠다고 했지?
그걸 조회해서 bucket_list 라는 변수에 저장할 것이고..
return jsonify({'buckets': buckets_list})
DB 서버에서 플라스크로 응답하면서 넘어온 데이터들은 이상하게 다시 문자열로 변환되어 있다.
클라이언트의 ajax call에 응답하기 위해서는 부득이하게 JSON 형식으로 변환하는 과정이 수반되어야 한다.
function show_bucket() {
$.ajax({
type: "GET",
url: "/bucket",
data: {},
success: function (response) {
let rows = response['buckets']
for (let i = 0; i < rows.length; i++) {
let bucket = rows[i]['bucket']
let num = rows[i]['num']
let done = rows[i]['done']
let temp_html = ``
if (done == 0) {
temp_html = `<li>
<h2>✅ ${bucket}</h2>
<button onclick="done_bucket(${num})" type="button" class="btn btn-outline-primary">완료!</button>
</li>`
} else {
temp_html = `<li>
<h2 class="done">✅ ${bucket}</h2>
</li>`
}
$('#bucket-list').append(temp_html)
}
}
});
}
이제 DB 서버에서부터 플라스크 내부 (Internal) 서버로 응답된 데이터들을
다시 클라이언트로 되돌려 줄 차례이다.
아니, 엄밀히 말하자면 클라이언트의 초기 요청을 조금 뒤늦게 작성하려는 것이다.
애초에 API의 진행순서는 다음과 같이 진행되지 않는가?
- Client: Flask야! ☆☆한 데이터들을 등록 / 조회 하려고 하는데 괜찮겠니?
- Flask: ☆☆한 데이터들 말이지? MongoDB 서버님! ☆☆한 데이터들 좀 주시겠어요?
- MongoDB: 예아! 안될 거 머있농? ☆☆한 데이터들 지금 바로 갑니다!
- Flask: 감사합니다! ㅎㅎㅎ Client야! 요청했던 ☆☆한 데이터들 여깄어!
- Client: 응, 고마워! 이제 고객놈들에게 보여주면 되겠당.. ㅎㅎㅎ
중간에 뭔가 이상한 게 껴 있기는 한데.. API는 진짜 저런 순서로 프로세스가 진행된다.
다시 말해 클라이언트에 작성한 ajax call은 1번 목록과 같은 기능을 수행하는 녀석인 셈이다.
사실 처음 API를 다룰 때 가장 먼저 이 비유를 들었으면 좋았겠지만..
어쨌든 전체적인 프로세스를 머리속에 박아 넣었으니 이제 for문을 분석 해보자.
success: function (response) {
let rows = response['buckets']
for (let i = 0; i < rows.length; i++) {
let bucket = rows[i]['bucket']
let num = rows[i]['num']
let done = rows[i]['done']
let temp_html = ``
if (done == 0) {
temp_html = `<li>
<h2>✅ ${bucket}</h2>
<button onclick="done_bucket(${num})" type="button" class="btn btn-outline-primary">완료!</button>
</li>`
} else {
temp_html = `<li>
<h2 class="done">✅ ${bucket}</h2>
</li>`
}
$('#bucket-list').append(temp_html)
}
}
첫 줄부터 머리속이 하얘지는 거 같은가?
당황하지 말아라
return jsonify({'buckets': buckets_list})
요 놈에 상응하는 코드일 뿐이다.
뒷단에서 buckets 테이블에 있는 모든 데이터들을 조회한 다음
bucket_list 변수에 배열 형태로 저장하라고 했지?
그놈을 다시 buckets 안에 집어 쳐넣어서 JSON으로 되돌려 준 것을..
let rows = response['buckets']
AJAX Call에서 별도의 변수를 지정해서 고대로 선언해 준 것 뿐이다.
배열 형태이건, 일반 문자열이건 변수로 한 번 감싸줘야 for문을 돌릴 것이 아닌가?
for (let i = 0; i < rows.length; i++) {
let bucket = rows[i]['bucket']
let num = rows[i]['num']
let done = rows[i]['done']
(생략)
}
i 번째에 해당하는 값들을 각각 뽑아내서 변수에 저장해주고 있다.
let temp_html = ``
if (done == 0) {
temp_html = `<li>
<h2>✅ ${bucket}</h2>
<button onclick="done_bucket(${num})" type="button" class="btn btn-outline-primary">완료!</button>
</li>`
} else {
temp_html = `<li>
<h2 class="done">✅ ${bucket}</h2>
</li>`
}
$('#bucket-list').append(temp_html)
for문에서 뽑아낸 값들을 실제로 HTML 화면에 적용시켜 줘야겠지?
필요한 값들을 jQuery로 선언하여 내장객체처럼 가져다가 쓰고 있다.
여기서 끝나면 기출변형 문제가 아니겠지?
소원 성취를 했다면 더 이상 버킷리스트에 남아 있을 필요가 없을 것이다.
그 기능을 다음 페이지에서 함께 구현 해보자.
3. POST (UPDATE)
마지막으로 요딴 기능을 하나 추가했기 때문에 기출변형인 것이다.
필자는 오늘 자기 전에 게임에 접속할 것이기 때문에 소원 성취를 한 것으로 치고
그것을 리스트에서 삭제해준 것이다.
물론 실제 DB 서버에서 데이터가 사라진 것은 아니다.
그냥 클라이언트 화면에서 삭제된 것 처럼 보이게 값을 새로 추가해줬을 뿐이다.
그래도 의도는 확실하지 않은가?
이번 시간을 마지막으로 API 프로세스는 마무리 짓도록 하자.
@app.route("/bucket/done", methods=["POST"])
def bucket_done():
# UPDATE TABLE
num_receive = request.form['num_give']
db.buckets.update_one({'num': int(num_receive)}, {'$set': {'done': 1}})
# int(num_receive): Type Conversion
# ex) Integer.ParseInt(); in Java
return jsonify({'msg': '목표를 달성하셨네요! 축하드려요~'})
바로 소스코드로 들어가도록 하자.
이 녀석도 POST 요청을 처리하고 있다.
다만 테이블을 생성하고, 컬럼을 삽입하는 이전 구조와는 조금 차이가 있다.
요 녀석은 테이블을, 그 중에서도 특정 컬럼을 업데이트 해주기 위한 코드이다.
우선은 클라이언트에서 ajax로 num을 요청하면 서버에서 그 값을 되돌려줘야 할 것이다.
일차적으로는 요청을 수렴해서 DB 서버에 다시 전달을 하는 것이 플라스크 서버의 역활이므로 receive로 구분해줬다.
오히려 그게 더 헷갈리면 독자들은 더 직관적인 단어로 변수명을 설정해줘도 상관 없다.
그 다음은 DB서버와 직접적으로 통신을 해야겠지?
그래야지 클라이언트의 요청에 응답할 것 아닌가?
특정한 값, 그 중에서도 테이블 Id에 해당하는 num만 수정해주기로 했다.
이런 식으로 말이다.
아직 소원 성취가 되지 않았다면 done 값은 0일 것이고..
소원이 성취 되었다면 done 값이 1로 업데이트 되면서, 화면단에는 취소선이 그어질 것이다.
num_receive를 굳이 int()로 감싸준 것은 형 변환 (Type Conversion)을 해준 것이다.
num은 정수 타입의 데이터여야만 하는데 일차적으로 넘어오는 값은 String이기 때문이다.
자바 같으면 Integer.ParseInt()로 처리해줘야 하는 부분인데 확실히 파이썬이 편하긴 하다..
function done_bucket(num) {
$.ajax({
type: "POST",
url: "/bucket/done",
data: {num_give: num},
success: function (response) {
alert(response["msg"])
window.location.reload()
}
});
}
클라이언트는 이 정도로 수정해줄 수 있겠지
num을 POST (UPDATE)하기 위해 플라스크 서버로 요청을 보냈고..
요청이 성공적으로 이루어졌다면 새로고침과 함께 메시지가 출력될 것이다.
목표를 달성했으니 축하하댄다..
이제 진짜, 진짜로 API 프로세스는 여기서 마무리 지을 것이다.
어짜피 이후로도 끊임없이 반복되는 내용이므로 개념을 이해하는 것보다
손이 더 먼저 익숙해질 것이다.
단내가 날 정도로 익숙해 진다면 머리에서는 까먹어도 손 끝이 그것을 기억하고 있다니까?
무튼.. 로컬호스트에서의 연습은 이정도로 되었다.
이제 여지껏 쪼물딱거린 예제들을 웹 상에 배포하고, 자랑질도 해봐야지?
스프링 공부할 때 처럼 말이다.
5주차 개발일지 시간에 뵙도록 하겠다.