[day-39] DRF 이해 및 기본 세팅

Joohyung Park·2024년 3월 1일
0

[모두연] 오름캠프

목록 보기
73/95

기존의 방식(모놀리식)

지금까지 모놀리식으로 Django를 배워보았다. 이러한 모놀리식은 템플릿 문법을 사용해 사용자에게 html, css, js코드를 주는 방법인데 API 명세서가 따로 필요 없다는 특징이 있다. 렌더링은 서버가 다 해주기 때문이다.

  • 장점 : 규모가 크지 않은 서비스의 경우 빠른 개발 가능, 소규모 팀인 경우 선택하기 좋다. 혼자하시기에도 좋다.
  • 단점 : 규모가 커질 경우 BE, FE에 역할이 애매하다.

앞으로의 방식(마이크로식)

이는 Django 서버와 프론트엔드 서버를 별도로 운영하는 방식이다.

  • 장점 : 백엔드 개발자와 프론트엔드 개발자는 API 명세서로 원활한 소통이 가능하다.
  • 단점 : 소규모 프로젝트에서는 의사소통 비용이 올라간다. 예를 들어 API 명세서 만들시간이면 서비스 만든다는 말이 있다.

기본 세팅

  • 폴더, 사용할 앱 생성
mkdir mysite
cd mysite
python -m venv venv
.\venv\Scripts\activate
pip install django
pip install pillow
pip freeze > requirements.txt -> 가상환경 설명서
django-admin startproject tutorialdjango .
python manage.py migrate
python manage.py startapp blog
  • settings.py 수정

    • ALLOWED_HOSTS
    • INSTALLED_APPS
    • TEMPLATES
    • LANGUAGE_CODE, LANGUAGE_CODE
    • STATIC_URL, STATICFILES_DIRS
    • MEDIA_URL, MEDIA_ROOT
  • static, media, templates 폴더 생성

  • 모델 정의

  • DB 반영

  • admin 페이지 설정(모델과 연결)

  • 관리자 계정 생성

  • 프로젝트의 url 설정

DRF(Django Rest Framework)

설치

pip install djangorestframework
# CORS(Cross-Origin Resource Sharing) 구현을 도와줌. 이를 통해 다른 도메인에서 온 요청에 대한 처리를 쉽게 할 수 있음
pip install django-cors-headers
pip freeze > requirements.txt

DRF 관련 앱 선언(settings.py)

  • 설치된 앱 선언
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    # django 라이브러리 앱 추가
    "rest_framework",
    "corsheaders",
    "blog",
]
  • 앱의 요청을 처리하는 데 사용되는 미들웨어 목록 정의
MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",  # 최상단 추가
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

각 미들웨어는 순서대로 적용되며, 순서에 따라 요청과 응답이 변경될 수 있다. 따라서, CORS는 최상단에 위치하며 보안, 인증 미들웨어는 상위에 위치시키는게 일반적이다.

  • 미들웨어 설정 선언
# 모든 도메인에서 오는 요청을 허용하도록 Django에 지시
CORS_ORIGIN_ALLOW_ALL = True
# 브라우저가 요청과 함께 쿠키 등의 인증 정보를 서버로 전송할 수 있음
CORS_ALLOW_CREDENTIALS = True

blog앱 url 선언

from django.urls import path
from . import views

urlpatterns = [
    path("", views.blog_list, name="blog_list"),
]

blog앱 화면에 보여줄 내용 선언

# views.py

from django.shortcuts import render
from .models import Post
from django.http import JsonResponse

# 모든 블로그 게시물을 가져와 HTML 템플릿으로 렌더링하며
# 템플릿과 게시물 데이터를 함께 반환
# def blog_list(request):
#     posts = Post.objects.all()
#     return render(request, "blog/blog_list.html", {"posts": posts})


# 하드코딩된 JSON 응답 반환. 테스트 용도로 사용할 수 있으며
# 실제 데이터 대신 임시 데이터로 JSON 응답을 확인 가능하다
# def blog_list(request):
#     # posts = Post.objects.all()
#     # data = {"posts": list(posts.values())}
#     posts = [
#         {"title": "1", "content": "111"},
#         {"title": "2", "content": "222"},
#         {"title": "3", "content": "333"},
#     ]
#     # dictionary이외를 받을 경우, safe=False로 설정
#     return JsonResponse(posts, safe=False)


# def blog_list(request):
#     # 아래와 같은 형태를 허용하지 않습니다.
#     # 이유는 posts가 QuerySet이기 때문입니다. 단순 문자열로 변환할 수 없습니다.
#     # 방법1: 리스트로 만들어서 JsonResponse로 반환
#     # 방법2: posts를 순회해가면서 각각의 값을 딕셔너리로 만들어서 JsonResponse로 반환
#     posts = Post.objects.all()
#     data = {"posts": posts}
#     # dictionary이외를 받을 경우, safe=False로 설정
#     return JsonResponse(data)


def blog_list(request):
    # 방법2를 사용한 해결책
    l = []
    posts = Post.objects.all()

    for i in posts:
        l.append({"title": i.title, "content": i.content})
    data = {"posts": l}
    return JsonResponse(data, safe=False)

Django에서 JSON 응답을 다루는 다양한 방법은 여러가지가 존재한다. 가장 일반적인 방식은 JsonResponse를 이용하는 방식이다.

그렇지만 QuerySet 객체나 Django 모델 인스턴스와 같은 복잡한 데이터 타입은 직렬화가 어려운데, 이러한 경우에는 다음 두 가지 방식을 사용한다.

  • 직접 변환 방식: 각 객체를 순회하며 필요한 데이터를 딕셔너리나 리스트로 만들고, 이를 JsonResponse에 전달하는 방식

  • 직렬화 모듈 사용 방식: Django의 serializers 모듈을 사용하여 QuerySet을 직접 JSON으로 변환하는 방식

상황에 맞는 적합한 방식을 사용하면 되는데 직접 변환 방식은 각 객체의 특정 필드반 반환하고 싶을 때 유용하고, 직렬화 모듈 사용 방식은 모델의 모든 필드를 JSON으로 변환하고 싶을 때 유용하다.

DRF를 사용한 GET 요청 처리 예제

Django 서버와 완전히 다른 컴퓨터 또는 다른 서버 또는 다른 폴더에서 작동되며 이러한 방식을 마이크로식이라고 한다.

Django서버는 AWS lightsail, FE서버는 Netlify Vercel 등을 사용한다.

이번 실습에서는 다른 서버를 돌리기엔 무리가 있기에 FE라는 폴더를 새로 만들어 Go Live라는 vscode extension의 기능으로 그 폴더의 내용을 실행하였다.

  • FE 서버(로컬) FE > index.html
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
</head>
<body>
    <div id="data"></div>
    <script>
        // fetch로 http://127.0.0.1:8000/blog/ 데이터 가져와서 출력
        fetch('http://127.0.0.1:8000/blog/')
        .then(response => response.json())
        .then(data => {
            console.log(data);
            document.getElementById('data').innerHTML = data;
        });
    </script>
</body>
</html>
  • Django 서버(로컬) blog > views.py
from django.shortcuts import render
from .models import Post
from django.http import JsonResponse
# rest_framework 추가 후 추가된 코드
from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view
  • FBV 방식
@api_view(['GET']) # ['GET', 'POST']하면 둘 다 처리 가능
def blog_list(request):
    posts = [
        {'title':'1', 'content':'111'},
        {'title':'2', 'content':'222'},
        {'title':'3', 'content':'333'},
        {'title':'4', 'content':'444'},
    ]
    serializer = posts # 직렬화 하는 단계
    return Response(serializer) # Response로 반환 되었을 때 데이터를 읽을 수도 있고, POST를 보낼 수도 있습니다.
  • CBV 방식
# CBV 사용하는 방식
# class LicatView(APIView):
#     def get(self, request):
#         posts = [
#             {'title':'1', 'content':'111'},
#             {'title':'2', 'content':'222'},
#             {'title':'3', 'content':'333'},
#         ]
#         serializer = posts # 직렬화 하는 단계
#         return Response(serializer) # Response로 반환 되었을 때 데이터를 읽을 수도 있고, POST를 보낼 수도 있습니다.

# postlist = LicatView.as_view()

django서버의 object들을 잘 받아오고 있다.

blog > serializers.py 선언

DRF를 사용하여 Post 모델과 관련된 API를 구현하기 위한 파일을 선언하였다. Serializer는 직렬화와 역직렬화를 쉽게 구현하도록 도와준다.

from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'

PostSerializer는 Post 모델에 대한 Serializer로, Post 모델의 객체를 JSON 형식으로 직렬화하거나, JSON 데이터를 파싱하여 역직렬화하는 역할을 한다.

Meta 클래스에서는 직렬화 대상 모델과 필드를 지정하였다.

이러한 과정은 Post 모델과 관련된 API를 구현하기 위한 준비 단계라고 볼 수 있다.

그래서 직렬화/역직렬화가 뭔가요?

그림에서 볼 수 있듯이, DB 데이터를 JSON과 같은 형태로 변환하는 것을 직렬화라고 한다.
반대의 과정을 역직렬화라고 한다.

이는 Django의 경우이며, 다른 프레임워크에서는 데이터의 변환만 다룬다. 다른 프레임워크는 복잡한 구조나 객체를 문자열 또는 바이트스트림과 같은 전송이 쉬운 형태로 변환하는 과정을 직렬화라고 하며, 그 반대를 역직렬화라고 한다.

Django의 Serializer만 특이하게 ORM을 기반으로 DB와 상호작용 + 데이터 변환을 뜻한다.

  • 왜 직렬화/역직렬화를 하나요?
    • DRF에서는 데이터를 저장할 때 Django의 Model을 통해 저장하게 된다. Django의 모델은 ORM으로 처리할 수 있는 ”파이썬 객체”이며 클라이언트는 파이썬 객체를 해석할 수 없기 때문이다.
    • 따라서 JSON, XML과 같은 클라이언트 측에서 읽을 수 있는 문자열로 변환하여 보내주어야 한다!

serializer를 views.py에 적용

# blog > views.py

from django.shortcuts import render
from .models import Post
from django.http import JsonResponse
# rest_framework 추가 후 추가된 코드
from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view
from .serializers import PostSerializer


@api_view(['GET', 'POST'])
def blog_list(request):
    if request.method == 'GET':
        postlist = Post.objects.all()
        serializer = PostSerializer(postlist, many=True) # 다수의 Queryset을 넘길 때는 many=True
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Get 요청과 Post 요청을 처리하는 코드이다.

Get 요청이 들어오면 Post 객체의 모든 객체를 가져와 JSON 형식으로 직렬화 한다. 이 JSON 데이터를 반환하게 된다.
Post 요청이 들어오면 요청 데이터(request.data)를 파싱하고 is_valid() 메서드로 유효성을 검사한다. 유효하면 save() 메서드를 호출하여 Post 모델의 새 객체를 생성하고 직렬화하여 응답으로 반환한다.

만약, 유효하지 않은 데이터라면 400 에러를 반환한다. status의 값을 그냥 400이라고 해도 되는데 저런식으로 선언한 이유는 명확성 때문이다.

title이 4이고, content가 44인 JSON 형식의 데이터를 post형식으로 보내니 다음과 같은 응답이 돌아왔다.

id를 적어도 무시하는 모습이다. id는 알아서 붙여준다.

Thunder Client라는 extension인데 api 테스트하기 참 편하고 좋은 것 같다.

profile
익숙해지기 위해 기록합니다

0개의 댓글