ViewSet과 Router

최동혁·2022년 12월 10일
0

DRF

목록 보기
7/19

ViewSet

단일 리소스에서 관련있는 View들을 단일 클래스에서 제공

  • list/create/detail/update/partial_update/delete 등의 멤버 함수로 구현
  • 보통 list/create을 위한 하나의 URL
  • detail/update/partial_update_delete를 위한 하나의 URL
  • 총 2개의 URL이 필요하다.
  • 최소 2개의 Class 기반 View가 필요한데, ViewSet은 2개의 대한 구현을 하나의 단일 클래스에서 지원을 한다.
# views.py
from rest_framework import viewsets
from rest_framework.response import Response

class PostViewSet(viewsets.ViewSet):
    def list(self, request):
        queryset = Post.objects.all()
        serializer = PostSerializer(queryset, many=True)
        return Response(serializer.data)
        
    def retrieve(self, request, pk):
        queryset = Post.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = PostSerializer(user)
        return Response(serializer.data)
  • viewsets.ViewSet을 이용해 하나의 단일 클래스 내에서 모든 멤버 함수들을 구현할 수 있다.
  • 혹은 ModelViewSet을 통하면, Model과 serializer로부터 list나 retrieve 같은 function들이 자동으로 만들어지게 할 수 있다.
  • 실제 URL은 두개이기 때문에 list에 관한 get 요청 URL이 오면 list 함수가 호출되게 할 수 있다.
  • 특정 detail url에서 get 요청이 오게 되면 retrieve 함수를 호출하게 할 수 있다.
# urls.py
router = DefaultRouter()
router.register('post', PostViewSet, basename='post')
router.urls
  • router.register 할 때 ViewSet을 등록해주면 최소 2개 url에 대한 pattern이 생기게 된다.
  • router.urls 하면 해당 url의 list가 나오게 된다.
  • 보통 include(router.urls)를 이용해서 사용한다.

Post 리스소에 대한 2개의 URL

아래 코드 역시 정형화된 패턴

-> ModelViewSet을 통해 간결하게 구현할 수 있다.

from rest_framework import generics

class PostListAPIView(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
class PostDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
  • 이 두가지의 클래스가 별도의 URL에 매핑되어 있는데, 이것들을 별도로 합쳐서 ModelViewSet으로도 동일한 역할을 하는 View를 만들 수 있다.

ModelViewSet

2가지 ModelViewSet

viewsets.ReadOnlyModelViewSet

  • get 요청에만 반응하는 ModelViewSet이다.
  • 조회 목적

    list 지원 -> 1개의 URL
    detail 지원 -> 1개의 URL

viewsets.ModelViewSet

  • 조회를 포함한 여러 기능 지원

    list/create 지원 -> 1개의 URL
    detail/update/partial_update/delete 지원 -> 1개의 URL

URL Patterns에 매핑하기

from rest_framework import viewsets

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

개별 View를 만들 수도 있다.

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

post_detail = PostViewSet.as_view({
	'get': 'retrieve',
})
  • 어떠한 method가 왔을 때, 어떠한 함수를 호출해주겠다라는 의미

    get method 요청이 오면, list 함수를 호출
    get method 요청이 오면, retrieve 함수를 호출

  • 그래서 각각 다른 urls.py에 하나하나 다 써서 매핑해야됨.

Router 통해 일괄적으로 urlpatterns에 등록할 수도 있다.

from rest_framework.routers import DefaultRouter

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

urlpatterns = [
	path('', include(router.urls)),
]
  • 위의 방식대로 하면, 하나하나 등록 안해도 알아서 다 등록됨.
  • 근데 router.urls에는 좀 더 추가적인 기능이 있다.
  • 예를 들면 1.json을 하면 1에 관련된 데이터들이 json 형식으로 보여지게 된다.
  • 1.api로 쓰면 html 형식으로 보여줌.

Router

ReadOnlyModelViewSet과 ModelViewSet에 대해서 동일한 URLPattern 리스트가 생성

  • list route
  • detail route
from rest_framework.routers import DefaultRouter

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

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

router.register를 통해 등록되는 urls 리스트

[
    <URLPattern '^post/$' [name='post-list']>,
    <URLPattern '^post\.(?P<format>[a-z0-9]+)/?$' [name='post-list']>,
    <URLPattern '^post/(?P<pk>[^/.]+)/$' [name='post-detail']>,
    <URLPattern '^post/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='post-detail']>,
    <URLPattern '^$' [name='api-root']>,
    <URLPattern '^\.(?P<format>[a-z0-9]+)/?$' [name='api-root']>
]
  • 2번째에 format은 위에서 봤듯이, .json과 .api를 뜻한다.
  • 4번째에 format도 동일하다.
  • 그리고 5번째는 현재 api에 등록되어 있는 목록을 보여준다.
  • 그리소 6번째도 format이 있다. .json과 .api

그래서 ViewSet을 쓰게 된다면 router를 이용해 쓰는 것을 추천한다.

  • router.register에서 resource name을 붙여서 사용할 수 있다.
  • 위에서는 post라고 썼는데, 이 네임이 router.urls 리스트에 post로써 들어가게 된다.

ViewSet에 새로운 EndPoint 추가하기 (1/2)

공개 게시글만 요청, 공개 게시글로 변경 요청

from rest_framework.decorators import action

class PostModelViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
    @action(detail=False, methods=['GET'])
    def public(self, request):
        qs = self.queryset.filter(is_public=True)
        serializer = self.get_serializer(qs, many=True)
        return Response(serializer.data)
        
    @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)
  • action이라는 데코레이터가 있다.

  • 위에 함수에서 public url로 접근하면 공개된 게시글만 불러온다.

    • URL Reverse 명 : basename-함수명 (언더바는 하이픈으로 변경) 즉, post-public이 URL이 된다.
  • set_public은 public으로 변경해주는 함수.

  • 여기서 detail=False는 공개된 게시글들을 전부 불러오는것이기에 detail이 아닌 list이다.

  • set_public은 특정 게시글을 공개 게시글로 바꾸는 것이기 때문에 detail=True이다.

  • 그래서 detail을 통해 list쪽 url을 탈 것이냐, detail쪽 url을 탈 것이냐 판가름이 난다.

ViewSet에 새로운 EndPoint 추가하기 (2/2)

생성된 URL Patterns 리스트

[
    ...
    <URLPattern	'^post/public/$' [name='post-public']>,
    <URLPattern	'^post/public\.(?P<format>[a-z0-9]+)/?$' [name='post-public']>,
    ...
    <URLPattern	'^post/(?P<pk>[^/.]+)/set_public/$' [name='post-set-public']>,
    <URLPattern	'^post/(?P<pk>[^/.]+)/set_public\.(?P<format>[a-z0-9]+)/?$' [name='post-set-public']>,
    ...
]

HTTPie를 활용한 요청의 예

쉘> http PATCH http://도메인/post/10/set_public/
- 위의 함수를 적용시켰을때 해당 도메인으로 이동하면 공개 게시글로 적용
쉘> http PATCH http://도메인/post/10/ is_public=true
- 만약 위의 함수를 적용시키지 않으면 위의 명령어를 쉘에 작성해서 직접 공개 게시글로 만들어줄 수 있다.

  • PUT 요청 : 반영할 모든 필드값을 지정
  • PATCH 요청 : 변경할 값만을 지정
profile
항상 성장하는 개발자 최동혁입니다.

0개의 댓글