viewset에서 url을 연결하는 방법에는 크게 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
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을 라우터로 연결해야 한다면?
- views를 import 해준다!
from ecoshop import views
from event import views
당연히 오류가 난다!
- app을 import 해준다!
router.register(r'shop', ecoshop.views.EcoShopViewSet)
router.register(r'event', event.views.EcoShopViewSet)
역시 안된다.
🤔
- viewset을 import 해준다!
from ecoshop.views import ShopPostViewSet
from event.views import EventViewSet
router = DefaultRouter()
router.register(r'shop', ShopPostViewSet)
router.register(r'event', EventViewSet)
🎉🎉 성공적으로 연결되었다!
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)
위 코드에서는 하트를 누르는 것과 취소하는 기능을 추가적으로 구현하였다. 자세히 살펴보면
- detail=True
True: pk 값을 지정해줘야하는 경우에 사용
False: 목록 단위로 적용
- 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으로 통합되었다. 공식 문서 참고