[0705] DRF - Front 이미지 전송

nikevapormax·2022년 7월 4일
0

TIL

목록 보기
64/116

유화제작 프로젝트

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_URLMEDIA_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}
          &nbsp &nbsp &nbsp &nbsp &nbsp
          ${time_before}&nbsp&nbsp<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
        )
      }
    }
  })
};
profile
https://github.com/nikevapormax

0개의 댓글