유화제작 프로젝트
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
)
}
}
})
};