<DB에 카테고리 고정값 넣기>
- 우리가 만든 사이트는 공부(study)와 휴식(rest) 두 카테고리만 있다
- 두 카테고리는 다른 사람이 프로젝트를 열었을 때도 동일해야 한다
- JSON, CSV 데이터를 장고 DB에 넣는 법 - YouTube
- 위 동영상을 보고 구현할 수 있었다
- 공식문서: 모델에 초기 데이터를 제공하는 방법 | Django 문서 | Django (djangoproject.com)
- 나중에 프로젝트 여유가 있다면 초기 데이터를 생성해서 제출해도 좋겠다는 생각이 들었다
- category.json 파일 생성하기
- 우선 카테고리 리스트가 있는 파일을 생성해야 한다
- 파일명은 마음대로 정하면 된다
- 나는 카테고리가 2개뿐이라 뭔가 이게 꼭 필요한가 싶기는 하다 ㅋㅋ
- 파일은 manage.py 파일이 위치한 곳에 만들어준다
# category.json
[
"study",
"rest"
]
- category_loader.py
- 위 카테고리 목록을 장고 공식문서에 나온 그 형태로 바꿔주기 위한 파일이다
- 파일명은 마음대로 정하면 된다
# category_loader.py
import json
with open("category.json", "r") as f:
category_list = json.load(f)
new_list = []
for category in category_list:
new_data = {"model": "posts.category"}
new_data["fields"] = {}
new_data["fields"]["name"] = category
new_list.append(new_data)
with open("category_data.json", "w", encoding="UTF-8") as f:
json.dump(new_list, f, ensure_ascii=False, indent=2)
- 우선 위에서 만든 category.json 파일을 읽기모드로 열어서 category_list에 담는다
- 장고 공식문서를 보면 데이터들이 리스트 안에 딕셔너리 형태로 있다
- 그래서 new_list라는 빈 리스트를 만들고 위에서 가져온 category_list를 for문에 돌린다
- 난 카테고리가 2개뿐이라 for문이 2번만 돌아간다 ㅋㅋ
- 데이터는 딕셔너리 형태로 있는데 key값으로 model, pk, fields가 있다
- pk값은 자동으로 설정된다 따로 정하고 싶으면 정해줘도 된다
- 먼저 new_data에 key값으로 model, value값으로 app이름.모델명을 적어준다
- 그리고 fields라는 key의 value로 에 빈 딕셔너리를 넣고 name이라는 키를 만들어서 category를 담는다
- category에는 category_list에 있던 study와 rest가 하나씩 들어온다
- new_list에 new_data를 붙인다
- 그럼 study와 rest 데이터가 new_list에 들어간다
- 이제 category_data라는 파일을 쓰기 형식으로 연다(없으면 새로 생성된 후에 열린다)
- 마지막 줄은 정확히 뭔지는 모르지만 여튼 방금 만든 new_list를 category_data에 담는다
- category_data.json
- 이제 새로고침 하면 category_data.json가 새로 생겨서 그 안에 값이 담긴 것을 확인할 수 있다
# category_data.json
[
{
"model": "posts.category",
"fields": {
"name": "study"
}
},
{
"model": "posts.category",
"fields": {
"name": "rest"
}
}
]
- 장고 공식문서에 나온 형식으로 제대로 담겼다
- 이제 터미널 창에 아래 명령어를 입력한다
python mangae.py loaddata category_data
- 이제 데이터베이스를 확인하면 카테고리 테이블에 study와 rest 데이터가 생성된 것을 확인할 수 있다
<mySQL 설치 및 Django와 연동>
- 여기에서 도움을 받아 mySQL을 설치했다
- MySQL Workbench에 들어가서 데이터베이스를 생성했다
create database 데이터베이스명
- 위 명령어 입력 후 번개 모양이나 ctrl + enter 누르면 데이터베이스가 생성된다
- 데이터베이스가 잘 생성된 경우 맨 아래 빈 네모칸에 초록색 동그라미가 생긴다
- 이제 vscode와 MySQL을 연동해보자
- VSCode와 MySql 연동 (velog.io)
- 위 블로그를 참고해서 연동에 성공했다!
<프론트엔드 게시글 수정 시 원래 값을 수정창에 띄우기>
- 제목이랑 내용을 띄우는 건 쉬웠다
window.onload = async function loadUpdatePost() {
const urlParams = new URLSearchParams(window.location.search);
const postId = urlParams.get("post_id");
const exist_post = await getPost(postId);
// 원래 제목 띄우기
const updateTitle = document.getElementById("update-title")
updateTitle.value = exist_post.title
// 원래 내용 띄우기
const updateContent = document.getElementById("update-content")
updateContent.value = exist_post.content
...
}
- 수정창이 로드되었을 때 해당 함수가 실행된다
- 수정창 url에는 파라미터로 post_id가 담겨있다
- url에서 post_id를 추출해서 getPost 함수의 매개변수에 담아 게시글을 가져왔다
- html의 아이디가 update-title인 부분의 값에 원래 게시글의 제목을 담았다
- 내용도 똑같이 했다
- 이제 문제는 이미지와 별이다
- 이 상태에서 수정창을 켜면 별은 선택이 안 되어 있어서 이용자는 별점을 수정하고 싶지 않아도 수정해야 하는 상황에 처한다
- 그리고 이미지가 있는 게시글이었는데 이미지 선택을 안 하고 그냥 수정을 누르면 이미지가 사라진다
- 별 색칠하기
...
//원래 별 띄우기
for (let i = 1; i <= exist_post.star; i++) {
document.getElementById(`rating${i}`).checked = true;
}
....
- html에 별 태그 각각 id는 rating1, rating2 이런 식으로 되어 있다
- 우선 데이터베이스에 저장된 star값을 가져와서 for문을 돌린다
- 1부터 해당하는 star 값까지 1씩 증가하면서 for문이 도는데 그때마다 html에 rating(star값) 아이디를 가진 태그 부분에 checked를 true로 바꿔준다
- 게시글 상세창에서 수정 버튼을 눌렀을 때 별이 제대로 색칠되어 있는 것을 확인할 수 있다(제목, 내용도 제대로 들어온다)
- 별 다시 선택하지 않았을 때 Bad Request 뜨는 오류
- 별이 색칠되어 있지만 현재 코드는 별을 클릭해야 저장이 되는 방식으로 되어 있다
- 그래서 별을 선택하지 않으면 아무리 색칠이 되어 있어도 null 값이 뜬다
- 별을 고치지 않았을 때 null 값을 별이 색칠된 수만큼의 값을 넣어줘야 한다
- 그 말은 원래 데이터베이스에 있던 star 값을 그대로 유지하면 된다는 것이다
// api.js
// 게시글 수정
async function updatePosts(url) {
const urlParams = new URLSearchParams(url);
const postId = urlParams.get("post_id");
const exits_post = getPost(PostId);
...
// 별 클릭 안 했을 때 원래 star에 있던 데이터 다시 저장하는 코드
if (star == null) {
star = exist_post.star
}
...
}
- 그래서 이 코드를 추가했다
- 별을 다시 선택한 경우에는 게시글 작성과 마찬가지로 html의 value값을 가져와서 저장한다
- 이미지 새로 첨부하지 않았을 때 기존 이미지 그대로 두기
- 게시글 수정 기능을 구현하고 테스트를 하다가 원래 이미지가 있던 게시글인데 이미지를 선택하지 않았더니 이미지가 사라지는 오류를 발견했다
- 수정값을 받아서 백엔드로 데이터를 전송하는 부분에서 오류가 있는 것 같았다
// api.js
// 게시글 수정
async function updatePosts(url) {
...
const formdata = new FormData();
// 사진 새로 선택한 경우에만 formdata에 이미지 붙이기(안 하면 원래 데이터베이스에 이미지 그대로 있게 함)
if (img) {
formdata.append("image", img)
}
formdata.append("title", title)
formdata.append("content", content)
formdata.append("star", star)
...
}
- formdata에 제목, 내용, 별은 그대로 붙여지고 사진은 값이 새로 들어온 경우에만 붙이는 걸로 로직을 수정했다
- 별 수정한 것처럼 사진이 undefined인 경우에 원래 데이터베이스에 있는 값을 그대로 저장하려고 했는데 이미 저장된 이미지를 굳이 다시 꺼내서 저장할 필요가 없고 그렇게 되지도 않았다 ㅋㅋㅋ
- star 한대로 exist_post.image 했는데 files[0]와 같은 형식이 아니어서 bad request 에러가 뜬다
- 근데 이렇게 하고 보니까 star도 값을 새로 넣어서 formdata에 붙일 필요없이 star가 있는 경우에 붙이는 걸로 했어도 됐을 것 같다
- exist_post 빼고 이미지 한 것처럼 수정해야겠다~
// api.js
// 게시글 수정
async function updatePosts(url) {
const urlParams = new URLSearchParams(url);
const postId = urlParams.get("post_id");
const title = document.getElementById('update-title').value
const content = document.getElementById('update-content').value
let img = document.getElementById('update-image').files[0]
let star = document.getElementById('star').getAttribute('value')
const formdata = new FormData();
formdata.append("title", title)
formdata.append("content", content)
// 사진 새로 선택한 경우에만 formdata에 이미지 붙이기(안 하면 원래 데이터베이스에 이미지 그대로 있게 함)
if (img) {
formdata.append("image", img)
}
// 별을 새로 선택한 경우에만 formdata에 데이터 붙이기(클릭 안 한 경우 원래 데이터베이스에 star 값 그대로 있게 함)
if (star) {
formdata.append("star", star)
}
let token = localStorage.getItem("access")
const response = await fetch(`${backend_base_url}/posts/${postId}/`, {
headers: {
'Authorization': `Bearer ${token}`
},
method: 'PUT',
body: formdata
})
if (response.status == 200) {
alert("글 수정 완료")
window.location.replace(`${frontend_base_url}/posts/post_detail.html?post_id=${postId}`)
} else if (title == '' || content == '') {
alert("빈칸을 입력해 주세요.")
} else {
alert(response.statusText)
}
}
<메인페이지 구현하기>
- 원래 기획했던 대로 구현하지는 못 하고 그냥 메인페이지에 최근 글 10개가 뜨게끔 했다..
- Backend
# posts/views.py
...
class PostView(APIView):
def get(self, request):
"""메인 페이지"""
posts = Post.objects.all().order_by("-created_at")[:12]
serializer = PostListSerializer(posts, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
...
- 원래 기획했던 대로 카테고리별 인기 게시글 10개, 카테고리별 좋아요 상위 3개씩 구현하는 건 실패했다
- 다른 것도 구현 안 된 부분이 있어서 여기에 시간을 쏟을 수가 없어서 심플하게 카테고리 상관없이 최근 글 12개가 뜨도록 했다
- 12개인 이유는 현재 게시글 목록이 3개씩 1줄이기 때문에 맞추려고 12개를 했다
- Frontend
// index.js
// 게시글 리스트 보기
window.onload = async function loadPosts() {
posts = await getAllPosts()
const post_list = document.getElementById("post-list")
// 게시글 목록 UI
posts.forEach(post => {
const newCol = document.createElement("div");
newCol.setAttribute("class", "col")
newCol.setAttribute("onclick", `postDetail(${post.pk})`)
const newCard = document.createElement("div")
newCard.setAttribute("class", "card h-100")
newCard.setAttribute("id", post.pk)
newCol.appendChild(newCard)
const postImage = document.createElement("img")
postImage.setAttribute("class", "card-img-top")
if (post.image) {
postImage.setAttribute("src", `${backend_base_url}${post.image}`)
} else {
postImage.setAttribute("src", "https://cdn11.bigcommerce.com/s-1812kprzl2/images/stencil/original/products/426/5082/no-image__12882.1665668288.jpg?c=2")
}
newCard.appendChild(postImage)
const newCardBody = document.createElement("div")
newCardBody.setAttribute("class", "card-body")
newCard.appendChild(newCardBody)
const newCardTile = document.createElement("h5")
newCardTile.setAttribute("class", "card-title")
newCardTile.innerText = post.title
newCardBody.appendChild(newCardTile)
post_list.appendChild(newCol)
});
}
- 카테고리별 리스트 보기에서 따온 코드다
- 근데 겹치는 부분이 많아서 게시글 목록 UI 부분을 함수로 만들어서 함수 불러오는 걸로 수정했다