DRF ViewSet과 Router

2022. 7. 13. 14:24강의 정리/Django REST Framework

반응형

REST의 규칙에 우리는 맞춰 코딩을 하고있음을 인지하는게 매우 중요하다.

 

 

ViewSet은 REST의 반복적인 코딩 패턴을 줄여준다.


우리는 일반적으로 REST API를 구현할 때 모델을 기준으로 List와 Detail URL에 대한 API를 구현한다.

 

이 때 List API는 GET, POST 메소드를 구현하며 Detail API 는 GET, PUT, DELETE메소드를 구현한다.

 

이 각 2개의 URL별로 모두 5개의 메소드 구현을 REST의 반복되는 일관된 패턴으로 볼 수도 있을 것이다.

 

DRF에서는 모델을 기준으로 하나의 ViewSet으로 묶어서 위에서 언급한 패턴을 한방에 구현할 수 있다. ViewSet에 queryset과 serializer를 지정만 해주고 Router클래스로 url에 추가만 해주면 된다.

 

 


ViewSet


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

  • 위의 말을 쉽게 설명 하자면 RESST API는 일반적으로 2개의 URL이 필요하다. 
    • list/create, detail/update/partial_update/delete로 나눠지는 2개의 클래스 기반 뷰가 필요하다.
  • 그러나 ViewSet에서는 2개의 구현을 단일 클래스에서 지원해준다.
# https://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/
from rest_framework import viewsets
from rest_framework.response import Response

class PostViewSet(viewsets.ViewSet):
    def list(self, request):  # list
        queryset = Post.objects.all()
        serializer = PostSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk):  # detail
        queryset = Post.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = PostSerializer(user)
        return Response(serializer.data)

# https://www.django-rest-framework.org/api-guide/routers/
router = DefaultRouter()
router.register('post', PostViewSet, basename='post')  # 2개의 URL을 만들어준다.
router.urls

# urls에 다음과 같이 추가한다. -> path('', include(router.urls)
  • 위는 메소드들을 직접 구현한 것이고, ModelViewSet을 상속받으면 메소드가 전부 구현이 되어있다.

 

# https://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/
from rest_framework import ModelViewSet
from rest_framework.response import Response

class PostViewSet(ModelViewSet):
	queryset = Post.objects.all()
    serializer_class = PostSerializer
  • ModelViewSet에서는 쿼리셋과 Serializer만 적으면 된다. // 기본 메소드가 모두 구현되어있기에

 

 

 

 

 


 

ModelViewSet


2가지 ModelViewSet이 존재한다.

 

 

Viewsets.ReadOnlyModelViewSet -> GET 요청(조회)에만 반응하는 ModelViewSet

  • list 지원 → 1개의 URL
  • detail 지원 → 1개의 URL

 

Viewsets.ModelViewSet -> GET뿐만 아니라 모든 요청에 반응하는  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

 

 

필요한 method만 뽑아서 개별 View를 만들수도있고

# PostViewSet으로부터 필요한것만 뽑아 쓰기

#'입력받은 mehtod' : '출력할 함수' 

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

# urls에 하나하나 직접 path를 다 써야한다.

또 다른 예시

message_list = MessageViewSet.as_view({
        'get': 'list',
        'post': 'create',
    })

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

urlpatterns = [
    path('/message', message_list),
    path('/message/<int:pk>', message_detail),
]

 

 

 

 

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

# Router를 통해 일괄등록을 하면 추가 기능이 있다. 
# http://localhost:8000/post/1.api, http://localhost:8000/post/1.json 등의 포맷 인자(api, json)를 추가로 url_patterns에 추가된다. 포맷 인자로 요청 시 요청에 따라 json, api응답이 온다. 아래 Router 참조

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('post', views.PostViewSet)  # 'post': URL에 사용될 리소스 이름이다. 보통은 모델명 사용

urlpatterns = [
    path('', include(router.urls)),
]
  • http://localhost:8000/post/1.api, http://localhost:8000/post/1.json 등의 포맷 인자(api, json)를 추가로 url_patterns에 추가된다. 포맷 인자로 요청 시 요청에 따라 json, api(DRF html)응답이 온다. 

 

 


 

 

Router


ReadOnlyModelViewSet과 ModelViewSet에 대해서 동일한 URL Pattern 리스트가 생성된다.

  • list route
  • detaul route

 

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('post', views.PostViewSet) # 'post'가 url의 prefix가 된다.

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



# 아래 URL 패턴들은 위 router 추가시 자동으로 등록되는 주소들이다.
[
    <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']>  
# api-root : 현 Router에 등록된 ViewSet내역을 조회
]

 

 


 

ViewSet에 새로운 EndPoint 추가하기


EndPoint란

  • 특정 URL을 호출하게 되면, 생성되고
  • 하나의 endpoint 는 보통 JSON 포맷으로 된 데이터와 동작 가능한 HTTP method 정보를 가지고 있다.

 

from rest_framework.decorators import action

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

		"""
    URL Reverse 명: basename-함수명 (언더바는 하이픈으로 변경)
										즉, post-public이 된다.
		"""
    @action(detail=False, methods=['GET'])  # 특정 포스팅이 아니므로 detail=False
    def public(self, request):
        qs = self.get_queryset().filter(is_public=True)
        serializer = self.get_serializer(qs, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['PATCH']) # 특정 포스팅이므로 detail=True
    def set_public(self, request, pk):  # public으로 변경하는 api
        instance = self.get_object()
        instance.is_public = True
        instance.save()  # 또는 instance.save(update_fields=['is_public'])
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

    # @action의 detail인자에 따라서 list URL을 탈건지, detail URL을 탈건지 결정된다.
  • get_serializer(쿼리셋, many=True)에서 many=True는 여러 객체를 가져온다는 것을 의미한다.

 

Router 사용시 API를 반환하는 함수 이름으로 자동으로 endpoints가 생성된다.

 

 

 

위 코드를 통해 자동으로 생성된 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']>,
    # ...
]

 

 

반응형