스파르타 해적단 웹트랙Lv.2 2주차 개발일지 이다.
이번 주차에서는 삭제(Delete), 수정(Update)기능을 배우고 추가했다.
위 영상은 완성된 결과물이다.
수정, 추가, 삭제 기능이 정상적으로 동작하며, 체크하면 input박스안의 텍스트에 줄이 그어지는 기능도 수정 매커니즘을 이용해 구현해보았다.
CRUD란?
등록(Create)
조회(Read)
수정(Update)
삭제(Delete)
데이터를 관리할 때 필요한 4가지 요소이다.
이번 수업을 들으면서 많이 달라진 부분이 있었는데, 웹종 초보반에서는 CRUD중 등록(C)와 조회(R)만 다루었었다.
하지만 해적단 Lv.2 수업에서는 수정(U)과 삭제(D)를 다루어 보았다는 점이다.
기본적 기능이지만 내가 만들려니 많이 어려웠었다. 하지만 기본기능들이 만들어지니 좀 더 사용할 수 있고 제대로 된 웹사이트 같은 느낌이 들었다.
수정과 삭제를 하려면 일단 어떤 데이터를 수정,삭제를 할지 알아야 하기때문에 원래 _id값을 받아오지 않았는데, 이제 _id값을 받아와서 id값을 이용해 데이터를 수정,삭제했다.
list(db.todos.find({},{'_id':false}))
#↓↓↓↓↓↓↓↓
list(db.todos.find({}))
위와같이 _id데이터를 받지 않도록 false했었지만 이제 지우고 받아온다.
contents = list(db.todos.find({}))
for content in contents:
content['_id'] = str(content['_id'])
그리고 위 코드 처럼 _id데이터는 다시 반복문을 돌려주며 스트링 형으로 형변환 하여 데이터마다 넣어준다.
str(데이터) ← string형으로 형변환
그럼 MongoDB 데이터 베이스에도 데이터마다 MongoDB가 지정해준 무작위 id값이 같이 저장되는데, 이걸로 데이터들을 선택해서 기능을 수행한다.
HTML 코드를 뿌려줄 temp_html=``을 만들때 id값을 같이 넣어준다.
let temp_html = `<div id="${_id}" class="todo flex-row">
<input type="checkbox">
<span class="todo-content">${content}</span>
</div>`
위와같이 id값에 데이터 내의 id 값을 넣어준다.
이걸 append로 추가해주면 id 값이 설정된다.
수정
이제 기능중에 수정기능을 만들 때 영상에서 처럼 할일을 눌렀을 때 input내의 텍스트를 바꿀 수 있도록 만들어야 하는데, 눌렀을 때 화면에 표시해주는 input을 hide하고 다른 div를 새로 만들어 내부에 input과 button을 만들어 그 div를 활성화 해준다.
수정시도 시 함수 ↓↓↓
function showUpdateForm(_id) { #눌렀을 때
$(`#${_id} > span`).hide(); #id를 따라가 그 데이터의 span을 hide해준다.
$(`#${_id} > div`).css('display','flex'); #그 뒤 생성해둔 수정div의 display를 flex로 변경
}
수정취소 시 함수 ↓↓↓
function hideUpdateForm(_id) { #눌렀을 때
$(`#${_id} > span`).show(); #span을 다시 보여주고
$(`#${_id} > div`).css('display','none'); #display를 다시 none으로 변경
}
수정을 위해서는 update_one 명령어를 이용해야 하는데,
db.todos.update_one({'_id':id_receive},{'$set':{'content':content_receive}})
위 코드를 이용해서 바꿀 수 있는데 앞에서 id를 선언해서 그 id를 찾아 $set으로 컨텐츠를 바꿔주는데 id를 찾기 위해서는 찾는 id와 우리가 넣어준 id를 같은 형식으로 바꿔줘야하는데 우리가 넣은 id는 string형태이기 때문에 ObjectID형태로 변환 해줘야합니다.
그걸 위해 필요한게 from bson.objectid import ObjectId입니다. app.py 위쪽에 이 코드를 임포트 해주면
문자열 형변환 했을 때 처럼 ObjectID(id_receive)로 id값을 ObjectID형으로 변환 해줄 수 있다.
따라서 수정API를 만들 때 다음과 같이 먼저 id형을 변환해주고 올리는 모습을 볼 수 있다.
@app.route('/modify', methods=["POST"])
def modify_todo():
id_receive = request.form["id_give"]
id_receive = ObjectId(id_receive) #id를 형변환 해주는 모습
content_receive = request.form["content_give"]
db.todos.update_one({'_id':id_receive},{'$set':{'content':content_receive}})
return jsonify({"msg":"수정완료!"})
그 뒤 fetch코드에서 받아온 id내의 div의 input의 value를 가져와서 바꿔주는 모습을 볼 수 있다.
완료되면 수정이 완료 된다.
function modifyContent(_id){
let new_content = $(`#${_id}> div > input`).val(); #받아온id의 데이터를 찾아 value를 받아오는 모습
let formData = new FormData()
formData.append("id_give", _id)
formData.append("content_give", new_content)
fetch('/modify', {method : "POST", body: formData} ).then(res => res.json()).then(data => {
alert(data['msg'])
window.location.reload() #페이지 새로고침
})
}
삭제
삭제는 수정보다 간단했다.
버튼을 눌렀을 때 id만 체크해서 데이터를 지워주면 되기 때문에 content, 즉 데이터의 value자체를 받아올 필요가 없었다.
지울 땐 delete_one()을 이용했다.
@app.route('/delete', methods=["POST"])
def delete_todo():
id_receive = request.form["id_give"]
id_receive = ObjectId(id_receive)
db.todos.delete_one({'_id':id_receive}) #데이터베이스에서 지워주는 부분
return jsonify({"msg":"삭제완료!"})
그 뒤는 체크 박스를 체크 했을 때 박스체크를 확인 하는 boolean형을 데이터에 추가하고, 그 boolean의 값에 따라 글에 줄이 그어지고 체크박스가 체크된상태가 되도록 코드를 짰다.
if를 이용해 함수에서 db의 boolean데이터를 true인지 false인지 확인해주면 되는데, 여기서 오류가 발생했었다.
알고보니 데이터에 들어간 boolean데이터는 true, false 라고 되어 있지만, 문자열 형식으로 저장되어 있다는 것이었다.
그러니 if(is_bool == true)와 같이 체크하면 문자열이 들어가 모두 true가 되어버린다.
if(is_bool == 'true')와 같이 문자열으로 체크를 해줘야 한다... 몰라서 한참 찾다. 해설영상에서 알아냈다...
그외 체크박스를 체크 상태로 하고싶다면 html코드에서
<input checked type="checkbox">
위와같이 checked를 넣어주면 체크된 상태가 된다.
텍스트에 줄을 긋고 싶으면
style="text-decoration:line-through"
스타일을 위와같이 선언해주면 된다.
완성한 전체 코드도 올려두겠다.
2주차는 생각보다 새로 배운 내용이나 진도가 많이 나간것같아 힘들기도 했지만 뭔가 제대로 만들 수 있게 된것 같아서 재밌는 수업이었다.
app.py
app.py
from flask import Flask, render_template, request, jsonify
from bson.objectid import ObjectId
from pymongo import MongoClient
import certifi
ca = certifi.where()
client = MongoClient('mongodb+srv://sparta:test@cluster0.ehc0xum.mongodb.net/?retryWrites=true&w=majority', tlsCAFile=ca)
db = client.dbsparta
app = Flask(__name__)
@app.route('/')
def home():
return render_template("index.html")
@app.route("/list", methods=["GET"])
def list_todo():
contents = list(db.todos.find({}))
for content in contents:
content['_id'] = str(content['_id'])
return jsonify({'result':contents})
@app.route('/post', methods=["POST"])
def post_todo():
content_receive = request.form["content_give"]
doc ={
'content' : content_receive
}
db.todos.insert_one(doc)
return jsonify({"msg":"등록 완료!"})
@app.route('/modify', methods=["POST"])
def modify_todo():
id_receive = request.form["id_give"]
id_receive = ObjectId(id_receive)
content_receive = request.form["content_give"]
db.todos.update_one({'_id':id_receive},{'$set':{'content':content_receive}})
return jsonify({"msg":"수정완료!"})
@app.route('/delete', methods=["POST"])
def delete_todo():
id_receive = request.form["id_give"]
id_receive = ObjectId(id_receive)
db.todos.delete_one({'_id':id_receive})
return jsonify({"msg":"삭제완료!"})
@app.route('/check', methods=["POST"])
def check_todo():
id_receive = request.form["id_give"]
id_receive = ObjectId(id_receive)
check_receive = request.form["check_give"]
db.todos.update_one({'_id':id_receive},{'$set':{'check':check_receive}})
return jsonify({"msg":"체크!"})
if __name__ == "__main__":
app.run(debug=True, port=5001)
index.html
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>오늘의 할일</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.12.4.min.js"></script>
<style>
html, body, h1, h2, h3, div, span, a, button, input{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
background-color: rgb(235,235,235);
}
.wrap{
background-color: #f2f4f8;
width: 350px;
height: 700px;
margin: auto;
}
.flex-row{
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.flex-col{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.wrap-todo{
padding: 16px;
}
h1{
font-size: 24px;
margin: 16px;
}
.todo{
padding: 16px;
background-color: white;
border-radius: 16px;
width: 300px;
height: 50px;
margin-bottom: 8px;
}
.todo-content{
margin: auto;
}
.todo > input{
zoom: 1.5;
background-color: transparent;
}
.add-form{
padding: 16px;
background-color: #fff6f8;
border-radius: 16px;
width: 300px;
height: 50px;
border: 1px solid red;
display: none;
}
.add-form-btns{
margin-left: auto;
}
.add-form-btns > button{
margin: 0px 4px;
background-color: transparent;
border: none;
cursor: pointer;
font-size: 12px;
font-weight: 600;
}
.add-form > input{
background-color: transparent;
border: none;
font-size: 16px;
width: 200px;
}
.add-form > input:focus-visible{
outline: none;
}
.add-btn{
padding: 16px;
background-color: transparent;
border-radius: 8px;
width: 300px;
height: 50px;
border: 1px dashed gray;
cursor: pointer;
}
.add-btn > button{
margin-left: 8px;
margin-right: auto;
background-color: transparent;
border: none;
font-size: 12px;
font-weight: 600;
cursor: pointer;
}
.todo > button{
background-color: transparent;
border: none;
cursor: pointer;
font-weight: 600;
color: red;
font-size: 11px;
}
.todo-content-modify > input{
border: none;
margin-left: 8px;
margin-right: auto;
font-size: 16px;
color: gray;
width: 160px;
}
.todo-content-modify > button{
background-color: transparent;
border: none;
margin: 0px 8px;
font-weight: 600;
cursor: pointer;
}
.todo-content-modify > input:focus-visible
{
outline: none;
}
</style>
<script>
$(document).ready(() => {
listContents()
})
function showAddForm(){
$('#add-form').css('display','flex');
$('#add-btn').hide();
}
function hideAddForm(){
$('#add-form').hide();
$('#add-btn').css('display','flex');
}
function addContent(){
let content = $('#content').val()
let formData = new FormData()
formData.append("content_give", content)
fetch('/post', {method : "POST", body: formData} ).then(res => res.json()).then(data => {
alert(data['msg'])
window.location.reload()
})
}
function listContents(){
fetch("/list").then(res => res.json()).then(data => {
let rows = data['result']
rows.forEach((row) => {
let id = row['_id']
let content = row['content']
let check = row['check']
let temp_html = ``
if((check == 'false') || (check == null)){
temp_html = `<div id = '${id}' class="todo flex-row">
<input type="checkbox" onchange="checkContent('${id}')">
<span onclick="showUpdateForm('${id}')" class="todo-content">${content}</span>
<button onclick="deleteContent('${id}')">삭제</button>
<div style="display: none" class="todo-content-modify flex-row">
<input value=${content} type="text">
<button onclick="modifyContent('${id}')">수정</button>
<button onclick="hideUpdateForm('${id}')">취소</button>
</div>`
}
else{
temp_html = `<div id = '${id}' class="todo flex-row">
<input checked type="checkbox" onchange="checkContent('${id}')">
<span style="text-decoration:line-through" onclick="showUpdateForm('${id}')" class="todo-content">${content}</span>
<button onclick="deleteContent('${id}')">삭제</button>
<div style="display: none" class="todo-content-modify flex-row">
<input value=${content} type="text">
<button onclick="modifyContent('${id}')">수정</button>
<button onclick="hideUpdateForm('${id}')">취소</button>
</div>`
}
$('#todos').append(temp_html)
});
})
}
function AddConfirm(){
if(confirm("추가하시겠습니까?"))
{
alert('추가했습니다.')
addContent()
}else{
alert('취소했습니다.')
hideAddForm()
}
}
function showUpdateForm(_id){
$(`#${_id}> span`).hide()
$(`#${_id}> button`).hide()
$(`#${_id}> input`).hide()
$(`#${_id} > div`).css('display','flex')
}
function hideUpdateForm(_id){
$(`#${_id}> span`).show()
$(`#${_id}> button`).show()
$(`#${_id}> input`).show()
$(`#${_id} > div`).css('display','none')
}
function modifyContent(_id){
let new_content = $(`#${_id}> div > input`).val();
let formData = new FormData()
formData.append("id_give", _id)
formData.append("content_give", new_content)
fetch('/modify', {method : "POST", body: formData} ).then(res => res.json()).then(data => {
alert(data['msg'])
window.location.reload()
})
}
function deleteContent(_id){
let formData = new FormData()
formData.append("id_give", _id)
fetch('/delete', {method : "POST", body: formData} ).then(res => res.json()).then(data => {
alert(data['msg'])
window.location.reload()
})
}
function checkContent(_id){
let is_checked = $(`#${_id} > input`).is(':checked')
let formData = new FormData()
formData.append("id_give", _id)
formData.append("check_give", is_checked)
fetch('/check', {method : "POST", body: formData} ).then(res => res.json()).then(data => {
alert(data['msg'])
window.location.reload()
})
}
</script>
</head>
<body>
<div class="wrap">
<div class="wrap-todo flex-col">
<h1>오늘의 할 일</h1>
<div id="todos" class="todos flex-col">
</div>
<div id="add-form" class="add-form flex-row">
<input id="content" type="text">
<div class="add-form-btns flex-row">
<button onclick="AddConfirm()">추가</button>
<button onclick="hideAddForm()">취소</button>
</div>
</div>
<div id="add-btn" onclick="showAddForm()" class="add-btn flex-row">
<span>+</span>
<button>추가하기</button>
</div>
</div>
</div>
</body>
</html>