All Articles

Django Rest Framework 활용하기 2 - @api_view

Udemy 강의를 들으면서, DRF 공식문서를 보면서, 그리고 구글링하면서 정리한 내용입니다.

Django REST Framework - Level One

The @api_view Decorator - Part One

DRF에서는 API 뷰에 쓸 수 있는 두 가지 wrapper를 제공한다. 하나는 @api_view 데코레이터로 함수 기반(function based) API 뷰를 짤 때 쓴다. 다른 하나는 APIView 클래스로, 클래스 기반(class based) API 뷰를 짤 때 사용한다. 이 wrapper를 사용하면 request instance를 받는 데에 필요한 모든 코드를 손쉽게 사용할 수 있으며, 상황에 맞는 response를 내보내주는 것, 그리고 예외처리까지 가능하다.

그렇다면 DRF를 이용해 짠 뷰는 메소드를 어떻게 구분해내는가? 요청한 request에 따라 결정되며 그것을 판별하는 장치가 @api_view 데코레이터다.

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from news.models import Article
from news.api.serializers import ArticleSerializer

@api_view(['GET'])
def article_list_create_api_view(request):
  # 데코레이터를 통해 request로 온 메소드와 일치하는지 판별 후,
  if request.method == 'GET':
    # 유효한(active = True) 기사 객체만 불러온다.
    articles = Article.objects.filter(active = True)
    # 직렬화한 데이터를 리턴하기 위해 위에서 선언한 query set을 serializer에 인자로 넣는다.
    serializer = ArticleSerializer(articles)
    return Response(serializer.data)

url을 설정한 뒤 서버를 띄워서 확인해보면 AttributeError가 뜬다. 왜일까? 에러 메시지를 확인해보자.

Got AttributeError when attempting to get a value for field `author` on serializer `ArticleSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance.
Original exception text was: 'QuerySet' object has no attribute 'author'.

불러온 쿼리셋(list)을 단일 항목으로 직렬화하려고 해서 생기는 오류다. serializer = 부분에 many = True를 추가해주면 된다.

serializer = ArticleSerializer(articles, many = True)

이제 모든 article list가 뜨는 것을 확인할 수 있다.

article list

보다시피 DRF에서 API는 browsable하다. 기본적으로 웹 인터페이스를 가지고 있어서 개발자가 사용하기 매우 편리하다. 저 페이지 하나에서

  • request 메소드
  • 엔드포인트 주소
  • status code 및 message
  • 해당 api에서 사용 가능한 메소드

를 모두 확인할 수 있다. 장고를 쓸 때에는 Postman에서 엔드포인트 및 자료 리턴 형태를 테스트했지만 DRF에서는 웹 브라우저에서도 그 작업이 가능하다.

DRF의 편리한 점은 여기서 끝이 아니다! views.py 소스코드를 다시 들여다보면 장고를 쓸 때와는 어딘지 다른 점이 보인다. return 부분이다.

from rest_framework.response import Response

'''
코드 중략
'''

    return Response(serializer.data)

꼬박꼬박 return JsonResponse를 붙여줘야 json 형태로 리턴할 수 있었는데 DRF에서는 Response 클래스만 사용하면 된다. request 맥락에 따라서 DRF가 알아서 가장 적절한 형태로 response를 보내기 때문이다! DRF 만세! 박수~~

이제 post 메소드를 추가해보자.

'''
코드 전략
'''

@api_view(['GET', 'POST'])
def article_list_create_api_view(request):
    
  if request.method == 'GET':
    articles = Article.objects.filter(active = True)
    serializer = ArticleSerializer(articles, many = True)
    return Response(serializer.data)

  elif request.method == 'POST':
    serializer = ArticleSerializer(data = request.data)
    # 장고와 달리 DRF에서는 request에서 데이터를 받을 때(request.data)
    # 반드시 .is_valid() 여부를 체크해야 한다.
    # valid하지 않을 때는 serializer.errors를 리턴한다.
    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)

post method

Allow 메소드에 POST가 추가되었으며 하단에 객체를 생성할 수 있는 폼이 생겼다. 폼에 json 형태로 작성해서 post 버튼을 누르면 새 인스턴스를 간편하게 추가할 수 있다. "id""created_at", "updated_at" 항목은 자동생성이므로 작성하지 않아도 괜찮다.

posted article

post 클릭 후 하단 폼을 이용해 추가한 인스턴스를 확인할 수 있다.

The @api_view Decorator - Part Two

이제 개별 인스턴스를 조회(get), 수정(put), 삭제(delete)할 수 있는 함수를 만들어보자.

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from news.models import Article
from news.api.serializers import ArticleSerializer

@api_view(['GET', 'POST'])
def article_list_create_api_view(request):
  '''
  코드 중략
  '''

@api_view(['GET', 'PUT', 'DELETE'])
def article_detail_api_view(request, pk):
  # try, except 대신 get_object_or_404를 import 해서 쓸 수도 있다. 
  try:
    # pk(인스턴스의 id)값을 받아 어떤 인스턴스인지 특정
    # url slug로 pk값을 받도록 urls.py에서 설정해준다.
    article = Article.objects.get(pk = pk)
  # 받은 pk값으로 조회했을 때 해당하는 인스턴스가 없다면 출력할 에러 코드와 메시지를 설정한다.
  except Article.DoesNotExist:
    return Response({'error' : {
      'code' : 404,
      'message' : "Article not found!"
    }}, status = status.HTTP_404_NOT_FOUND)
  # 만약 article이 존재한다면,
  if request.method == 'GET':
    serializer = ArticleSerializer(article)
    return Response(serializer.data)
  elif request.method == 'PUT':
    serializer = ArticleSerializer(article, data = request.data)
    # request에서 data를 받았으니 .is_valid() 필수
    if serializer.is_valid():
      serializer.save()
      return Response(serializer.data)
    return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)
  elif request.method == 'DELETE':
    article.delete()
    # 인스턴스를 삭제한 뒤에는 204 NO CONTENT를 리턴
    return Response(status = status.HTTP_204_NO_CONTENT)

url slug에 불러오고 싶은 인스턴스의 id 필드 값(value)에 해당하는 숫자를 입력해주면 조회할 수 있다. 수정, 삭제도 가능하다.

article detial view get

없는 id를 입력하면 뷰에서 선언한 not found 메시지가 뜬다.

article not found