APIView를 활용한 JSON 응답 뷰 만들기

guava·2022년 1월 14일
0
post-custom-banner

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.

1. APIView를 활용한 뷰 만들기


1.1. Serializer를 통한 뷰 처리


Form 처리와 유사한 방식으로 동작

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

# Views
"""주의) Form 생성자의 첫번째 인자는 data이지만, 
   Serializer 생성자의 첫번째 인자는 instance임. """
serializer = PostSerializer(data=request.POST)
if serializer.is_valid():
    return JsonResonse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)

1.2. DRF의 기본 CBV인 APIView


  • APIView 클래스 혹은 @api_view장식자 View에 여러 기본 속성을 부여한다.
    1. renderer_classes: 직렬화 class 다수
    2. parser_classes: 비직렬화 class 다수
    3. authentication_classes: 인증 class 다수
    4. throttle_classes: 사용량 제한 class 다수
    5. permission_classes: 권한 class 다수
    6. content_negotiation_class: 요청에 따라 적절한 직렬화/비직렬화 class를 선택하는 class
    7. metadata_class: 메타 정보를 처리하는 class
    8. versioning_class: 요청에서 API버전 정보를 감지하는 class

1.3. 각 옵션의 디폴트 값


  1. renderer_classes
    • rest_framework.renderers.JSONRenderer: JSON 직렬화
    • rest_framework.renderers.TemplateHTMLRenderer: HTML 페이지 직렬화
  2. parser_classes
    • rest_framework.parsers.JSONParser: JSON 포맷 처리
    • rest_framework.parsers.FormParser
    • rest_framework.parsers.MultiPartParser
  3. authentication_classes
    • rest_framework.authentication.SessionAuthentication : 세션에 기반한 인증
    • rest_framework.authentication.BasicAuthentication : HTTP Basic 인증
  4. throttle_classes
    • 빈 튜플 (디폴트로는 요청 횟수 제한이 설정 되어 있지 않음)
  5. permission_classes
    • rest_framework.permissions.AllowAny : 누구라도 접근 허용
  6. content_negotiation_class
    • rest_framework.negotiation.DefaultContentNegotiation
    • 같은 URL로의 요청이지만, JSON응답을 요구하는 것이냐 / HTML응답을 요구하는 것인지 판단
  7. metadata_class
    • rest_framework.metadata.SimpleMetadata
  8. versioning_class
    • None: API 버전 정보를 탐지하지 않겠다.
    • 요청 URL에서, GET인자에서, HEADER에서 버전 정보를 탐지하여 해당 버전의 API뷰가 호출되도록 합니다.

2. APIView와 @api_view


공식 튜토리얼: https://www.django-rest-framework.org/tutorial/3-class-based-views/

2.1. DRF의 2가지 기본 뷰


  1. APIView: 클래스 기반 뷰
  2. @api_view: 함수 기반 뷰를 위한 장식자

2.2. APIView


  • 하나의 CBV 이므로 하나의 URL만 처리 가능
    • ApiView → API를 좀더 표준화: Generic → Generic을 좀더 합친 것: ViewSet
    • ViewSet은 여러개의 URL의 대응이 가능하다.
  • 각 method(get, post, put, delete)에 맞게 멤버함수를 구현하면, 해당 method 요청이 들어올 때 호출. 호출 이전 단계(initial)에서 다음을 처리함
    1. 직렬화 / 비직렬화 처리(JSON 등)
    2. 인증 체크
    3. 사용량 제한 체크: 호출 허용량 범위인지 체크
    4. 권한 클래스 지정: 비인증/인증 유저에 대해 해당 API 호출을 허용할 것인지를 결정
    5. 요청된 API버전 문자열을 탐지하여, request.version에 저장

2.3. APIView 내 dispatch


APIView 구현체

def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        **[self.initial(request, *args, **kwargs)](https://www.notion.so/EP04-APIView-a08bfeb63a2c49dea8431398488ad049)**

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:  # 지원하는 method 목록에 있다면
            handler = getattr(self, request.method.lower(),  # 해당 멤머함수를 찾는다.
                              self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)  # 지원하는 method 멤버함수 호출

    except Exception as exc:
        response = self.handle_exception(exc)

    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response
def initial(self, request, *args, **kwargs):
    """
    Runs anything that needs to occur prior to calling the method handler.
    """
    self.format_kwarg = self.get_format_suffix(**kwargs)  # 어떤 format 응답?

    # Perform content negotiation and store the accepted info on the request
    neg = self.perform_content_negotiation(request)  # content 탐지
    request.accepted_renderer, request.accepted_media_type = neg

    # Determine the API version, if versioning is in use.
    version, scheme = self.determine_version(request, *args, **kwargs)  # version 탐지
    request.version, request.versioning_scheme = version, scheme

    # Ensure that the incoming request is permitted
    self.perform_authentication(request)  # 인증
    self.check_permissions(request)  # permission
    self.check_throttles(request)  # 요청 수 제한

2.4. API 구현 샘플


get, post등을 직접 구현 하였으나 직렬화, 인증 등은 APIView에 포함되어 있으므로 직접 구현하지 않아도 된다.

APIView (Class Based View)

list/create

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):
        qs = Post.objects.all()
        serializer = PostSerializer(qs, many=True)
        return Response(serializer.data)

    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)

# rest_framework/views.py
from django.views.decorators.csrf import csrf_exempt
class APIView(View):
    # …
    @classmethod
    def as_view(cls, **initkwargs):
        # …
        **return csrf_exempt(view)**  # 뷰가 [csrf_exampt 장식자로 이미 감싸져 있기에](https://github.com/encode/django-rest-framework/blob/de497a9bf12605b8b71bf7c21da57bc2a8238786/rest_framework/views.py#L144) POST요청에서 csrf token체크를 하지 않는다.

delete/update/delete

from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Post
from .serializers import PostSerializer
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)

@api_view 장식자 (Function Based View)

하나의 작업 만을 구현코자 할 때 @api_view를 쓰면 편리함.

list/create

from django.http import get_object_or_404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import Post
from .serializers import PostSerializer

**@api_view**(['GET', 'POST'])  # 인자를 비우면 매칭되지 않음
def post_list(request):

    if request.method == 'GET':
        serializer = PostSerializer(Post.objects.all(), 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)

detail/update/delete

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

**@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=request.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)

정리

  • generic 클래스 뷰를 사용하면 대체로 queryset과 serializer만 지정해주는 일관된 인터페이스를 갖는다.
  • 혹은 APIView를 상속받아서 get, post등의 메소드별 함수를 정의해준다.
  • 간단한 로직은 @api_view 데코레이터를 이용헤 함수형 뷰를 구현하는것도 좋다.
post-custom-banner

0개의 댓글