Renderer를 통한 다양한 응답 포맷 지원

guava·2022년 1월 15일
0

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

API를 구현할 때 다양한 포맷에 맞춰서 응답할 수 있다.

하나의 API에서 인자에 따라 같은 내용의 PDF, XLSX, JSON, HTML 등 다양한 포맷으로 응답을 하고 싶을 수도 있을 것이다.

DRF의 Renderer를 이용하면 다양한 형태의 응답 포맷을 지원할 수 있다.

1. Renderer를 통한 다양한 응답포맷 지원


1.1. Renderer


같은 Endpoint에서 요청받은 타입에 맞춰, 다양한 응답포맷을 지원

Content-Type, URL의 방법을 통해 Renderer 지정 가능

1.2. 기본 지원되는 Renderer


  1. JSONRenderer (디폴트 지정): json.dumps를 통한 JOSN 직렬화
    • media_type → application/json, format → json
    • httpie로 요청 시 json응답으로 오고 브라우저에서 요청 시 html응답이 온다. 이는 Content-Type에 의해서 결정된다.
  2. BrowsableAPIRenderer(디폴트 지정): self-document HTML 렌더링
  3. TemplateHTMLRenderer: 지정 템플릿을 통한 렌더링
    • media_type → text/html, format → api
    • Response에서 template_name인자 지정 필요
    • API서버라고 해서 모든 응답을 JSON으로 받지 않아도 된다. 경우에 따라서 HTML응답을 받을 수도 있다. (Server Side Rendering?)

1.2.1. TemplateHTMLRenderer

템플릿을 통해 Render를 수행하기에 별도의 Serializer가 불필요

# https://www.django-rest-framework.org/topics/html-and-forms/
from rest_framework.renderers import TemplateHTMLRenderer

class PostDetailAPIView(RetrieveAPIView):
    queryset = Post.objects.all()
    renderer_classes = [TemplateHTMLRenderer]  # 1
    template_name = 'blog/post_detail.html'    # 2

    def get(self, request, *args, **kwargs):
        return Response({
            'post': self.get_object(),
        })

# 위에 1, 2번을 빼고, self.get_object()를 serializer를 통한 직렬화 이후 Response로 넘기면 json응답으로 됨

# myapp/urls.py에 아래를 추가
urlpatterns = [
    # ...
    path('mypost/<int:pk>/', views.PostDetailAPIView.as_view())
]

연습하기

  • 위와 같이 PostDetailAPIView에서 renderer_classes의 유무에 따른 차이를 브라우저에서 확인해본다.

1.2.2. 이 외에도 다양한 Renderer

StaticHTMLRenderer

→ 미리 렌더링된 HTML을 반환. Response 객체 생성 시에 HTML문자열을 직접 지정

@api_view(["GET"])
@renderer_classes([StaticHTMLRenderer])
def static_view(request):
    html = """<html><body>example</body></html>"""
    return Response(html)

추가로, AdminRenderer, HTMLFormRenderer, MultiPartRenderer 등이 지원됨

1.2.3. 써드파티 Renderer

  • drf-renderer-xlsx (내부적으로 openpyxl 활용, application/xlsx, 포맷: xlsx)
  • djangorestframework-yaml
  • djangorestframework-xml
  • djangorestframework-jsonp
  • djangorestframework-msgpack
  • djangorestframework-csv
  • rest-framework-latex

2. Renderer 선택


2.1. Renderer 클래스 리스트 지정하기


  • 일반적으로 디폴트 렌더러는 전역 지정으로 지정하고 스페셜하게 특정 API에서만 사용하는 렌더러* 의 경우에는, APIView에 직접 지정할 수 있다.
  • 실제 Content-type이나 포맷 인자에 따라 다른 응답을 받을 수 있으므로, 주로 전역 지정을 추천.

전역 지정

  • settings → REST_FRAMEWORK → DEFAULT_RENDERER_CLASSES 리스트에 문자열로 지정
    • 설정 예(프로젝트의 settings.py에 작성하면 된다.)
  • 기본 설정이 있고, 위와 같이설정하면 오버라이딩 된다.

APIView 마다 지정

  • queryset, serializer_class와 더불어 renderer_classes 리스트

@api_view마다 지정

  • renderer_classes 장식자

Renderer 이외의 다른 속성들도 마찬가지 방법으로 설정

2.2. Response의 기본 2가지 유형의 응답 포맷


rest_framework.response.Response

  • api: API Endpoint에 브라우저를 통해 접근할 때, 웹 UI로 조회 가능
  • json: 보통의 API 접근
  • Router를 통해 URL 등록 시 다음 주소들을 지원하도록 구현되어있다.
    • http://localhost:8000/resource.api
    • http://localhost:8000/resource.json
    • http://localhost:8000/resource/?format=json

2.3. 응답 포맷은 어떻게 결정되는가?


Accept 헤더

  • Accept: application/json
  • Accept: text/html

GET 인자 format

  • ?format=json
  • ?format=api

URL Captured Values에서의 format인자

  • .json
  • .api

2.4. URL Captured Values에서의 format인자


  1. rest_framework.urlpatterns.format_suffix_patterns를 통해, 기존 urlpatterns끝에 format 인자 지원을 추가
  2. DefaultRouter에서는 기본 지원
    • Router.urls에서의 URL Patterns
      [
          <URLPattern '^post/$' [name='post-list']>,
          <URLPattern '^post\.(?P<format>[a-z0-9]+)/?$' [name='post-list']>,
          <URLPattern '^post/(?P<pk>[^/.]+)/$' [name='post-detail']>,
          <URLPattern '^post/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='post-detail']>,
          <URLPattern '^$' [name='api-root']>,
          <URLPattern '^\.(?P<format>[a-z0-9]+)/?$' [name='api-root']>
      ]

2.5. @api_view에서의 format 인자


함수 기반 뷰에서는 URL Captured Values는 Keyword Arguments를 통해 전달됩니다. 즉, format인자를 받도록 설정했다면 format인자를 추가해주세요.

views.py

from rest_framework.decorators import api_view

@api_view(['GET'])
def hello(request, format=None):
    return Response([])

실제 Router를 통해서 format인자가 붙은 urlpattern이 추가되었는데, Router를 쓰지 않고도 추가하고싶다면 아래와 같이 format_suffix_patterns를 이용해서 format을 지원하도록 추가할 수 있다.

urls.py

from rest_framework.urlpatterns import format_suffix_patterns

urlpatterns = format_suffix_patterns([
    path('hello/', views.hello),
])

생성된 URL Patterns

[
    <URLPattern 'hello/'>,
    <URLPattern 'hello<drf_format_suffix:**format**>'>,
]

HTTPie 통한 format 지정

json 형식 요구

  • http http://localhost:8000/ Accept:application/json → 커스텀 헤더를 정해준다.
  • http http://localhost:8000/?format=json
  • http http://localhost:8000/.json

html 요구

  • http http://localhost:8000/ Accept:text/html
  • http http://localhost:8000/?format=api
  • http http://localhost:8000/.api

0개의 댓글