앞 장에서는 Article Model과 Serializer를 만들었다.
이제 Article을 Create-Retrieve-Update-Delete하는 뷰를 만들어 보자. DRF에서 뷰를 만드는 것은 Pure Django에서와 마찬가지로 함수형과 클래스형 2가지로 만들 수 있다.
REST프레임워크는 API 뷰를 만드는데 사용할 수 있는 2가지 wrapper를 제공한다.
wrapper 에서는 뷰에서 Request 인스턴스를 수신하고, 해당 메소드를 인자로 전달해서 해당 메소드에 맞는 로직이 실행되도록 도와준다. 즉, @api_view는 클래스형 뷰의 as_view()처럼 여러 가지 유형의 메소드를 함수형에서도 처리해 줄 수 있도록 도와준다고 보면 될 듯 하다.
엔드포인트는 pk 정보가 필요 없는 List, Create를 수행하는 뷰와 pk 정보가 필요한 Detail, Update, Delete를 수행하는 뷰, 2가지로 작성할 수 있다.
[api/urls.py]
from django.urls import path
from news.api.views import (article_list_create_api_view,
article_detail_api_view)
urlpatterns = [
path("articles/", article_list_create_api_view, name='article-list'),
path("articles/<int:pk>", article_detail_api_view, name='article-detail')
]
뉴스 아티클의 리스트를 뿌려주는 기능과 리스트를 추가하는 기능은 하나의 뷰에서 처리할 수 있도록 아래와 같이 코드를 짤 수 있다.
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):
if request.method == "GET":
articles = Article.objects.filter(active=True)
serializer = ArticleSerializer(articles, many=True)
# many => queryset에 대응. many 없으면 instance 1개가 올 것으로 기대하고 있어 에러 발생함.
return Response(serializer.data)
elif request.method == "POST":
serializer = ArticleSerializer(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)
Article을 생성하는 POST 메소드가 처리되는 로직을 debug로 살펴보면 아래와 같다.
아래 내용을 통해 request.data, request.query_params, serializer에는 각각 어떤 값들이 담겨줘서 처리하게 되는지 이해할 수 있다.
> /Users/seungholee/udemy_drf/newsapi/news/api/views.py(79)article_list_create_api_view()
-> serializer = ArticleSerializer(data=request.data)
(Pdb) n
> /Users/seungholee/udemy_drf/newsapi/news/api/views.py(80)article_list_create_api_view()
-> if serializer.is_valid():
(Pdb) request
<rest_framework.request.Request object at 0x10fa3a150>
(Pdb) request.data
{'time_since_publication': '1 month, 2 weeks', 'author': 'Joey', 'title': 'Trade force', 'description': "it's tough", 'body': 'hahahah', 'location': 'Seoul', 'publication_date': '2020-07-09T08:31:45Z', 'active': True}
(Pdb) request.query_params
<QueryDict: {}>
(Pdb) request.parsers
[<rest_framework.parsers.JSONParser object at 0x10fa148d0>, <rest_framework.parsers.FormParser object at 0x10fa3a450>, <rest_framework.parsers.MultiPartParser object at 0x10fa3a190>]
(Pdb) n
> /Users/seungholee/udemy_drf/newsapi/news/api/views.py(81)article_list_create_api_view()
-> serializer.save()
(Pdb) serializer
ArticleSerializer(data={'time_since_publication': '1 month, 2 weeks', 'author': 'Joey', 'title': 'Trade force', 'description': "it's tough", 'body': 'hahahah', 'location': 'Seoul', 'publication_date': '2020-07-09T08:31:45Z', 'active': True}):
id = IntegerField(label='ID', read_only=True)
time_since_publication = SerializerMethodField()
author = StringRelatedField()
title = CharField(max_length=120)
description = CharField(max_length=200)
body = CharField(style={'base_template': 'textarea.html'})
location = CharField(max_length=120)
publication_date = DateTimeField()
active = BooleanField(required=False)
created_at = DateTimeField(read_only=True)
updated_at = DateTimeField(read_only=True)
3-1) Request
DRF에서는 HTTP 요청 객체로서 HttpRequest 객체를 확장한 Request 객체를 사용한다. Request는 HttpRequest 객체보다 요청 내용을 유연하게 파싱할 수 있도록 돕는다. 자주 사용하게 될 Request의 속성은 아래와 같다.
[request.POST와 request.data의 차이점]
request.POST # 폼 데이터만 처리할 수 있고, POST 메소드에서만 동작한다.
request.data # 임의의 데이터를 처리할 수 있고, POST, PUT, PATCH 메소드에서 동작한다.
2-2) Response
렌더링되지 않은 내용을 읽어서 클라이언트가 요청한 콘텐트 타입에 맞는 형식으로 자동 렌더링 해 준다. Pure Django에서는 전달할 데이터에 따라 HttpResponse, JsonResponse를 개발자가 직접 지정을 해 주어야 하지만, DRF의 Response를 이용하면 알아서 렌더링을 해 주기 때문에 편하게 쓸 수 있다.
# Signature
Response(data, status=None, template_name=None, headers=None, content_type=None)
# use case
return Response(data, status.HTTP_201_CREATED)
2-3) status
REST Framework에서는 status 모듈 안에 각각의 상태정보를 속성으로 담고 있다. 따라서 status.[상태 속성값]을 호출하면 그에 맞는 상태 값이 전달된다.
status.HTTP_200_OK
stauts.HTTP_400_BAD_REQUEST
뉴스 아티클의 디테일 정보 가져오기, 수정하기, 삭제하기를 수행하는 뷰를 만들어 보자.
@api_view(["GET", "PUT", "DELETE"])
def article_detail_api_view(request, pk):
try:
article = Article.objects.get(pk=pk)
except Article.DoesNotExist:
return Response({"error" : {
"code" : 404,
"message" : "Article not found"
}}, status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = ArticleSerializer(article)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = ArticleSerializer(article, 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':
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)