SW개발/Django

[Django]Django REST Framework 튜토리얼 2 (Requests and Responses)

이번 포스팅에서는 DRF의 Requests and Responses 공식 문서를 공부하면서 번역해보려고 한다.

해석에 틀린 내용이 있을 수 있으니 이해가 안가는 부분은 아래의 공식문서를 참조하기 바란다.

 

DRF Requests and Responses tutorial 공식 Documentation

이번 파트에서는 REST Framwork의 핵심을 다룰 것이다. 몇가지 필수적인 구성요소를 소개할 것이다.

 

Request objects

REST framework의 Request 객체는 HttpRequest 객체를 상속 받아 만들어 졌으며 보다 유용한 request parsing 기능을 제공한다.

Request 객체의 핵심 기능은 request.data 속성으로 request.POST와 비슷하지만 Web API 작업에 더 유용하다.

request.POST  # 오직 폼 데이터를 처리하고, 오직 POST 메소드에서만 동작함
request.data  # 임의의 데이터를 처리하고, POST와 PATCH 메소드에서 동작함

 

Response objects

REST Framework는 Response 객체를 가지고 있다. 이 객체는 TemplateResponse 유형이며, 렌더링 되지 않은 컨텐츠를 가져오고, 컨텐츠 협상을 통해 클라이언트에게 반환할 올바른 컨텐츠 형식을 결정한다.

return Response(data)  # 클라이언트가 요청한 컨텐츠 유형으로 렌더링 함

 

Status codes

뷰에서 숫자로 이루어진 HTTP status 코드를 사용할지라도 이것은 항상 명확하게 읽을 수 없고 오류코드가 잘못 나오면 알아 차리는 것이 어렵다. REST Framework는 status 모듈 안에 있는 HTTP_400_BAD_REQUEST를 사용하여 각각의 상태 코드를 명확하게 한다.

이것은 숫자 식별자를 사용하는 것보다 좋은 아이디어 이다.

 

Wrapping API views

REST Framework는 API view를 작성하기 위해 두개의 wrapper를 제공한다.

  • @api_view 데코레이터는 함수 기반 뷰에서 동작한다
  • APIView 클래스는 클래스 기반 뷰에서 동작한다

이 wrapper들은 view에서 Request 인스턴스를 수신하는지 확인하고, 컨텐츠 협상을 수행할 수 있도록 Response 객체에 context를 추가하는 등 몇가지 기능을 한다.

wrapper들은 또한 405 Method Not Allowed 응답도 제공하는데, 이는 request.data에 잘못된 형식을 입력할 때 일어나는 ParseError처럼 예외를 처리하는 동작 같은 것이다.

 

Pulling it all together

자, 이제 새로운 컴포넌트를 이용하여 이전에 만들었던 view를 리팩토링 할 것이다.

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
def snippet_list(request):
    """
    코드 snippet의 목록을 보여주거나, 새로운 snippet 생성
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

instance view는 이전의 예제에 비해 향상 되었다. 좀더 간결하고 코드는 이제 Forms API로 작업하는 것과 매우 유사하게 느껴진다. 

또한 네이밍 된 status code를 사용하고 그것은 명백한 response 의미를 전달할 수 있다.

views.py 모듈안에 개별 snippet을 위한 뷰가 있다. 따라 수정하자.

@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
	snippet을 검색, 업데이트, 삭제	
	"""
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

이 과정이 매우 친숙하게 느껴질 것이다. 이것은 일반적인 Dajngo view를 작업하는 것과 다르지 않다.

더 이상 특정 컨텐츠 유형에 대한 요청이나 응답을 명시적으로 연결하지 않았다. request.data는 json 요청도 처리할 수 있고, 다른 형식의 요청 또한 처리할 수 있다. 마찬가지로 데이터와 함께 response 객체를 반환하지만 REST framework가 response를 올바른 컨텐츠 유형으로 렌더링 하도록 허용한다.

 

Adding optional format suffixes to our URLs

response가 더 이상 하나의 컨텐츠 유형에 고정되지 않는다는 사실을 활용하기 위해 API endpointsuffixes에 대한 지원을 추가할 것이다. format suffix를 사용하면 지정된 형식을 명시적으로 참조하는 URL이 제공되며, API가 http://example.com/api/items/4.json 같은 URL을 처리할 수 있다.

양쪽의 view에 아래처럼 format 키워드를 추가하자.

def snippet_list(request, format=None):

그리고

def snippet_detail(request, pk, format=None):

이제 snippets/urls.py 파일을 약간 업데이트 하여 기존 URL에 추가로 format_suffix_patterns set을 추가하자.

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

이러한 추가 URL 패턴을 반드시 추가 할 필요는 없지만, 특정 형식을 참조하는 간단하고 깔끔한 방법을 제공하는 것이다.

 

How's it looking ?

tutorial part 1 에서 했던 것처럼 command line에서 테스트를 해봐라. 모든 것이 거의 비슷하게 동작할 테지만 유효하지 않은 요청에 대해서 더 좋은 오류 처리를 해준다.

이전과 마찬가지로 모든 스니펫 목록을 얻을 수 있다.

http http://127.0.0.1:8000/snippets/

HTTP/1.1 200 OK
...
[
  {
    "id": 1,
    "title": "",
    "code": "foo = \"bar\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  },
  {
    "id": 2,
    "title": "",
    "code": "print(\"hello, world\")\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  }
]

우리는 Accept 헤더를 사용하여 response 되는 형식을 제어할 수 있다.

http http://127.0.0.1:8000/snippets/ Accept:application/json  # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html         # Request HTML

혹은 아래 처럼 형식 suffix 를 추가할 수도 있다.

http http://127.0.0.1:8000/snippets.json  # JSON suffix
http http://127.0.0.1:8000/snippets.api   # Browsable API suffix

마찬가지로, Content-Type 헤더를 사용하여 우리가 보낸 request의 형식을 컨트롤 할 수도 있다.

# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"

{
  "id": 3,
  "title": "",
  "code": "print(123)",
  "linenos": false,
  "language": "python",
  "style": "friendly"
}

# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"

{
    "id": 4,
    "title": "",
    "code": "print(456)",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

만약 너가 http request 에 --debug 스위치를 추가하면 request 헤더에서 요청한 타입을 볼 수 있다.

이제 http://127.0.0.1:8000/snippets/ 를 접속하여 웹 브라우저에서 API를 열어보자.

 

Browasability

API는 클라이언트의 요청에 따라 response 유형을 선택하기 때문에 기본적으로 웹 브라우저에서 해당 리소스를 요청할 때 리소스의 HTML 형식 표현을 반환해준다. 이를 통해 API가 완벽하게 web-browsable 가능한 HTML 표현을 반환할 수 있다.

web-browsable API가 있으면 유용성이 크게 향상되고 API를 훨씬 쉽게 개발하고 사용할 수 있다.

또한, API를 검사하고 사용하려는 다른 개발자의 진입 장벽을 크게 낮출 수 있다.

browsable api 특징 또는 어떻게 커스터 마이징 하는 정보에 대한 것은 browsable api 를 참고하자.

 

What's next?

tutorial part 3 에서는 클래스 기반 뷰를 사용하기 시작하고 일반적인 뷰가 작성하는 코드의 양을 줄이는 방법을 알아볼 것이다.

 

728x90