DRF 공부하기 (3) :: FBV, Request, Response, APIView(CBV)

PHYYOU·2020년 10월 14일
2
post-thumbnail

출처
출처 2
추가적인 설명의 출처


위의 블로그의 내용을 복붙함.

Function-based view, Request, Response (News api 2편)

  • 앞 장에서는 Article Model과 Serializer를 만들었다. 이제 Article을 Create-Retrieve-Update-Delete하는 뷰를 만들어 보자. DRF에서 뷰를 만드는 것은 Pure Django에서와 마찬가지로 함수형과 클래스형 2가지로 만들 수 있다.

REST프레임워크는 API 뷰를 만드는데 사용할 수 있는 2가지 wrapper를 제공한다.

  • 함수형 뷰를 위한 @api_view
  • 클래스형 뷰를 위한 APIView

wrapper 에서는 뷰에서 Request 인스턴스를 수신하고, 해당 메소드를 인자로 전달해서 해당 메소드에 맞는 로직이 실행되도록 도와준다. 즉, @api_view는 클래스형 뷰의 as_view()처럼 여러 가지 유형의 메소드를 함수형에서도 처리해 줄 수 있도록 도와준다고 보면 될 듯 하다.

1. 엔드포인트 구성 (urls.py)

엔드포인트는 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')
]

2. Article List/Create View 만들기

뉴스 아티클의 리스트를 뿌려주는 기능과 리스트를 추가하는 기능은 하나의 뷰에서 처리할 수 있도록 아래와 같이 코드를 짤 수 있다.

1) views.py 전체 코드

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)

2) Debug를 통한 views.py 처리 로직 이해하기

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) Request, Response 공식문서 살펴보기

3-1) Request

DRF에서는 HTTP 요청 객체로서 HttpRequest 객체를 확장한 Request 객체를 사용한다. Request는 HttpRequest 객체보다 요청 내용을 유연하게 파싱할 수 있도록 돕는다. 자주 사용하게 될 Request의 속성은 아래와 같다.

  • Request Parsing
    • request.data : (POST, PUT, PATCH메소드에서) Body에 담겨 전달된 데이터를 리턴 (key:value)
    • request.query_params : 쿼리스트링으로 전달되는 데이터를 리턴 (key:value)
  • Request Authentication
    • request.user : django.contrib.auth.models.User의 객체를 리턴
    • request.auth : 해당 객체의 token를 리턴 (없다면, None을 리턴)
  • Browser enhancements
    • request.method : request의 메소드를 리턴
    • request.content_type : request의 컨텐트타입을 리턴

[request.POST와 request.data의 차이점]

request.POST  # 폼 데이터만 처리할 수 있고, POST 메소드에서만 동작한다.
request.data  # 임의의 데이터를 처리할 수 있고, POST, PUT, PATCH 메소드에서 동작한다.

추가적인 설명
DRF는 HttpRequest를 Request 객체로 확장하여 더 유연한 요청 파싱을 제공한다. 핵심 기능은 requst.POST와 비슷하지만 웹 API에 더 유용한 request.data 속성이다.

request.POST
request.data

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)

추가적인 설명
Response 객체는 TemplateResponse 객체의 일종으로, 렌더링되지 않은 컨텐츠를 가져오고 컨텐츠 협상(?)을 통해 클라이언트에게 반환할 올바른 컨텐츠 유형을 결정한다.

return Response(data)

2-3) status

REST Framework에서는 status 모듈 안에 각각의 상태정보를 속성으로 담고 있다. 따라서 status.[상태 속성값]을 호출하면 그에 맞는 상태 값이 전달된다.

status.HTTP_200_OK
stauts.HTTP_400_BAD_REQUEST





2. Article Detail View 만들기

뉴스 아티클의 디테일 정보 가져오기, 수정하기, 삭제하기를 수행하는 뷰를 만들어 보자.

1) views.py 전체 코드

@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)


=> 이렇게 했다가 오류가 나왔다, 그 이유는 try: 문중 get을 할 때 get에 옵션을 걸지 않아서 serialize 할 때 many=True로 걸어야됀다고 하는 에러였다.
https://itinerant.tistory.com/164 ==> 2번의 Attribute 에러

고친 views.py

Browsability

API는 클라이언트 요청에 따라 응답 데이터의 content type을 정하기 때문에 웹 브라우저에서 요청하면 HTML 포맷의 데이터를 반환할 것이다.

web-browsable한 API를 사용한다는 건 굉장히 사용성면에서 유리하고 API를 더 쉽게 만들고 사용할 수 있게 한다. 또한 다른 개발자들이 내가 만든 API를 파악하거나 사용하는 데에 진입장벽을 낮추기도 한다.

APIView 클래스와 api_view 데코레이터

이번 시간에는 본격적으로 APIView 에 대해서 알아보도록 합시다.

APIViewapi_view는 각각 CBV와 FBV 에 대응되는 내용입니다.

두 가지 모두 뷰에 여러가지 기본 설정을 부여하게 됩니다. 이는 아래와 같고 상황에 맞춰서 이를 커스튬하여 사용하게 됩니다.

  • 직렬화 클래스 지정
    • renderer_classes
    • default
      • JSON 직렬화 : rest_framework.renderers.JSONRenderer
      • HTML 페이지 직렬화 : rest_framework.renderers.TemplateHTMLRenderer
  • 비직렬화 클래스 지정
    • parser_classes
    • default
      • JSON 포맷 처리 : rest_framework.parsers.JSONParser
      • FormParser : rest_framework.parsers.FormParser
      • MultiPartParser : rest_framework.parsers.MultiPartParser
  • 인증 클래스 지정
    • authentication_classes
    • default
      • 세션기반인증 : rest_framework.authentication.SessionAuthentication
      • HTTP basic 인증 : rest_framework.authentication.BasicAuthentication
  • 사용량 제한 클래스 지정
    • throttle_classes
    • default
      • 빈 튜플
  • 권한 클래스 지정
    • permission_classes
    • default
      • 누구라도 접근 허용 : rest_framework.permissions.AllowAny
  • 요청에 따라 적절한 직렬화/비직렬화 선택

    • content_negotiation_class
    • 같은 URL 요청에 대해서 JSON 응답을 할 지, HTML 응답을 할 지 판단
    • default
      • rest_framework.negotiation.DefaultContentNegotiation
  • 요청 내역에서 API 버전 정보를 탐지할 클래스 지정

    • versioning_class
    • 요청 URL의 HEADER에서 버전 정보를 탐지하여 맞는 버전을 호출
    • default
      • 버전 정보를 탐지하지 않습니다. : None

APIView

우선 APIView 부터 자세히 알아보도록 합시다.

  • 위에서 말했듯이 이는 CBV 중 하나이기 때문에 하나의 URL 에 대해서만 처리를 할 수 있습니다.

    • /post/ 에 대한 CBV

      • get : 포스팅 목록
      • post : 새 포스팅 생성
    • /post/<int:pk>/ 에 대한 CBV

      • get : pk 번 포스팅 내용
      • put : pk번 포스팅 수정
      • delete : pk번 포스팅 삭제
  • 요청 method 에 맞게 맴버함수를 정의하면 해당 method 로 request가 들어올 때 호출되게 됩니다.

def get(self, request):
    pass 
def post(self, request):
    pass 
def put(self, request):
    pass
def delete(self, request):
    pass
  • 각 method 가 호출되면 위에서 봤던 설정에 맞춰 처리가 이루어집니다.

    • 직렬화/비직렬화
    • 인증 체크
    • 사용량 제한 체크
    • 권한 체크
    • 요청한 버전 체크
  • 실습

실습을 위해 간단한 Post 모델을 정의하였습니다.

# models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    create_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

이에 대한 Seializer 입니다.

# serializers.py

from rest_framework.serializers import ModelSerializer
from .models import Post

class PostSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'

views 에서 이들을 불러와 처리를 해주게 됩니다.

# views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Post
from .serializers import PostSerializer

# 포스팅 목록 및 새 포스팅 작성
class PostListAPIView(APIView):
    def get(self, request):
        serializer = PostSerializer(Post.objects.all(), many=True)
    def post(self, request):
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
          	serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)  
      
from django.shortcuts import get_object_or_404

# 포스팅 내용, 수정, 삭제
class PostDetailAPIView(APIView):
    def get_object(self, pk):
        return get_object_or_404(Post, pk=pk)
      
    def get(self, request, pk, format=None):
        post = self.get_object(pk)
        serializer = PostSerializer(post)
        return Response(serializer.data)
    
    def put(self, request, pk):
      	post = self.get_object(pk)
        serializer = PostSerializer(post, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
    def delete(self, request, pk):
        post = self.get_object(pk)
        post.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

각 클래스에 대해 as_view 로 라우팅을 해줍니다.

from django.urls import path, include
from . import views

urlpatterns = [
    # FBV
    path('post/', views.PostListAPIView.as_view()),
    path('post/<int:pk>/',views.PostDetailAPIView.as_view()),
]

@api_view 장식자

api_view는 FBV 에 대해서 사용하는 장식자 입니다.

python decorator 란

장식자에 대한 설명은 위의 링크를 참고하시면 됩니다.

  • 실습

    api_view의 첫번째 인자로 해당 함수에서 가능한 request method 를 리스트로 지정해줍니다.

# views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Post
from .serializers import PostSerializer
from rest_framework.decorators import api_view

@api_view(['GET','POST'])
def post_list(request):
	if request.method == 'GET':
		qs = Post.objects.all()
		serializer = PostSerializer(qs, many=True)
		return Response(serializer.data)
	else:
		serializer = PostSerializer(data=request.data)
		if serializer.is_valid():
			serializer.save()
			return Response(serializer.data, status=201)
		return Response(serializer.errors, status=400)

@api_view(['GET','PUT','DELETE'])
def post_detail(request, pk):
	post = get_object_or_404(Post, pk=pk)
	if request.method == 'GET':
		serializer = PostSerializer(post)
		return Response(serializer.data)
	elif request.method == 'PUT':
	serializer = PostSerializer(post, data=reqeust.data)
		if serializer.is_valid():
			serializer.save()
			return Response(serializer.data)
		return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
	else:
		post.delete()
		return Response(status=status.HTTP_204_NO_CONTENT)

# urls.py
    
from django.urls import path, include
from . import views
    
urlpatterns = [
	# FBV
	path('cbv/post/', views.post_list),
	path('cbv/post/<int:pk>/',views.post_detail),
]
profile
박효영

0개의 댓글