django rest framework

  • Views : Class-based Views(CBV) / Function Based Views(FBV)
  • Generic views : Mixins / generics APIView
  • Viewsets

@api_view, APIView -> Generic views -> viewsets 으로 갈수록 코드를 간략하게 API로 만들 수 있다.

APIView 클래스와 api_view 장식자

APIView 와 api_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 가 호출되면 위에서 봤던 설정에 맞춰 처리가 이루어집니다.
    • 직렬화/비직렬화
    • 인증 체크
    • 사용량 제한 체크
    • 권한 체크
    • 요청한 버전 체크
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from django.contrib.auth.models import User

class ListUsers(APIView):
    """
    View to list all users in the system.

    * Requires token authentication.
    * Only admin users are able to access this view.
    """
    authentication_classes = [authentication.TokenAuthentication]
    permission_classes = [permissions.IsAdminUser]

    def get(self, request, format=None):
        """
        Return a list of all users.
        """
        usernames = [user.username for user in User.objects.all()]
        return Response(usernames)
  • 실습
# 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)
# serializers.py

from rest_framework.serializers import ModelSerializer
from .models import Post

class PostSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'
# 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)
        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)

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)
# urls.py

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 에 대해서 사용하는 장식자

  • 실습
# 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)
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):
  '''
  코드 중략
  '''

@api_view(['GET', 'PUT', 'DELETE'])
def article_detail_api_view(request, pk):
  # try, except 대신 get_object_or_404를 import 해서 쓸 수도 있다.
  try:
    # pk(인스턴스의 id)값을 받아 어떤 인스턴스인지 특정
    # url slug로 pk값을 받도록 urls.py에서 설정해준다.
    article = Article.objects.get(pk = pk)
  # 받은 pk값으로 조회했을 때 해당하는 인스턴스가 없다면 출력할 에러 코드와 메시지를 설정한다.
  except Article.DoesNotExist:
    return Response({'error' : {
      'code' : 404,
      'message' : "Article not found!"
    }}, status = status.HTTP_404_NOT_FOUND)
  # 만약 article이 존재한다면,
  if request.method == 'GET':
    serializer = ArticleSerializer(article)
    return Response(serializer.data)
  elif request.method == 'PUT':
    serializer = ArticleSerializer(article, data = request.data)
    # request에서 data를 받았으니 .is_valid() 필수
    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()
    # 인스턴스를 삭제한 뒤에는 204 NO CONTENT를 리턴
    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),
]

generics APIView

  1. Basic settings
  • 다음 속성들을 통해 View를 컨트롤함
    • queryset : View에서 객체를 반환하는 데 사용해야 하는 쿼리셋. 반드시 1) queryset 속성을 설정하거나, 2) get_queryset() 메서드로 override해서 사용해야 함.
    • serializer_class : 입력된 값을 validate하거나 deserialize하거나, 출력값을 serialize할 때 사용하는 serializer 클래스. 일반적으로 이 속성을 설정하거나 get_serializer_class()메소드로 override해서 사용해야 함
    • lookup_field : 개별 모델 인스턴스의 object 조회를 수행 할 때 사용해야하는 모델 필드. 기본값은 'pk'임. 하이퍼링크 된 API에 custom 값을 사용해야 하는 경우 API views와 serializer 클래스가 lookup필드를 설정해야 함.
  1. Pagination
  • 다음 속성을 통해 리스트뷰에서 페이지네이션을 컨트롤하게 된다.
    • pagination_class : 리스트 결과들을 페이지네이션할 때 사용되는 클래스임. 디폴트는 DEFAULT_PAGINATION_CLASS (rest_framework.pagination.PageNumberPagination 모듈안에) 세팅으로 결정됨.
  1. Filtering
  • filter_backends : A list of filter backend classes that should be used for filtering the queryset. Defaults to the same value as the DEFAULT_FILTER_BACKENDS setting.

Mixins 상속

APIView 는 위에서 봤듯이 각 request method 마다 직접 serializer 처리를 해주었습니다.
하지만 이러한 부분들은 많이 사용되므로 여러 serializer 에 대해서 중복이 발생합니다.
따라서 rest_framework.mixins 에서는 이러한 기능들이 미리 구현이 되어 있습니다.

단, Mixin 클래스에 존재하는 메소드나 속성을 상속받는 클래스에서 사용할 경우 믹스인 클래스의 메소드가 오버라이딩되어 의도하지 않게 작동할 수 있으니 주의

  • CreateModelMixin
    • 모델 인스턴스를 생성하고 저장하는 역할을 하는 믹스인
    • .create(request, *args, **kwargs) 메소드로 호출하여 사용
    • 성공 시, 201 Created 리턴
    • 실패 시, 400 Bad Request 리턴
  • ListModelMixin
    • Queryset을 리스팅하는 믹스인
    • .list(request, *args, **kwargs) 메소드로 호출하여 사용
    • GenericAPIView의 self.filter_queryset, self.get_queryset, self.get_serializer 등의 메소드를 활용해 데이터베이스에 저장되어 있는 데이터들을 목록 형태로 response body로 리턴
    • 성공 시, 200 OK response 리턴
  • RetrieveModelMixin
    • 존재하는 모델 인스턴스를 리턴해 주는 믹스인
    • .retrieve(request, *args, **kwargs) 메소드로 호출하여 사용
    • 성공 시, 200 OK response 리턴
    • 실패 시, 404 Not Found 리턴
  • UpdateModelMixin
    • 모델 인스턴스를 수정하여 저장해 주는 믹스인
    • .update(request, *args, **kwargs) 메소드로 호출하여 사용
    • 부분만 변경하고자 할 경우, .partial_update(request, *args, **kwargs)메소드를 호출하여야 하며, 이 때 요청은 HTTP PATCH requests여야 함
    • 성공 시, 200 OK response 리턴
    • 실패 시, 404 Not Found 리턴
  • DestroyModelMixin
    • 모델 인스턴스를 삭제하는 믹스인
    • .destroy(request, *args, **kwargs) 메소드로 호출하여 사용
    • 성공 시, 204 No Content 리턴
    • 실패 시, 404 Not Found 리턴

queryset 과 serializer_class 를 지정해주기만 하면 나머지는 상속받은 Mixin 과 연결해주기만 하면 됩니다.

  • 실습
# views.py

from rest_framework.response import Response
from rest_framework import generics
from rest_framework import mixins
from .models import Post
from .serializers import PostSerializer

class PostListMixins(mixins.ListModelMixin, mixins.CreateModelMixin,generics.GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request)

    def post(self, request, *args, **kwargs):
        return self.create(request)

class PostDetailMixins(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.delete(request, *args, **kwargs)
# urls.py

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

urlpatterns = [
    # Mixin
    path('mixin/post/', views.PostListMixins.as_view()),
    path('mixin/post/<int:pk>/', views.PostDetailMixins.as_view()),
]

Concrete View Classes

Mixin 을 상속함으로서 반복되는 내용을 많이 줄일 수 있었습니다. 하지만 여러 개를 상속해야 하다보니 가독성이 떨어집니다. 다행히도 rest_framework 에서는 저들을 상속한 새로운 클래스를 정의해놨습니다.

총 9개의 클래스로 다음과 같습니다.

  • generics.CreateAPIView : 생성
  • generics.ListAPIView : 목록
  • generics.RetrieveAPIView : 조회
  • generics.DestroyAPIView : 삭제
  • generics.UpdateAPIView : 수정
  • generics.RetrieveUpdateAPIView : 조회/수정
  • generics.RetrieveDestroyAPIView : 조회/삭제
  • generics.ListCreateAPIView : 목록/생성
  • generics.RetrieveUpdateDestroyAPIView : 조회/수정/삭제
# rest_framework/generics.py

class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)
  • 실습
# views.py

from rest_framework import generics
from .models import Post
from .serializers import PostSerializer

class PostListGenericAPIView(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class PostDetailGenericAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
# urls.py

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

urlpatterns = [
    # Mixin
    path('mixin/post/', views.PostListGenericAPIView.as_view()),
    path('mixin/post/<int:pk>/', views.PostDetailGenericAPIView.as_view()),
]

ViewSet

ViewSet 은 CBV 가 아닌 헬퍼클래스로 두 가지 종류가 있습니다.

  • viewsets.ReadOnlyModelViewSet : 목록 조회, 특정 레코드 조회
  • viewsets.ModelViewSet : 목록 조회, 특정 레코드 생성/조회/수정/삭제
  • 실습
# views.py
# post 모델

from .models import Post
from .serializers import PostSerializer
from rest_framework import viewsets

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

Router 를 통해서 하나의 url 로 처리가 가능

#. urls.py

from django.urls import path, include
from . import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('viewset',views.PostViewSet)

urlpatterns = [
    path('',include(router.urls)),
]
728x90

'Study > Django' 카테고리의 다른 글

[Django] code formatting black 설치 및 적용  (0) 2021.05.20
[Django] 환경 변수 분리하기 django-environ  (0) 2021.05.18
[Django] settings.py - cors, static path  (0) 2021.05.11
[Django] Serializers  (0) 2021.05.11
[Django] models.py  (0) 2021.05.10

+ Recent posts