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

urls.py
views.py
postman get ok
httpie post created
httpie post 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)

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

고친 views.py
views.py(2)

get response

delete

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개의 댓글