프로젝트 명 : 소개위드미
프로젝트 개요 : 팀원 소개 웹 페이지 제작
팀 명 : 코딩위드미
개발 일정 : 23.05.15 ~ 23.05.19 오전 11시
소개위드미 S.A.
https://gleaming-harp-2af.notion.site/S-A-5d4797ac156440289ccd9d25f8c3f169
와이어프레임
사용하는 기술
- 프론트엔드 : HTML
- 프론트엔드 : JavaScript
- 프론트엔드 : CSS
- 백엔드 : Python (flask)
- DB : MongoDB
API 명세
기능 | HTTP Method | URL | request | response |
팀 방명록 생성 | POST | /comments | 닉네임, 방명록 | {'msg': '방명록 저장 완료!'} |
팀 방명록 읽기 | GET | /comments | - | 닉네임, 댓글 |
선택된 방명록 삭제하기 | DELETE | /comments | 닉네임 | {'msg': '방명록 삭제 완료!'} |
선택된 방명록 수정하기 | PUT | /comments | 닉네임, 수정할 내용 | {'msg': '방명록 수정 완료!'} |
나의 역할 : 팀장
- 방명록 생성 app.py, 방명록 수정 app.py index.html
- 메인페이지 index.html 뼈대
- 주석
- S.A. 작성
- 와이어프레임 2차
우리 팀 규칙
- 13:00 ~ 14:00 점심 시간
- 18:00 ~ 19:00 저녁 시간
- TIL 링크 9시에 팀 방에 업로드
- 피드백을 요청하면 긍정/부정에 관계 없이 확실하게 답변을 준다. by. 다영 님
- 누구 한명이라도 모르는 것이 있으면 짚고 넘어간다. by. 상우 님
- 모르는 내용이 있다면 제일 먼저 검색해 본다. by. 승현 님
- 정보 공유를 잘한다. by. 성원 님
- 모르는게 있으면 물어보기 by. 혜민 님
S. A. 피드백
- 전반적으로 S.A 작성이 깔끔하게 되어있어서 좋습니다.
Good!
- 업무 분장의 경우에는 다영님, 혜민님도 코드적으로 기여를 할 수 있게 업무 분장을 다시 할 것을 부탁드립니다.
업무 재분배, 혜민 님은 참여가 어려우신 것으로 보여 참여 가능하실 때 추가 업무 드리기로 함.
- 와이어프레임만 봐서는 하나의 싱글페이지에서 전부 돌아가는 것인지 아니면 각자의 프로필 페이지가 따로 존재하는지 구별하기가 어렵습니다. 페이지가 따로 있다면 그것에 대해서 명확하게 구분해주시길 부탁드립니다.
변경 O : 다른 팀의 것을 참고하여 구조화 시킴
- API 명세의 경우에는 어떤 팀원에 대한 comments API인지가 추가적으로 기입이 되어야 합니다.
변경 X : 방명록은 팀 전체에 대한 방명록 1개를 운영합니다.
최종 발표 이후 피드백
디자인, 발표자료 깔끔
SNS보여준것 신선했음
포부나 소감을 공표한만큼 잊지 않기
버그 또는 에러의 해결
문제 : 삭제하기 버튼을 누르면 삭제가 바로 되는 기능을 만들고 싶었음
시도 : find를 이용하여 변수에 값을 담고 그 값이 삭제되게 하려고 함
해결 : find 하지 않고 변수에 바로 삭제할 닉네임 값을 담아서 pymongo 코드로 삭제함.
> 닉네임 값을 저장 폼처럼 입력 받으면 페이지 한 면이 입력 폼으로 가득 차게 됨
> 각 방명록에 버튼을 달고 입력 폼을 띄우는 모달을 적용함. 부트스트랩에서 모달 코드 가져옴.
> 원하는 삭제하기 탕! 은 아니었으나 삭제하기 탕!탕! 이 됨.
알게 된 점: key 가 있어야 데이터 조작 가능.
문제 : 수정하기 method 를 PUT 이 아닌 PULL로 적음. 그런데 작동함.
시도 : 튜터님과 chatGPT, Bard 에 질문
해결 : flask 에서 적절한 method가 없으면 GET으로 처리
https://github.com/pallets/flask/blob/main/src/flask/app.py 의 1024 line
알게 된 점: 적절한 methods를 입력하지 않으면 알아서 methods를 준다.
소스 코드
뼈대 :
스파르타피디아를 기반으로 부트스트랩에서 버튼 등을 가져와 붙임. 구현하고 싶은데 모르는 것들은 구글링 함.
방명록 생성 app.py :
1. nickname_give comment_give 를 html 에서 끌고 와서 nickname_receive comment_receive 에 저장한다.
2. doc 라는 딕셔너리에 담는다.
3. pymongo 코드 db.comments.insert_one(doc) 을 이용하여 key를 nickname 과 comment 로 갖는 데이터를 생성한다.
4. 방명로 저장 완료! 라는 msg를 리턴한다.
수정 app.py :
1. editnickname_give editcomment_give 를 html 에서 끌고 와서 editnickname_receive editcomment_receive 에 저장한다.
2. pymongo 코드 db.comments.update_one을 이용한다.
editnickname_receive 가 value 인 방명록의 내용을 editcomment_receive 로 바꿔 준다.
4. 방명로 수정 완료! 라는 msg를 리턴한다.
수정 index.html :
1. editnickname_give editcomment_give 에 모달에서 입력 받은 수정할 방명록의 닉네임과 수정하고 싶은 내용을 담는다.
2. fetch를 통해 보내준다.
3. 수정이 완료되면 alert를 통해 리턴 받은 msg를 띄운다.
4. window.location.reload를 통하여 새로고침한다.
app.py
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
from pymongo import MongoClient
import certifi
ca = certifi.where()
client = MongoClient('mongodb+srv://sparta:test@cluster0.0uiki8z.mongodb.net/?retryWrites=true&w=majority', tlsCAFile=ca)
db = client.dbsparta
# localhost:5000을 입력하면 index.html 이 나오게 해라
# @app.route('/') = localhost:5000
@app.route('/')
def home():
return render_template('index.html')
# POST 방식 : 받은 nickname_give comment_give 로 방명록 저장하기
@app.route('/comments', methods=["POST"])
def comments_post():
nickname_receive = request.form['nickname_give']
comment_receive = request.form['comment_give']
doc = {
'nickname':nickname_receive,
'comment':comment_receive
}
db.comments.insert_one(doc)
return jsonify({'msg': '방명록 저장 완료!'})
# jsonify : 사용자가 json data를 내보내도록 제공하는 flask의 함수.
# GET 방식 : 방명록 불러오기
@app.route('/comments', methods=['GET'])
def comments_get():
all_comments = list(db.comments.find({},{'_id':False}))
return jsonify({'result':all_comments})
# DELETE 방식 : 입력한 닉네임 값에 해당하는 방명록을 삭제합니다.
@app.route('/comments', methods=['DELETE'])
def comments_del():
delnickname_receive = request.form['delnickname_give']
db.comments.delete_one({'nickname': delnickname_receive})
return jsonify({'msg': '방명록 삭제 완료!'})
# PUT 방식 : 입력한 닉네임값에 해당하는 방명록의 내용을 editcomment_give 로 받아서 수정합니다.
@app.route('/comments', methods=['PUT'])
def comments_put():
editnickname_receive = request.form['editnickname_give']
editcomment_receive = request.form['editcomment_give']
db.comments.update_one({'nickname':editnickname_receive},{'$set':{'comment':editcomment_receive}})
return jsonify({'msg': '방명록 수정 완료!'})
if __name__ == '__main__':
app.run('0.0.0.0', port=5000, debug=True)
# debug=True 디버깅 모드 실행
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"></script>
<title>소개위드미</title>
<link href="https://fonts.googleapis.com/css2?family=Gowun+Dodum&display=swap" rel="stylesheet">
<!-- CSS -->
<style>
* {
font-family: 'Gowun Dodum', sans-serif;
}
.title {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 40px;
}
.title>h1 {
margin: 0px;
padding: 0px;
font-size: 80px;
font-weight: bold;
font-style: italic;
margin-bottom: 8px;
}
.subtitle {
color: gray
}
.title2>img {
border-radius: 5px;
width: 600px;
height: 400px;
margin: 20px 20px 20px 20px;
}
.title2 {
margin: 0px 20px 20px 20px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.desc {
display: flex;
align-items: center;
font-size: 21px;
margin-bottom: 60px;
}
.teamphoto {
border-radius: 20px;
}
.ptitle {
margin: 0px;
padding: 0px;
font-size: 30px;
color: #2aad3c;
font-weight: 400;
margin-bottom: 16px;
padding-bottom: 5px;
}
.ptitle2 {
margin: 0px;
padding: 0px;
font-size: 20px;
color: #2aad3c;
font-weight: 400;
margin-bottom: 16px;
padding-bottom: 5px;
}
.paragraph {
color: gray;
font-size: 17px;
}
.desc img {
width: 450px;
margin-right: 30px;
}
.mycards {
margin: 20px auto 0px auto;
width: 1000px;
max-width: 1500px;
}
.col {
padding: 0px 10px 0px 0px;
}
.comment-list {
margin: 20px auto 20px auto;
}
/* 아래로는 코멘트 박스 */
.comment-box {
width: 500px;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 20px auto 0px auto;
}
.comment-box>h2 {
text-align: center;
font-size: 25px;
font-weight: 300;
color: #222;
letter-spacing: 1px;
text-transform: uppercase;
margin-top: 60px;
display: grid;
grid-template-columns: 1fr max-content 1fr;
grid-template-rows: 27px 0;
grid-gap: 20px;
align-items: center;
}
.comment-box>h2:after,
.comment-box>h2:before {
content: " ";
display: block;
border-bottom: 1px solid #2aad3c;
border-top: 1px solid #2aad3c;
height: 5px;
background-color: #f8f8f8;
}
.reple {
border-radius: 10px;
}
</style>
<script>
// 페이지를 열면 show_comment() 함수가 실행됩니다. 방명록 리스트가 나옵니다.
$(document).ready(function () {
show_comment();
});
// POST 방식 : nickname_give, comment_give 에 방명록을 작성하기 위한 닉네임, 내용을 담아 보냅니다.
function save_comment() {
let nickname = $('#nickname').val()
let comment = $('#comment').val()
let formData = new FormData();
formData.append("nickname_give", nickname);
formData.append("comment_give", comment);
fetch('/comments', { method: "POST", body: formData }).then((res) => res.json()).then((data) => {
alert(data["msg"]);
window.location.reload()
});
}
// GET 방식 : DB에 저장되어 있는 정보들을 가져옵니다.
function show_comment() {
fetch('/comments').then((res) => res.json()).then((data) => {
let rows = data['result']
$('#comment-list').empty()
rows.forEach((a) => {
let nickname = a['nickname']
let comment = a['comment']
// temp_html 안에 모달을 넣어 수정/삭제 버튼을 누르면 입력 창이 뜨게 합니다.
let temp_html = `<div class="card">
<div class="card-body">
<blockquote class="blockquote mb-0" id="blockquote">
<p>${comment}</p>
<footer class="blockquote-footer">${nickname}</footer>
<button id="boxbutton" type="button" class="btn btn-dark" data-bs-toggle="modal" data-bs-target="#exampleModal" data-bs-whatever="@mdo">삭제하기</button>
<!--삭제하기 모달-->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">삭제하기</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-3">
<label for="recipient-name" class="col-form-label">닉네임: </label>
<input type="text" class="form-control" id="delnickname">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
<button onclick = "del_comment()" type="button" class="btn btn-primary" >삭제하기</button>
</div>
</div>
</div>
</div>
<button id="boxbutton" type="button" class="btn btn-dark" data-bs-toggle="modal" data-bs-target="#exampleModal2" data-bs-whatever="@mdo">수정하기</button>
<!--수정하기 모달-->
<div class="modal fade" id="exampleModal2" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">수정하기</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-3">
<label for="recipient-name" class="col-form-label">닉네임: </label>
<input type="text" class="form-control" id="editnickname">
<label for="recipient-name" class="col-form-label">방명록: </label>
<input type="text" class="form-control" id="editcomment">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
<button onclick = "edit_comment()" type="button" class="btn btn-primary">수정하기</button>
</div>
</div>
</div>
</div>
</blockquote>
</div>
</div>`
$('#comment-list').append(temp_html)
});
})
}
var exampleModal = document.getElementById('exampleModal')
exampleModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget
var recipient = button.getAttribute('data-bs-whatever')
var modalTitle = exampleModal.querySelector('.modal-title')
var modalBodyInput = exampleModal.querySelector('.modal-body input')
modalTitle.textContent = 'New message to ' + recipient
modalBodyInput.value = recipient
})
// DELETE 방식 : 입력한 삭제할 닉네임 값을 delnickname_give에 담아 보냅니다.
function del_comment() {
let delnickname = $('#delnickname').val()
let formData = new FormData();
formData.append("delnickname_give", delnickname);
fetch('/comments', { method: "DELETE", body: formData }).then((res) => res.json()).then((data) => {
alert(data["msg"]);
window.location.reload()
});
}
// PUT 방식 : 입력한 수정할 방명록의 닉네임값 editnickname_give 와 수정할 내용을 editcomment_give 에 담아 보냅니다.
function edit_comment() {
let editnickname = $('#editnickname').val()
let editcomment = $('#editcomment').val()
let formData = new FormData()
formData.append("editnickname_give", editnickname)
formData.append("editcomment_give", editcomment)
fetch("/comments", { method: "PUT", body: formData }).then(res => res.json()).then(data => {
alert(data["msg"])
window.location.reload()
})
}
</script>
</head>
<body>
<div class="title">
<h1>소개 with me</h1>
<p class="subtitle">Node.js A반 2조</p>
</div>
<div class="title2" id="title2">
<div class="desc" id="desc">
<img class="teamphoto" src="https://i.postimg.cc/PqKgCJbd/allteam.png" />
<div class="descp" id="descp">
<p class="ptitle">Team. 코딩위드미</p>
<p class="paragraph">
팀 소개 : 개발자가 되고 싶은 5명이 함께 협업을 배워나가요!<br>
팀 목표 : 프로젝트 완성! <br>
팀 약속 : 13:00 ~ 14:00 점심 시간 / 18:00 ~ 19:00 저녁 시간 <br>
</p>
<p class="ptitle2"> 팀 규칙 ! </p>
<p class="paragraph">
다영: 피드백을 요청하면 y/n에 관계없이 확실하게 답변을 준다. <br>
상우: 누구 한명이라도 모르는 것이 있으면 짚고 넘어간다. <br>
승현: 모르는 내용이 있다면 제일 먼저 검색해 본다. <br>
성원: 정보 공유를 잘한다. <br>
혜민: 모르는 게 있으면 물어보기.
</p>
</div>
</div>
</div>
<!-- 멤버 카드 -->
<div class="mycards">
<div class="row row-cols-1 row-cols-md-5 g-5" id="cards-box">
<div class="col">
<div class="card h-100">
<img src="https://i.postimg.cc/4x5ZLDB2/t043597jk8v-u055afalu9e-844e26a259ec-512-480.jpg"
class="card-img-top">
<div class="card-body">
<h5>이다영</h5>
<p>MBTI : INFJ</p>
<p>프로젝트를 완성하는 날, <br>트랙을 완주하는 날까지<br> 최선을 다하겠습니다!</p>
<button type="button" onclick="location.href='https://verdantjuly.github.io/withmedayoung/'"
class="btn btn-success">Introduce</button>
</div>
</div>
</div>
<div class="col">
<div class="card h-100">
<img src="https://i.postimg.cc/Pr1dZpZj/t043597jk8v-u053jslhs8k-ece0e600acf6-512-480.jpg"
class="card-img-top">
<div class="card-body">
<h5>이승현</h5>
<p>MBTI : ENTJ</p>
<p>열심히 캠프 참여해서 <br>어엿한 프로그램 <br>개발자로 거듭나겠습니다</p>
<button type="button" onclick="location.href='https://jamjaemm.github.io/project_beginning/'"
class="btn btn-success">Introduce</button>
</div>
</div>
</div>
<div class="col">
<div class="card h-100">
<img src="https://i.postimg.cc/1391qWsG/t043597jk8v-u051ukhdn83-cdd3648e9c6c-512-480.png"
class="card-img-top">
<div class="card-body">
<h5>우성원</h5>
<p>MBTI : ISFP</p>
<p>포기하지않고 꾸준히 <br> 해서 성공한 개발자가 <br>되겠습니다.</p>
<button type="button" onclick="location.href='https://sungwon93.github.io/mypage/'"
class="btn btn-success">Introduce</button>
</div>
</div>
</div>
<div class="col">
<div class="card h-100">
<img src="https://i.postimg.cc/KY8bd0WG/t043597jk8v-u055gm324au-7084f39e2aa9-512-480.jpg"
class="card-img-top">
<div class="card-body">
<h5>이상우</h5>
<p>MBTI : INFP</p>
<p>부트캠프를 수료하여 <br>멋진 개발자로 <br>거듭나겠습니다.</p>
<button type="button" onclick="location.href='https://sangwoorhie.github.io/sangwoo/'"
class="btn btn-success">Introduce</button>
</div>
</div>
</div>
<div class="col">
<div class="card h-100">
<img src="https://i.postimg.cc/3JY7pcfn/2023-05-15-22-55-19.png" class="card-img-top">
<div class="card-body">
<h5>서혜민</h5>
<p>MBTI : INFP</p>
<p>어엿한 개발자가 <br>될 수 있도록 <br>열심히 참여 하겠습니다.</p>
<button type="button" onclick="location.href='https://ex-1008.tistory.com/'"
class="btn btn-success">Introduce</button>
</div>
</div>
</div>
</div>
</div>
<!-- 방명록 -->
<div class="comment-box">
<h2 class="comment-title">Team.코딩위드미 응원하기</h2>
<div class="form-floating mb-3">
<input id="nickname" class="form-control" placeholder="닉네임">
<label for="floatingInput">닉네임</label>
</div>
<div class="form-floating">
<input id="comment" class="form-control" placeholder="응원 한 마디">
<label for="floatingPassword">응원 한 마디</label>
</div>
<br>
<button onclick="save_comment()" type="button" class="btn btn-outline-dark">저장</button>
<div id="comment-list" class="comment-list" id="comment-list">
<div id="commentcard" class="card">
<div class="card-body">
<blockquote class="blockquote mb-0">
<p>새로운 앨범 너무 멋져요!</p>
<footer class="blockquote-footer">호빵맨</footer>
<button onclick="change_comment()" type="button" class="btn btn-dark">수정하기</button>
<button onclick="delete_comment()" type="button" class="btn btn-dark">삭제하기</button>
</blockquote>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
완성 Git Hub
https://github.com/sangwoorhie/codingwithme
소감
누군가의 도움을 많이 받지 않고 같이 비전공자인 팀원들과 함께 페이지를 만들어 나가는 과정이었다. 진행하면서 어렵거나 힘든 부분도 있었고, 때로는 협업을 잘하지 못하는 것 같아 마음이 무거웠다. 하지만 모두가 같이 마음을 다하고 열심히 한 결과 회의도 합의점을 찾을 수 있었고 간단하지만 CRUD의 기초 구현도 해낼 수 있었다. CR만 목표로 했었는데 CRUD까지 할 수 있어 너무 감격스럽고 팀원들에게 감사하다. 서로 공유를 통해 배우는 점도 많았고, 구현에 실패한 부분들도 언젠가 이해하고 만들어낼 수 있는 개발자들이 되면 좋겠다. 내 개인적인 목표로는 협업을 더 잘해서 지체없이 최고의 효율을 자랑하는 팀 분위기를 만들어 보고 싶다. 이번에는 조장이었지만 다음에는 조장이 아니어도 그런 분위기를 만드는데 한 사람의 몫을 보태고 싶다. 마지막에는 코드를 완성했다는 것이 너무 즐거워서 콧노래를 자주 부르면서 작업하였다. 코드의 일부분은 자세히는 알지 못한다. 공부를 쉼없이 하여 알 수 있으면 좋겠다.
KPI : https://verdantjuly.tistory.com/84
'내일 배움 캠프 > 소개위드미' 카테고리의 다른 글
소개위드미 ver.5 (0) | 2023.05.21 |
---|---|
소개위드미 ver.4 (0) | 2023.05.21 |
소개위드미 ver.3 (0) | 2023.05.21 |
소개위드미 ver.2 (0) | 2023.05.21 |
6기 KPT 회고 (미니프로젝트) (0) | 2023.05.19 |