drf router - viewset

Han07·2021년 4월 12일
0
post-thumbnail

1. viewset과 router

viewset에서 url을 연결하는 방법에는 크게 2가지가 있다.

  1. 세부적으로 지정하기
  2. 라우터 사용

먼저 세부적으로 지정하는 방법을 보면

from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers

snippet_list = SnippetViewSet.as_view({
    'get': 'list',
    'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
    'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
    'get': 'list'
})
user_detail = UserViewSet.as_view({
    'get': 'retrieve'
})

이렇게 하나하나 지정해 주는 것이다. 연결하는 부분을 보면

urlpatterns = format_suffix_patterns([
    path('', api_root),
    path('snippets/', snippet_list, name='snippet-list'),
    path('snippets/<int:pk>/', snippet_detail, name='snippet-detail'),
    path('snippets/<int:pk>/highlight/', snippet_highlight, name='snippet-highlight'),
    path('users/', user_list, name='user-list'),
    path('users/<int:pk>/', user_detail, name='user-detail')
])

이런 식으로 한다. 하지만 이 방법을 사용하면 urls.py가 지저분해 보일 수 있다는 단점이 있다.

2번째 방법을 보면

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

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)

# The API URLs are now determined automatically by the router.
urlpatterns = [
   path('', include(router.urls)),
]

이런 식으로 라우터라는 것을 이용해 깔끔하게 연결할 수 있다. 하지만 viewset에서 기본 제공해주는 기능을 모두 사용하지 않는다면 첫 번째 방법이 좋을 수 있다. 아래는 viewset에서 제공하는 기능들이다.

 class UserViewSet(viewsets.ViewSet):
    """
    Example empty viewset demonstrating the standard
    actions that will be handled by a router class.

    If you're using format suffixes, make sure to also include
    the `format=None` keyword argument for each action.
    """

    def list(self, request):
        pass

    def create(self, request):
        pass

    def retrieve(self, request, pk=None):
        pass

    def update(self, request, pk=None):
        pass

    def partial_update(self, request, pk=None):
        pass

    def destroy(self, request, pk=None):
        pass

### 2. 여러 app에 있는 viewset 연결하기 ```python todo_list = TodoViewSet.as_view({"get": "list", "post": "create"}) todo_detail = TodoViewSet.as_view({"patch": "partial_update", "delete": "destroy"})

mypet_list = MyPetViewSet.as_view({"get": "list", "post": "create"})
mypet_detail = MyPetViewSet.as_view({"patch": "partial_update", "delete": "destroy"})

diary_list = DiaryViewSet.as_view({"get": "list", "post": "create"})
diary_detail = DiaryViewSet.as_view({"get": "retrieve", "delete": "destroy"})

urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('userSystem.urls')),
path('mypage/', include('mypage.urls')),

path('todo/', todo_list, name="todo-list"),
path('todo/<int:pk>/', todo_detail, name="todo-detail"),

path('mypet/', mypet_list, name="mypet-list"),
path('mypet/<int:pk>/', mypet_detail, name="mypet-detail"),

path('diary/', diary_list, name="diary-list"),
path('diary/<int:pk>/', diary_detail, name="diary-detail")

] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


하지만 이 방법을 이용하면 위와 같이 urls.py에 내용이 너무 많아진다. 그래서 이번에는 라우터를 이용해보기로 했다.
```python
from ecoshop import views

router = DefaultRouter()
router.register(r'shop', views.EcoShopViewSet)


urlpatterns = [
    path('admin/', admin.site.urls),
    path('user/', include('user_system.urls')),

    path('', include(router.urls)),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

🤓

여기서 한 가지 의문이 들었다.
router.register(r'shop', views.EcoShopViewSet)

위 코드에서 views는 ecoshop이라는 app에 있는 파일이다. 만약 여러 앱에 있는 viewset을 라우터로 연결해야 한다면?

  1. views를 import 해준다!
from ecoshop import views
from event import views

당연히 오류가 난다!

  1. app을 import 해준다!
router.register(r'shop', ecoshop.views.EcoShopViewSet)
router.register(r'event', event.views.EcoShopViewSet)

역시 안된다.

🤔

  1. viewset을 import 해준다!
from ecoshop.views import ShopPostViewSet
from event.views import EventViewSet

router = DefaultRouter()
router.register(r'shop', ShopPostViewSet)
router.register(r'event', EventViewSet)

🎉🎉 성공적으로 연결되었다!


3. @action 사용하기

viewset을 사용하다 보면 기본 제공 기능이 아닌 다른 기능을 사용해야 할 때가 있다. ApiView와 같이 새로 함수를 만들면 되지만 이런 경우에는 url 지정을 따로 해야 하는 문제가 생긴다.

다행히도 django는 @action이라는 데코레이터를 제공해준다.

viewset의 멤버 함수로 구현한 뒤 @action을 붙여주면 django가 자동적으로 url mapping을 해준다.

class EventViewSet(viewsets.ModelViewSet):
    serializer_class = EventSerializer
    permission_classes = [IsAuthenticated, ]
    queryset = Event.objects.all()

    def perform_create(self, serializer):
        serializer.save(user=self.request.user, nickname=self.request.user.nickname)

    @action(detail=True, methods=['get'])
    def heart(self, request):
        instance = self.get_object()
        instance.heart = not instance.heart

        if not instance.heart:
            instance.heart_cnt -= 1
        else:
            instance.heart_cnt += 1

        instance.save()
        serializer = self.get_serializer(instance)
        return Response(serializer.data, status=status.HTTP_200_OK)

위 코드에서는 하트를 누르는 것과 취소하는 기능을 추가적으로 구현하였다. 자세히 살펴보면

  1. detail=True

True: pk 값을 지정해줘야하는 경우에 사용
False: 목록 단위로 적용

  1. methods=['GET']

request method 지정 가능. default는 GET.

이 때 default 값에 따라 url이 달라진다.

True: /prefix/{pk}/{function name}/
False: /prefix/{function name}/

위 예시를 적용하면 heart를 연결하는 url은

/event/{pk}/heart 가 된다.(event는 EventViewSet을 연결한 것이다.)

덤: @list_route()와 @detail_route()는 django 3.8부터 @action으로 통합되었다. 공식 문서 참고

0개의 댓글