수학과의 좌충우돌 프로그래밍

[Django] ViewSet 과 Router 본문

웹프로그래밍/DRF

[Django] ViewSet 과 Router

ssung.k 2019. 12. 28. 17:14

Django-APIView-Mixins-genericsAPIView-ViewSet을-알아보자

 

[Django] APIView, Mixins, generics APIView, ViewSet을 알아보자

django 에서는 view 를 통해서 HTTP 요청을 처리합니다. view에서 이를 처리하는 방법은 다양합니다. FBV(함수기반뷰), CBV(클래스기반뷰) 를 통해서도 API 를 만들 수 있지만 rest_framework 는 보다 쉽게, 효율..

ssungkang.tistory.com

저번 포스팅에서 ViewSet 에 대해서 간단히 알아보았습니다. 이번에는 좀 더 자세히 알아보도록 하겠습니다.

ViewSet

저번 포스팅에서도 언급했지만 ViewSet 은 다른 것들과 좀 달랐습니다.

일반적인 CBV 가 아니기 때문에 as_view 를 통해서 뷰를 만들지 않고 router를 사용했습니다.

as_view 를 사용하지 않는 이유는 ViewSet 은 하나의 뷰가 아닌 set, 여러 개의 뷰를 만들 수 있는 확장된 CBV이기 때문입니다.

사실 ViewSet 도 as_view를 사용하여 각각의 뷰를 만들어줄 수 있습니다.

.as_view({
	'http method': '함수'
})

 

ViewSet 은 두 가지 종류로 이루어져 있습니다. 두 종류 모두 자체적인 구현은 없고 Mixin 을 적절히 상속받아 여러 기능을 수행하게 됩니다.

  • viewsets.ReadOnlyModelViewSet

    • 목록

      • mixins.ListModelMixin : list() 함수
    • 특정 레코드

      • mixins.RetrieveModelMixin : retrieve() 함수
  • viewsets.ModelViewSet

    • 목록

      • mixins.ListModelMixin : list() 함수
    • 특정 레코드

      • mixins.RetrieveModelMixin : retrieve() 함수
    • 레코드 생성

      • mixins.CreateModelMixin : create() 함수
    • 레코드 수정

      • mixins.UpdateModelMixin : update() 함수, partial_update() 함수

      partial_update()

      부분적인 필드값만 받아 수정이 가능하며 request method 중 Fetch 와 대응된다.

    • 레코드 삭제

      • mixins.DestroyModelMixin : destroy() 함수

 

ModelViewSetReadOnlyModelViewSet 의 기능들을 함축하므로 ModelViewSet 에 대한 간단한 예시를 살펴보겠습니다.

우선 Post 모델을 정의해주겠습니다.

# models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    is_public = models.BooleanField(default=False)
    create_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

 

이에 대해서 Serializer 를 만들어주고

# Serializers.py

from rest_framework.serializers import ModelSerializer
from .models import Post

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

 

ViewSet 을 정의해줍니다. 위에서 알아봤던 ModelViewSet 의 모든 기능을 다 연결시켜주었습니다.

# views.py

from django.shortcuts import render
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
from .models import Post
from .serializers import PostSerializer

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

post_list = PostViewSet.as_view({
    'get': 'list',
    'post': 'create',
})

post_detail = PostViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy',
})

 

마지막으로 url 만 연결해주면 아래와 같은 결과 화면을 볼 수 있습니다.

# urls.py

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

urlpatterns = [
    path('post/', views.post_list),
    path('post/<int:pk>/', views.post_detail),
]

 

 

 

Router

이 역시 저번 포스팅에서 봤듯이 URL 매핑을 편리하게 할 수 있습니다. 기존에는 as_view 를 통해 각 request method 마다 대응되는 함수를 연결시켜주었다면 router 는 이를 알아서 연결시켜줍니다.

디폴트 매핑은 위에서 본 것과 동일합니다.

  • list route

    • url : /prefix/

    • name : {model name}-list , 단 model name 은 소문자입니다.

      'get': 'list' 'post': 'create'

  • detail route

    • url : /prefix/pk/

    • name : {model name}-detail

      'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy',

 

위의 urls 를 아래와 같이 수정하였습니다. 이 때 prefix 자리에는 post 가 쓰였고 이에 따라 url이 결정됩니다.

# urls.py

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

router = DefaultRouter()
router.register(r'post',views.PostViewSet)

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

 

더 이상 as_view 를 통해 단일 뷰를 뽑아낼 필요가 없어졌습니다.

# views.py

from django.shortcuts import render
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
from .models import Post
from .serializers import PostSerializer

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

 

다음과 같이 Router 를 사용하게 되면 Api Root 를 제공해줍니다. 해당 app 에 대한 root url 로 갈 경우 아래 이미지와 같이 Router 에 등록된 ViewSet을 확인 할 수 있습니다.

 

Api Root

ViewSet 더 알아보기

디폴트 매핑의 경우, list route 에 대해서 2개, detail route 에 대해서 4개에 대해서 매핑을 해주게 됩니다. 하지만 실제로는 6개보다 더 많은 경우가 비일비재합니다. 따라서 추가적으로 매핑을 하는 방법에 대해서 알아보도록 하겠습니다.

추가적으로 매핑하는 방법은 간단합니다. ViewSet 의 맴버함수로 구현한 후 decorator 를 붙여주면 됩니다. 이 때 URL 은 Router가 알아서 결정해줍니다.

decorator action

매핑하기 위해서 action decorator 를 사용하는데 이에 대해서 알아보도록 하겠습니다.

  • import 경로

    from rest_framework.decorators import action
    
  • detail

    action 은 첫번째 인자로 detail 값을 받습니다. Boolean 으로서 True 일 경우, pk 값을 지정해줘야하는 경우에 사용하고 False일 경우, 목록 단위로 적용하게 됩니다.

  • methods

    request method 를 지정해줄 수 있습니다. 디폴트 값은 get 으로 자세한 사용방법은 아래 예시를 통해 보겠습니다.

 

이 때 detail 값에 따라 Router가 URL 을 결정하는 방법이 다릅니다.

  • detail=True

    • url : /prefix/{pk}/{function name}/
    • name : {model name}-{function name}
  • detail=False

    • url : /prefix/{function name}/
    • name : {model name}-{function name}

이 때 모든 이름은 소문자이며, function name의 언더바(_)는 하이픈(-) 으로 교체됩니다.

 

is_public 값이 True 인 레코드만 필터링하는 public_list 와 특정 레코드의 is_public 을 True로 바꿔주는 set_public 을 정의하였습니다.

# views.py

from django.shortcuts import render
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer

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

    # url : post/public_list/
    @action(detail=False)
    def public_list(self, request):
        qs = self.queryset.filter(is_public=True)
        serializer = self.get_serializer(qs, many=True)
        return Response(serializer.data)
    
    # url : post/{pk}/set_public/
    @action(detail=True, methods=['patch'])
    def set_public(self, request, pk):
        instance = self.get_object()
        instance.is_public = True
        instance.save()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

 

 

Comments