유화제작 프로젝트
back-end
미디어 파일을 프론트로 보내기 위한 url 세팅
- django에서 미디어 파일을 프론트로 서빙하기 위해서는 아래와 같이
media url 세팅을 해주어야 한다.
the_season/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('user.urls')),
path('article/', include('article.urls')),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
- 해당 세팅을 하기 전까지는 db에는 데이터가 잘 들어갔지만, live server를 사용해 띄운 프론트 쪽에서는 이미지가 깨져서 올라갔었다.
- 위의 세팅 말고도
settings.py에서 MEDIA_URL과 MEDIA_ROOT도 설정해주어야 한다.
- 여기서
output은 사진 파일을 저장하기 위한 폴더의 이름이다.
MEDIA_URL = "/output/"
MEDIA_ROOT = os.path.join(BASE_DIR, 'output')
style table 세팅
- 이번 프로젝트에서 style 테이블에는 기본적인 값이 세팅되어 있어야 했다.
- 물론 admin site에서 일일이 수작업을 할 수도 있지만, 다른 팀원들까지 해당 작업을 하게 되면 구성이 달라져 문제가 생길수도 있다고 생각했다. (물론 이번 프로젝트에서는 그정도까지의 문제는 생길 일이 없었지만)
- 따라서 아래의 명령어를 통해 데이터를 json 파일에 dump하고, db에 load할 수 있도록 했다.
$ python manage.py dumpdata article.style > style.json
$ python manage.py loaddata style.json
serializer 수정
- 프로젝트 제출 전날인만큼 팀원들끼리 작업한 branch의 모든 내용을 main branch로 모두 merge하였다.
- merge를 모두 진행하고 서버를 돌려 확인을 하는 도중
image가 저장은 되었지만 프론트 부분에 붙지 않는 것을 발견하였다.
- 이리저리 확인을 하고 나서 serializer에 문제가 있는 것을 발견하였고, 아래와 같이 수정하였다. 정말 간단한 문제점이지만, 간간히 실수하고 넘어가기 좋은 부분이라 생각해 리마인드를 하는 겸 올리게 되었다.
article/serializers.py
class ArticleSerializer(serializers.ModelSerializer):
comments = CommentSerializer(many=True, read_only=True, source="comment_set")
bookmarks = BookMarkSerializer(many=True, read_only=True, source="bookmark_set")
likes = LikeSerializer(many=True, read_only=True, source="like_set")
username = serializers.SerializerMethodField()
def get_username(self, obj):
return obj.user.fullname
class Meta:
model = ArticleModel
fields = ["id", "title", "content", "created_at", "modlfied_at",
"comments", "username", "user", "comments", "bookmarks", "likes", "image"]
- 프론트에 보내줘야 하는 값은 꼭
fields에 넣어줘야 한다!
사용자의 북마크 내용을 보여주는 serializer 세팅
- 해당 부분을 다 작성하고 난 뒤, 튜터님께 질문을 드리면서 아래와 다른 방식을 제안해주셨다.
- 아래와 같이 따로 테이블을 만드는 대신,
user 모델에 bookmark_user와 같은 필드를 하나 생성하는 것이었다.
- 위의 방식으로 진행하게 되면, 아래와 같은 데이터를 프론트로 서빙하는 과정이
ArticleSerializer 하나만 사용하면 되어 굉장히 편해진다고 한다.
- 프로젝트가 끝나는 대로 리팩토링을 진행해 볼 예정이다.
- 아래의 코드 중에서 그나마 생각해볼 만한 부분은
get_comments 함수이다.
- 처음에는 comment_list를 말그대로 리스트로만 프론트로 보내주었었다. 이 부분에 담겨있는 내용은 딱 댓글의 내용인
content 뿐이었다.
- 이러고 프론트로 데이터를 보냈는데, 댓글의 내용이 게시글의 본문에 나왔다.
- 그래서 데이터를 서빙하는 프론트 부분을 유심히 보다가 사용자의 아이디와 댓글을 언제 달았는지 알려주는 시간이 있다는 것을 알게 되었고, comment_list 라는 리스트 안에
딕셔너리로 프론트에서 필요한 데이터를 넣어서 보내주었다.
class BookMarkSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
title = serializers.SerializerMethodField()
content = serializers.SerializerMethodField()
comments = serializers.SerializerMethodField()
image = serializers.SerializerMethodField()
def get_username(self, obj):
return obj.user.username
def get_title(self, obj):
return obj.article.title
def get_content(self, obj):
return obj.article.content
def get_comments(self, obj):
comment_list = []
for comments in obj.article.comment_set.all():
username = comments.user.username
content = comments.content
time_post = comments.modlfied_at.replace(microsecond=0).isoformat()
comment_list.append({'username': username, 'content': content, 'modlfied_time': time_post})
return comment_list
def get_image(self, obj):
return obj.article.image
class Meta:
model = BookMarkModel
fields = ["user", "id", "username", "article", "title", "content", "comments", "image"]
front-end
NST에 사용할 스타일 선택 창 css
- 11가지의 스타일을 선택할 수 있기에 하나의 창에 모두 나열하지만, 가로 스크롤을 넣어 화면이 깨지거나 스타일 요소가 더럽게 나열되는 것을 방지하였다.

- 스타일을 선택하면 해당 스타일에 붉은 색의 박스가 쳐지도록 하여 사용자가 어떤 스타일을 선택하였는지 잘 파악할 수 있도록 하였다.
- 해당 부분에서 수정해야 할 부분은
체크박스를 없애고, 사진만 클릭해도 박스가 쳐지도록 하는 것이다.

.style-imgs {
overflow: scroll;
margin: auto;
padding: 30px 0px 30px 0px;
}
.input_image {
margin-left: 20px;
}
input[type="checkbox"] {
display: none;
}
input[type="checkbox"] {
display: inline-block;
vertical-align: middle;
}
input[type="checkbox"]:checked+img {
border-radius: 1px;
box-shadow: 0px 0px 0px 8px rgba(255, 0, 0, 0.7);
}
사진 클릭 시 style img의 alt 값 저장
- 원래 계획은 id의 값이나 value 값을 저장하는 것이었다. 그런데 img 태그를 사용하게 되면서, 추가적으로 작성해 코드가 더러워지는 것보다 내가 alt에 넣어 놓은 값을 가져다 쓰기로 했다.
addEventListner를 사용해 alt 값을 model에 저장해 주고, 이 값을 Session Storage에 저장하였다.백앤드로 넘기는 값인 style에 Session Storage에 저장되어 있던 model을 넣어주어 백앤드로 보내주었다.
- style img의 alt 값 뿐만 아니라 나는
진짜 사진도 input 값으로 백앤드로 보내주었어야 했다. 따라서 formData를 사용해 주었다.
- 사진과 나머지 텍스트값들까지 formData에 붙여 백앤드로 보내주었다.
- 주의해야 할 부분은
'Access-Control-Allow-Origin': '*'이다. 이전까지는 텍스트만 보냈어서 콘텐츠타입을 json에 맞춰 작성하였다면, 이번에는 사진을 같이 보내야 하므로 콘텐츠타입을 기입하지 않고 'Access-Control-Allow-Origin': '*'를 사용하였다.
var imgs = document.querySelectorAll("#style-imgs .imgs");
for (var i = 0; i < imgs.length; i++) {
imgs[i].addEventListener("click", click);
}
function click(e) {
sessionStorage.setItem('model', this.alt)
}
async function post_article() {
const title = document.getElementById("title").value
const content = document.getElementById("content").value
const style = sessionStorage.getItem('model')
const input = document.getElementById("fileUpload").files
console.log(title, content, style, input)
const formData = new FormData()
formData.append('input', input[0])
formData.append('title', title)
formData.append('content', content)
formData.append('style', style)
const response = await fetch(`${backend_base_url}article/`, {
headers: {
'Access-Control-Allow-Origin': '*',
'Authorization': 'Bearer ' + localStorage.getItem("access")
},
method: 'POST',
body: formData
}
)
if (response.status == 200) {
window.location.replace(`${frontend_base_url}`);
} else {
alert(response.status);
}
}
게시글 상세페이지 모달
- 사진을 다루기에 편하다고 생각해
background를 사용하였다. 백그라운드를 사진으로 채워주고 no-repeat center center/contain을 추가해주어, 백그라운드보다 사진이 작을 경우 생기는 반복을 제거해 주었고, 사진을 백그라운드의 중앙으로 위치시켜주어 이쁘게 사진을 적용할 수 있었다.
<!-- 게시글 상세페이지 모달창 바디 -->
<div class="popup-body">
<div class="popup-img" style="background: url(${backend_base_url}${image}) no-repeat center center/contain;">

북마크 탭
- 앞서 작성했던 북마크 탭의 백앤드 데이터를 받아 프론트 화면에 쏴주는 부분이다.
function bookmark_info(id, username, title, content, comments, image) {
temp_html = `
<li>
<div class="card" style="width: 18rem;" id="${id}">
<div class="card-img" style="background:
url(${back_base_url}${image}) no-repeat center center/contain;">
</div>
<div class="card-body">
<h5 class="card-title">${title}</h5>
<hr>
<p class="card-text">
${content}
</p>
</div>
</div>
<div class="icons">
<i class="far fa-heart heart${id}" style="font-size:24px"token interpolation">${id})"><span></span></i>
<span></span>
<i class="fa fa-bookmark-o bookmark${id}" style="font-size:24px"token interpolation">${id})"></i>
</div>
<!-- 게시글 상세페이지 모달 -->
<div class="popup-wrap" id="popup${id}">
<div class="popup">
<!-- 게시글 상세페이지 모달창 헤더 -->
<div class="popup-header">
<span></span>
<h2>${username} 님의 게시물</h2>
<span></span>
<span id="1${id}" class="popup-close"> X </span>
</div>
<!-- 게시글 상세페이지 모달창 바디 -->
<div class="popup-body">
<div class="popup-img" style="background: url(${back_base_url}${image}) no-repeat center center/contain;">
</div>
<h2 class="popup-title">
${title}
</h2>
<hr>
<h5 class="popup-content">
${content}
</h5>
<hr>
</div>
<!-- 게시글 상세페이지 모달창 댓글 output -->
<div class="popup-comment" id="comment${id}">
<h1>댓글 창</h1>
<hr>
</div>
<!-- 게시글 상세페이지 모달창 댓글 input -->
<div class="popup-post-comment">
<input class="popup-post-input" id="comment_input${id}" type="text" placeholder="댓글을 입력 해주세요..." />
<button class="popup-post-input-btn"token interpolation">${id})">
저장
</button>
</div>
</div>
</div>
</li>
`
$('#mypage_card').append(temp_html)
for (let j = 0; j < comments.length; j++) {
let time_post = new Date(comments[j].modlfied_time)
let time_before = time2str(time_post)
console.log("댓글 time post :", time_post)
$(`#comment${id}`).append(`<p>${comments[j].username} : ${comments[j].content}
         
${time_before}  <itoken interpolation">${comments[j].id})" class="fa-regular fa-trash-can"></i></p>
<hr>`)
}
}
function bookmark() {
var token = localStorage.getItem("access")
$.ajax({
type: 'GET',
url: `${back_base_url}article/mybookmark/`,
beforeSend: function (xhr) {
xhr.setRequestHeader("Content-type", "application/json");
xhr.setRequestHeader("Authorization", "Bearer " + token);
},
success: function (response) {
console.log(response)
for (let i = 0; i < response.length; i++) {
bookmark_info(
response[i].id,
response[i].username,
response[i].title,
response[i].content,
response[i].comments,
response[i].image,
response[i].modlfied_time
)
}
}
})
};