본문 바로가기

web/Django_rest_framework

django Rest framework APIView / Mixins / Viewset

- 아래의 코드는 python2.7 , restframework 3.6.4 의 내용입니다. 최신버전의 내용과 다른 부분이 있습니다.

1.APIView

APIView -> 클래스 기반 (CBV : class base view)

@api_view -> 함수기반 (FBV : function base view)

 APIView는 여러가지 기본 설정을 제공합니다. ( 직렬화, 인증, 사용량 제한, 권한 등 )

 CVB로 작성시 http 메소드에 해당하는 함수를 만들어줘야 합니다. 해당 함수 명은 지정되어 있으며, http의 메소드 명과 동일합니다. (함수명이 틀릴 경우 해당 메소드는 사용할수 없는 메소드로 간주 합니다. )

 url에는 파라미터인자를 선언하므로 인자 pk를 받아야 하는 get/put/delete가 하나의 클래스, 파라미터가 필요 없는 gets/post가 하나의 클래스로 만든후 각각의 url로 선언 해야 합니다.

 

- url과 views의 클래스가 두개로 나눠진 이유는 pk를 파라미터로 받기 위함입니다. 물론 router()로 url로 설정하시면 하나로 할수 있습니다. (router에 대한 설명은 하단에 있습니다. )

# urls.py

url(r'^post/(?P<pk>[\d+]+)/', views.PostDetailAPIView.as_view()),
url(r'^post/', views.PostAPIView.as_view()),
# views.py

from django.http import JsonResponse
from rest_framework.views import APIView

class PostDetailAPIView(APIView):
    def get(self, request, pk):
        return JsonResponse({}, status=status.HTTP_200_OK)

    def put(self, request, pk):
        return JsonResponse({}, status=status.HTTP_200_OK)

    def delete(self, request, pk):
        return JsonResponse({}, status=status.HTTP_200_OK)

class PostAPIView(APIView):
    def get(self, request):
        return JsonResponse({}, status=status.HTTP_200_OK)

    def post(self, request):
        return JsonResponse({}, status=status.HTTP_200_OK)

 

2. Mixins 

APIView는 메소드마다 쿼리와 시리얼라이져를 선언해줘야했습니다. 

쿼리와 시리얼라이져의 중복이 많으므로 공통된 것을 하나로 묶기 위해서 mixins를 사용합니다. 

각각의 믹스인은 메소드에 해당합니다. 

 

RetrieveModelMixin : Get

ListModelMixin : Gets

CreateModelMixin : Post

UpdateModelMixin : Put

DestroyModelMixin : delete

  

PostListMixins 클래스는 하위파라미터 없이 실행할수 있는 gets/post를 

PostDetailMixins 클래스는 pk를 받아 처리하는 get/put/delete를 각각의 믹스인으로 묶은것이다. (pk를 받는 메소드와 받지 않는 메소드)

 

# urls.py

url(r'^post/(?P<pk>[\d+]+)/', views.PostDetailMixins.as_view()),
url(r'^post/', views.PostListMixins.as_view()),

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

3. Generics APIView 

Mixin은 하나의 클래스에 너무 많은 상속을 합니다. 공통된것끼리 더 줄이기 위해서 generics를 사용합니다. 

generics.CreateAPIView : 생성

generics.ListAPIView : 목록

generics.RetrieveAPIView : 조회

generics.DestroyAPIView : 삭제

generics.UpdateAPIView : 수정

generics.RetrieveUpdateAPIView : 조회/수정

generics.RetrieveDestroyAPIView : 조회/삭제

generics.ListCreateAPIView : 목록/생성

generics.RetrieveUpdateDestroyAPIView : 조회/수정/삭제

# urls.py

url(r'^post/(?P<pk>[\d+]+)/', views.PostDetailGeneric.as_view()),
url(r'^post/', views.PostListGeneric.as_view()),

# views.py

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

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

class PostDetailGeneric(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

 

4. Viewset

Generics APIView를 통해 많은 부분이 간소화 되었지만 그래도 query set / serializer을 파라미터(Pk)에 따라 두개의 클래스를 선언해 줘야 합니다. 

이것도 귀찬으니 하나로 합시다!!

 

viewsets.ReadOnlyModelViewSet : 목록 조회, 특정 레코드 조회     (get만 가능)

viewsets.ModelViewSet : 목록 조회, 특정 레코드 생성/조회/수정/삭제 전부 자동으로 만들어줌.

Viewsets.ViewSet : 자동으로 만들지마! 내가 다 만들께 

# views.py

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

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

하지만 viewset을 이용하려면 url을 Router()를 사용해야 합니다. 

만일 .as_set()를 사용하면 메소드 하나하나 선언해 줘야 합니다. 

 

# urls.py

from rest_framework.routers import DefaultRouter

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

urlpatterns = [
    path('',include(post_router.urls)),
]

 

 

번외) view에 따라 url을 어떻게 선언해야 하는가? 

- 아래의 코드는 python2.7 , restframework 3.6.4 의 내용입니다. 최신버전의 내용과 다른 부분이 있습니다.

 

위에서 봤던 viewset과 APIView는 각각 상위 상속이 다르기 때문에 url의 선언도 약간식 차이가 있습니다. 

url에서 선언방식 as_view / router 와 view에서 클래스 선언방식 viewset / APIView 총 4가지 조합 방식에 대해 알아봅시다.

 

1. Url as_view() + view viewset

 .as_view()에 해당 method를 지정 해야 줘야 작동합니다. (함수의 이름은 커스텀이 당연히 가능합니다)
-> views.MoreUsersView.as_view({'get': 'get', 'put':'update’})

https://www.django-rest-framework.org/api-guide/viewsets/

url(r'^more/(?P<user_id>[\d+]+)/', views.MoreUsersView.as_view({"get": "get", "put": "put"}), name='more'),

class MoreUsersView(viewsets.ViewSet):
    queryset = UniqueUser.objects.all()

    def get(self, request, user_id):
        return JsonResponse({}, status=status.HTTP_200_OK)

    def put(self, request, user_id=None):
        return JsonResponse({}, status=status.HTTP_200_OK)

 

2. Url as_view + view APIView

APIView 일 경우엔 view안의 함수 명이 메소드 명과 일치해야 합니다.
그렇지 않으면 해당 메소드를 사용할 수 없습니다. Put 요청일 경우 
  def update(self, request, pk): <- 동작 안함
  def put(self, request, pk): <- 동작

 

당연히 리스트의 경우 url이 다르므로(pk를 파라미터로 받지 않으므로) url을  따로 지정 해줘야 합니다.

url(r'^more/(?P<user_id>[\d+]+)/', views.MoreUsersView.as_view(), name='more'),

class MoreUsersView(APIView):
    queryset = User.objects.all()

    def get(self, request, user_id):
        return JsonResponse({}, status=status.HTTP_200_OK)

    def put(self, request, user_id=None):
        return JsonResponse({}, status=status.HTTP_200_OK)

View 함수 이름 커스텀은 @api_view()로 CBV가 아닌 FBV로 해야 합니다.

url(r'^more/(?P[\d+]+)/', views.get_put_custom, name='more-get’),

@api_view(['GET', 'PUT'])
def get_put_custom(request, user_id):
    return JsonResponse({}, status=status.HTTP_200_OK)

 

3. Url router + view viewset

# urls.py
more_router = DefaultRouter()
more_router.register(r'', views.MoreUsersView)

url(r'^more/', include(more_router.urls)),

# views.py

class MoreUsersView(viewsets.ViewSet):
    # get 
    def retrieve(self, request, pk):
        return JsonResponse({}, status=status.HTTP_200_OK)

    # put
    def update(self, request, pk):
        return JsonResponse({}, status=status.HTTP_200_OK)

 

Router + ViewSet일 경우엔 view 내의 함수 이름을 router에서 동작하는 함수명으로만 해야 합니다.
이름이 틀리면 해당 함수는 동작하지 않습니다. (함수명 확인 https://www.django-rest-framework.org/api-guide/routers/#defaultrouter)

만일 정말!! 함수명을 변경하고 싶다면 rest_framework.decorators의 detail_route / list_route를 써야 하며, (최신 버전에서는 action)

from rest_framework.decorators import detail_route
class MoreUsersView(viewsets.ViewSet):
    @detail_route(methods=['post', 'delete', 'put'])
    def post_del_put_custom(self, request, pk=None):
        return JsonResponse({}, status=status.HTTP_200_OK)

접근시 url이 다음과 같아야 한다. 
url/{pk}/함수명/
예시 http://localhost/more/2/post_del_put_custom/

마지막에 작성한 함수명의 이름이 url에 들어가야 접근이 가능합니다.

아래의 명세를 참조하세요.
https://www.django-rest-framework.org/api-guide/routers/#defaultrouter
https://www.django-rest-framework.org/api-guide/viewsets/

 

4. Url router + view APIView

router는 viewset에서만 동작합니다. APIView에서는 동작하지 않습니다.

 

 

번외

Router() 로 url을 설정할 경우 id는 무조건 pk로 받아야 합니다. (아니면 에러가 난다)

router에서는 해당 아이디를 pk로 오버라이딩 하며, 뷰에서는 무조건 해당 파라미터로 받아야 하기 때문입니다.

# urls.py
more_router = DefaultRouter()
more_router.register(r'', views.MoreUsersView)


url(r'^more/', include(more_router.urls)),

# views.py
class MoreUsersView(viewsets.ViewSet):
    def get(self, request, pk):
        print("pass", pk)