DRF 공부하기 (4) :: CBV : APIView

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

아래 사례는 Udemy의 "The complete Guide to Django Restframework and Vue JS" 강의 중 Section3의 : Django REST Framework - Level One의 내용을 요약정리한 것입니다.

앞서 Function-based View로 짠 것과 동일한 역할을 하는 코드를 Class-based View로 짜 봤습니다. ListCreateAPIView는 사실상 함수형 코드와 거의 유사하다. DetailAPIView는 get_object 메소드를 추가하여 사용한다는 점이 큰 차이이다. 클래스형 뷰이기 때문에 함수를 메소드로 지정하여, 재활용할 수 있기 때문이다.

2. Views.py 전체 코드

1) ArticleListCreateAPIView

from rest_framework.views import APIView
from rest_framework.generics import get_object_or_404

class ArticleListCreateAPIView(APIView):

    def get(self, request):
        articles = Article.objects.filter(active=True)
        serializer = ArticleSerializer(articles, many=True)  
        # queryset many=True, Model instance면 없어도 됨.
        return Response(serializer.data)
    
    def post(self, request):
        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) ArticleDetailAPIView

class ArticleDetailAPIView(APIView):
    
    def get_object(self, pk):
        article = get_object_or_404(Article, pk=pk)
        return article
    
    def get(self, request, pk):
        article = self.get_object(pk)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)
    
    def put(self, request, pk):
        article = self.get_object(pk)
        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)
    
    def delete(self, request, pk):
        article = self.get_object(pk)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

ArticleListCreateAPIView
ArticleDeatilAPIView


[Debug를 통한 코드 이해]

  • get_object()메소드를 통해서 pk값이 2인 Article 인스턴스를 리턴
  • self는 ArticleDetailAPIView object를 의미함
  • self.get_object()를 통해 pk값이 2인 Article 인스턴스를 article 변수에 담음.
> /Users/seungholee/udemy_drf/newsapi/news/api/views.py(38)get_object()-><Article: Joh...are publising>
(Pdb) article
<Article: John Titor First articled we are publising>

/Users/seungholee/udemy_drf/newsapi/news/api/views.py(43)get()
(Pdb) self
<news.api.views.ArticleDetailAPIView object at 0x10332c310>
(Pdb) self.get_object(2)
<Article: John Titor How to be CEO it's tough>

3. 임포트한 모듈 이해

1) APIView

REST 프레임워크는 Django View 클래스의 하위 클래스로 APIView 클래스를 제공한다. APIView 클래스는 View 클래스와 아래와 같은 점에서 다른 부분이 있다.

  • Request는 Django의 HttpRequest 인스턴스가 아닌 REST 프레임워크의 request인스턴스가 됨
  • Django의 HttpResponse가 아닌 REST 프레임워크의 Response를 반환
  • APIException 예외케이스가 발견되면 적절한 response으로 조정됨
  • Incoming request를 authenticate하고, 적절한 권한 혹은 throttle(제한사항) 체크한 실행함

이 외에는 APIView 클래스는 View클래스를 사용하는 것과 동일하다. Incoming request는 .get()이나 .post()와 같은 적절한 핸들러 메서드로 전달된다.

2) get_object_or_404)

get_object_or_404 함수는 Django Model을 첫번째 인자로 받고, get() 함수에서 전달될 임의의 값을 키워드 인자로 받는다. 만약, 객체가 존재하지 않으면 Http404(page not found)를 띄운다.

article = get_object_or_404(Article, pk=pk)

4. Function-based view와 비교

  • 엔드포인트 숫자와 각 엔드포인트의 역할은 완전히 동일하고, 동일한 결과물을 리턴함
  • 다만, 차이점은 1) Function-based에서는 하나의 View에서 if request.method로 GET/DELETE/UPDATE를 분리한다면, Class-based에서는 def get/delete/update를 명시해 주기에 가독성이 더 좋음
  • Function-based에서는 try/except로 특정 객체가 존재하지 않을 때 예외 처리를 했다면, Class-based에서는 def get_object(self, pk)로 처리를 함

5. 추가적인 설명

FBV가 아닌 CBV를 이용해 API views를 작성할 수도 있다. 공통적인 함수를 재사용할 수 있고, 코드를 DRY 정책을 따르도록 하는 강력한 패턴이다.

DRY: Don't Repeat Yourself.

전체적인 모양은 FBV 때와 크게 다르지 않다.
다만 각 메소드명을 HTTP 메소드명과 동일하게 사용하고 있다는 점이 가장 큰 차이점이고,
이전 포스트에서 언급했듯이 FBV에서 사용하던 @api_view 데코레이터 대신에 APIView 클래스를 상속받아 각 용도에 맞는 클래스를 만들었다.
@api_view 데코레이터에 HTTP 메소드를 전달하는 대신 동일한 이름의 메소드를 정의하는 것이다.

FBV 패턴에서는 각 URL마다 호출할 메소드를 직접 명시했다. (views.snippet_list())
CBV 패턴에서는 상속했던 APIView 클래스의 클래스 메소드인 as_view()를 호출한다.

as_view() 메소드는 해당 클래스의 인스턴스를 생성하고, dispatch() 메소드를 호출한다. 호출된 이 dispatch() 메소드가 요청을 분석해서 어떤 HTTP 메소드 요청인지 얻어낸다. POST 요청이면 앞에서 생성된 인스턴스 안에서 post라는 이름을 가진 메소드로 요청을 중계한다.

매칭되는 메소드가 정의되지 않은 경우, HTTPResponseNotAllowed 예외를 발생시킨다.

profile
박효영

0개의 댓글