Django, DRF로 각각 JSON 응답뷰 만들어보기

guava·2022년 1월 11일
0

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.

장고 기본 구현과 DRF 구현 비교


한 Post 모델에 대한 API 서비스를 제공할 때


이에 대한 URL을 설게한다면?

  1. 새 포스팅 내용을 받아 등록하고, 확인 응답 → /post/new/ 주소로 POST 요청
  2. 포스팅 목록 및 검색 응답 → /post/ 주소로 GET 요청
  3. 10번 포스팅 내용 응답 → /post/10/ 주소로 GET 요청
  4. 10번 포스팅 내용 갱신하고, 확인 응답 → /post/10/update/ 주소로 POST 요청
  5. 10번 포스팅 내용 삭제하고, 확인 응답 → /post/10/delete/ 주소로 POST 요청

REST API 스타일로 재설계를 해본다면?

/post/ 주소

  • GET 방식 요청: 목록 응답
  • POST 방식 요청: 새 글 생성하고, 확인 응답
  • (사용 X) PUT/PATCH 방식 요청
  • (사용 X) DELETE 방식 요청

컨셉 코드1

  • 같은 주소에서 목록 응답, 새 글 생성을 처리한다.
  • 같은 주소에서 처리 → 같은 view함수에서 분기를 통해 처리한다.
  • 실제로 JsonResponse를 통해 모델 직렬화를 지원하지는 않는다. 따로 커스텀 해야한다.

myapp/models.py

from django.db import models

class Post(models.Model):
    message = models.TextField()

myapp/forms.py

from django import froms

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = '__all__'

myapp/views.py

def post_list(request):
    if request.method == 'POST':
        # 새 글 저장을 구현
        form = PostForm(request.POST, request.FILES)
	      if form.is_valid():
            port = form.save()
            return JsonResponse(post)
        return JsonResponse(form.errors)
    else:
        # 목록 응답을 구현
        return JsonResponse(Post.objects.all())

/post/10/ 주소

  • GET 방식 요청: 10번 글 내용 응답
  • (사용 X) POST 방식 요청
  • PUT 방식 요청: 10번 글 수정/저장하고, 확인 응답
  • DELETE 방식 요청: 10번 글 삭제하고, 확인 응답

컨셉 코드2

/post/주소와 마찬가지로 PUT, DELETE 요청을 동일 주소(view함수)에서 분기 처리한다.
컨셉 코드1과 마찬가지로 JsonResponse를 통해 모델 직렬화를 지원하지는 않는다. 따로 커스텀 해야한다.

myapp/views.py

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == 'PUT':
        # 특정 글 갱신을 구현
        put_data = QueryDict(request.body)
        form = PostForm(put_data, instance=post)
        if form.is_valid():
            post = form.save()
            return JsonResponse(post)
        return JsonResponse(form.errors)
    elif request.method == 'DELETE':
        # 특정 글 삭제를 구현
        post.delete()
        return HttpResponse()
    else:
        # 특정 글 내용 응답을 구현
        return JsonResponse(post)

→ 총 5개의 뷰 처리가 필요.

다른 모델에 대해서 동일한 기능을 구현한다면, Model/Form을 제외하고는 거의 정형화된 패턴이다.
→ DRF는 이런 정형화된 중복을 줄일 수 있도록 도와주는 Class Based View를 비롯하여 다양한 기능을 지원한다.

Django/DRF를 통한 API 구현 샘플


DRF 설치

공식문서를 확인하고 설치하자. 공식문서에 익숙해지는것이 실력의 기반이 된다.

# pip
pip install djangorestframework~=3.11.0

# peotry
poetry add djangorestframework@3.11.0
  • settings.INSTALLED_APPS에 "rest_framework" 추가

settings에 추가

# ...
INSTALLED_APPS = [
    'rest_framework',
    # ...
]

urlpatterns

urlpatterns = [
    # ...
    path('api-auth/', include('rest_framework.urls'))
]

DRF의 login, logout 뷰

단순히 django.contrib.authlogin/logout이다. (세션인증)

from django.conf.urls import url
from django.contrib.auth import views
app_name = 'rest_framework'
urlpatterns = [
    url(r'^login/$', views.LoginView.as_view(template_name='rest_framework/login.html'), name='login'),
    url(r'^logout/$', views.LogoutView.as_view(), name='logout'),
]

DRF 주요 코드 샘플

위 컨셉코드에서는 /posts/, /posts/10/의 URL을 대응하기 위해 view를 두개 작성해서 각 2, 3개의 분기처리를 하였다.

하지만 DRF에서의 ViewSet을 활용하면, 하나의 ViewSet으로 두 개(/posts/, /posts/10/)의 URL을 대응할 수 있다.

myapp/models.py

from django.db import models
class Post(models.Model):
    message = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

myapp/serializers.py

from rest_framework.serializers import ModelSerializer
from .models import Post
class PostSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'

myapp/views.py

from rest_framework.viewsets import ModelViewSet
from .serializers import PostSerializer
from .models import Post
class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    # def dispatch(self, request, *args, **kwargs):
        """
        dispatch재정의 후, httpie요청의 --form옵션 유무에 따른 로그를 확인해볼 수 있다.
        http POST :8000 message="hello world"
        http --form POST :8000 message="hello world"
        """
        # print("request.body:", request.body)
        # print("request.POST:", request.POST)
        # return super().dispatch(request, *args, **kwargs)

myapp/urls.py

from django.urls import include, path
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register('post', views.PostViewSet)  # 2개의 URL을 만들어 줍니다.
urlpatterns = [
    path('', include(router.urls)),
]

요청 테스트 (HTTPie)

http :8000 # 목록 조회
http --form POST :8000 message="hello world" # 새 포스팅 등록
http :8000/1/ # 1번 포스팅 조회
http --form PUT :8000/1/ message="hello django" # 1번 포스팅 수정
http :8000/1/ # 1번 포스팅 수정
http DELETE :8000/1/ # 1번 포스팅 삭제
http :8000 # 목록 조회

다양한 HTTP 클라이언트


다양한 HTTP 클라이언트 프로그램

  • 유저가 웹브라우저를 통해 웹페이지 간 이동을 할 때
  • 웹 프론트엔드에서 JavaScript를 통한 호출
  • Android/iOS 앱 코드를 통한 호출
  • 웹 요청 개발 프로그램을 통한 호출
    • GUI 프로그램: Postman → Powerful API Client
    • CLI 프로그램: cURL, HTTPie
    • 라이브러리: requests

우리는 이 중에, HTTPie를 통해 주로 실습을 진행한다.

HTTPie를 통한 HTTP 요청

설치

# pip install
pip install httpie

# poetry
poetry add --dev httpie

HTTPie 명령 예시

http GET 요청할주소 GET인자명==값 GET인자명==값
http --json POST 요청할주소 GET인자명==값 GET인자명==값 POST인자명=값 POST인자명=값
http --form POST 요청할주소 GET인자명==값 GET인자명==값 POST인자명=값 POST인자명=값
http PUT 요청할주소 GET인자명==값 GET인자명==값 PUT인자명=값 PUT인자명=값
http DELETE 요청할주소 GET인자명==값 GET인자명==

2종류의 POST 요청 → 인코딩 방법의 차이

  • --form 옵션 지정: multipart/form-data
  • --json 옵션을 지정하거나 생략: application/json → 요청 데이터를 JSON 직렬화

httpbin.org 서비스를 통해, http 요청 연습을 해보기

http GET httpbin.org/get x==1 y==2  # GET 인자는 등호를 2개씩
http --form POST httpbin.org/post a=1 b=2 c=3  # POST 인자는 등호를 한개
http PUT httpbin.org/put hello=world
http DELETE httpbin.org/delete

DRF를 통해 중복을 줄이자

  • 코드가 보다 간결하게 바뀌었음을 확인하실 수 있다
  • DRF가 만능은 아니다
  • DRF를 보다 잘 이해하고, 적절히 DRF의 기능을 선택적으로 활용하는 능력이 필요하다.

0개의 댓글