코딩 농장/웹 프로그래밍

[Django] 서버로 파일 업로드하는 API 만들기

GreenBNN 2024. 4. 12. 21:33

오늘 할 것

이전시간에 우리는 모달을 이용해 이미지를 업로드하는 html 윤곽만 잡았다.

이제 사진과 함게 글을 Django 서버에 올리고 그것을 띄어주는 것을 만들 것이다.

 

우리는 이미지, 글 ,사용자ID 등 데이터를 Django 서버에 올려야한다.

view.py 에서 데이터를 모아서 데이터베이스 Feed 테이블에 저장해야한다.


프론트에서 서버로 올리기 위해 Ajax(Asynchronous JavaScript and XML) 를 사용한다.

Ajax : 빠르게 동작하는 동적인 웹 페이지를 만들기 위한 개발기법, 웹 페이지 전체를 다시 로딩하지 않고 일부분만 갱신한다.

동기 : 요청을 하고 응답이 끝날 때 까지 기다리는 것

비동기 : 요청하고 응답이 안와도 그냥 할 것 함

 

우리는 이제까지 url 로 get 을 호출해서 html 을 띄어주었다.

이때 render(request, "...html", data)를 이용했는데 이건 화면 전체를 바꾸어 주는 기능이다.

하지만 우리는 전체 페이지를 바꿀 필요가 없기 때문에 Ajax 를 사용한다.


dragOver 이미지 드래그 앤 드롭 사용

<div class="img_upload_space" style="background-color: darkgray; width:50%; min-height:540px; margin: 10px">
            dd</div>
...
  <script>
    // 이미지 드래그 앤 드롭
    $('.img_upload_space')
      .on("dragover", dragOver)
      .on("dragleave", dragOver)
      .on("drop", uploadFiles);

    function dragOver(e) {
      e.stopPropagation();
      e.preventDefault();

      if (e.type == "dragover") {
        $(e.target).css({
          "background-color": "black",
          "outline-offset": "-20px"});
        {% comment %} alert('hello!') {% endcomment %}
      } else {
        $(e.target).css({
          "background-color": "gray",
          "outline-offset": "-10px"});
      }

    }
 </script>

이미지를 끌고 왔을 때 화면이 검게 변하도록 해주기

 

drop시 uploadFiles(e) 실행 + 화면에 띄어주기

<div class="img_upload_space" style="background-color: darkgray; width:50%; min-height:540px; margin: 10px">
            dd</div>
...
  <script>
   function uploadFiles(e) {
      e.stopPropagation();
      e.preventDefault();
      dragOver(e); //1

      e.dataTransfer = e.originalEvent.dataTransfer; //2
      files = e.target.files || e.dataTransfer.files;

      if (files.length > 1) {
        alert('하나만 올려라.');
        return;
      }

      if (files[0].type.match(/image.*/)) { // 이미지 업로드 됐을 때 띄우기
        $('#first_modal').css({
                                display: 'none'
                              });

        $('.img_upload_space').css({
                                      "background-image": "url(" + window
                                        .URL
                                        .createObjectURL(files[0]) + ")",
                                      "outline": "none",
                                      "background-size": "100%",
                                      "background-repeat": "no-repeat",
                                      "background-position": "center"
                                    });

        $('#second_modal').css({
                                display: 'flex'
                              });
      } else {
        alert('이미지가 아닙니다.');
        return;
      }

    }
 </script>

 

공유버튼을 눌렀을 때 이미지파일과 데이터를 서버로 보내주기

ajax 를 이용해 비동기로 데이터를 보내준다. 이후에 이걸 띄어주어야한다.

url : content/upload 로 들어가서 POST 로 서버로 데이터를 전송해준다.

<button id="feed_create_button" type="button" class="btn btn-primary">
   공유하기
</button>
...
  <script>
    //공유 버튼 눌렀을 때
    $('#feed_create_button').click(function () {
      alert('공유 누름!' + files[0]);

      let file = files[0];
      let image = files[0].name;
      let cotent = $('#input_feed_content').val();
      let user_id = 'emocode_admin';
      let profile_image = "{% static 'images/sogangcharacter.webp' %}";

      // json 데이터만 넘기냐
      // image 같은 데이터도 같이 넘기기 위해
      let fd = new FormData();

      fd.append('file', file);
      fd.append('image', image);
      fd.append('content', cotent);
      fd.append('user_id', user_id);
      fd.append('profile_image', profile_image);

      $.ajax({
        url: "content/upload",
        data: fd,
        method: "POST",
        processData: false,
        contentType: false,
        success: function (data) {
          console.log("성공");
        },
        error: function (request, status, error) {
          console.log('에러');
        },
        complete: function () {
          console.log('완료');
          location.replace("");
        }

      })
 </script>

이제 jinstagram/urls.py 에 url 추가해주고

urlpatterns = [
	...
    path('content/', include('content.urls')),
    ...
]

content/urls.py 에 content/upload/ 로 가면 업로드를 시행하는 것 같음?

urlpatterns = [
    path('upload', UploadFeed.as_view())
]

# 파일 나중에 조회하는 목적
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

content/views.py 에 두번째 모달에서 받은 이미지, 데이터를 서버에서 받아 저장해 Feed 로 보내는 코드

main.html 에서 Feed 정보를 토대로 게시물을 띄우기 때문에 생성한 게시물이 띄어진다. 

# 두번째 모달에서 받은 이미지, 데이터 받기
class UploadFeed(APIView):
    def post(self, request):

        # 파일 불러오기
        file = request.FILES['file']

        # 저장 준비
        uuid_name= uuid4().hex # 랜덤으로 고유한 값으로 저장, 한글 에러 날수도
        save_path = os.path.join(MEDIA_ROOT, uuid_name)

        with open(save_path, 'wb+') as destination:
            for chunk in file.chunks():
                destination.write(chunk)

        image =  uuid_name
        content =  request.data.get('content')
        user_id =  request.data.get('user_id')
        profile_image =  request.data.get('profile_image')

        Feed.objects.create(image=image, content=content, user_id=user_id, profile_image=profile_image, like_count=0)


        return Response(status=200)

여기서 우리는 파일의 대체 이미지 이름인 uuid_name 으로 저장을 했기 때문에

이걸 main.html 에서 띄어주려면 https://127.0.0.1:8000/media/uuid_name 으로 잘 불러와야 한다.

그 코드를 main.html 에 적용하면

 <!-- 피드 박스 -->
        {% for feed in feeds %}
          <div class="border feed_box">
            <!-- 프로필 -->
            <div style="
              display: flex;
              flex-direction: row;
              align-items: center;
              margin: 10px;
            ">
              <!-- 프로필사진 -->
              <div class="box" style="display:flex;">
                <img class="profile" src="{{feed.profile_image}}" style="background: grey"/>
              </div>
              <!-- 사용자명 -->
              <div style="margin-left: 10px">{{feed.user_id}}</div>
            </div>

            <!-- 이미지 -->
            <div>
              <img src="{% get_media_prefix %}{{feed.image}}" style="display:block;width: 100%;height:auto"/>
            </div>

            <!-- 아이콘들 -->
            <div style="
              display: flex;
              justify-content: space-between;
              margin: 10px 20px;
            ">
              <div>
                <span class="material-icons-outlined">
                  favorite_border
                </span>
                <span class="material-icons-outlined">
                  mode_comment
                </span>
                <span class="material-icons-outlined">
                  send
                </span>
              </div>
              <div>
                <span class="material-icons-outlined">
                  bookmark_border
                </span>
              </div>
            </div>

            <!-- 좋아요 -->
            <div style="text-align: left; margin: 0 20px">
              춘식이
              <b>외
                {{feed.like_count}}</b>이 좋아합니다
            </div>
            <!-- 캡션 -->

            <div style="text-align: left; margin: 0 20px">
              <b>{{feed.user_id}}</b>
              {{feed.content}}
            </div>

            <!-- 댓글 -->
            <div style="text-align: left; margin: 0 20px; font-size: 14px">
              <b>aaa</b>
              나도 놀러가고싶다
            </div>
            <div style="text-align: left; margin: 0 20px; font-size: 14px">
              <b>bbb</b>
              나도~
            </div>

            <!-- 댓글 입력창 -->
            <input type="text" class="form-control shadow-none" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="댓글 달기.." style="font-size: 14px"/>
          </div>
        {% endfor %}
        <!-- 여기까지 피드 박스 -->
      </div>

** <head> 부분에 아래 추가하기

<!-- connect static -->
    {% load static %}

 

 

여기까지 유투브 06:02:10 내용