본문으로 건너뛰기
김신건의 로그

[DRF] Routers: DefaultRouter, SimpleRouter, custom actions

· 수정 · 📖 약 1분 · 324자/단어 #drf #router #viewset #url
DRF Router, DefaultRouter, SimpleRouter, router.register, @action decorator, extra actions

정의

Router = ViewSet 에 대한 URL 자동 생성. Rails 의 resources :users 와 유사.

왜 Router?

# ✗ 수동 URL 등록 (ViewSet 마다 6-7 라인)
urlpatterns = [
    path('users/', UserViewSet.as_view({'get': 'list', 'post': 'create'})),
    path('users/<int:pk>/', UserViewSet.as_view({
        'get': 'retrieve',
        'put': 'update',
        'patch': 'partial_update',
        'delete': 'destroy',
    })),
]

# ✓ Router 자동
router = DefaultRouter()
router.register('users', UserViewSet)
urlpatterns = [path('', include(router.urls))]

종류

flowchart TD
    Router --> S["SimpleRouter (URL 만)"]
    Router --> D["DefaultRouter (SimpleRouter + API root + format suffix)"]
    Router --> Custom["Custom Router (상속)"]

DefaultRouter

from rest_framework import routers

router = routers.DefaultRouter()
router.register('users', UserViewSet)
router.register('groups', GroupViewSet, basename='group')

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

생성되는 URL:

URLMethod대응
/api/GETAPI root (endpoint 목록)
/api/users/GETlist
/api/users/POSTcreate
/api/users/{id}/GETretrieve
/api/users/{id}/PUTupdate
/api/users/{id}/PATCHpartial_update
/api/users/{id}/DELETEdestroy
/api/users.jsonformat suffix옵션

SimpleRouter

DefaultRouter 에서 API root + format suffix 제외. 더 minimal.

router = routers.SimpleRouter()

basename (queryset 없을 때 필수)

router.register('users', UserViewSet)
# queryset 있으면 자동 basename='user'

class ArticleViewSet(viewsets.ViewSet):
    def list(self, request):
        return Response(...)

router.register('articles', ArticleViewSet, basename='article')  # ← 필수

Custom Action (@action)

Router 는 표준 CRUD 외에 커스텀 endpoint 도 자동 등록:

from rest_framework.decorators import action

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=False, methods=['get'])
    def me(self, request):
        """GET /users/me/"""
        return Response(UserSerializer(request.user).data)

    @action(detail=False, methods=['post'])
    def bulk_delete(self, request):
        """POST /users/bulk_delete/"""
        ids = request.data.get('ids', [])
        User.objects.filter(id__in=ids).delete()
        return Response(status=204)

    @action(detail=True, methods=['post'])
    def deactivate(self, request, pk=None):
        """POST /users/{id}/deactivate/"""
        user = self.get_object()
        user.is_active = False
        user.save()
        return Response(UserSerializer(user).data)

    @action(detail=True, methods=['get'], url_path='articles',
            serializer_class=ArticleSerializer)
    def user_articles(self, request, pk=None):
        """GET /users/{id}/articles/"""
        user = self.get_object()
        articles = Article.objects.filter(author=user)
        page = self.paginate_queryset(articles)
        serializer = ArticleSerializer(page or articles, many=True)
        return self.get_paginated_response(serializer.data) if page else Response(serializer.data)
옵션의미
detail=True/users/{id}/action/ (인스턴스별)
detail=False/users/action/ (컬렉션)
methods=['post']HTTP method
url_path='...'URL 이름 (기본: 메서드명)
url_name='...'reverse URL 이름
serializer_class=...해당 action 전용
permission_classes=[...]해당 action 전용

Nested Router

기본 DRF 는 nested 지원 안 함. drf-nested-routers 사용:

pip install drf-nested-routers
from rest_framework_nested import routers

router = routers.DefaultRouter()
router.register('articles', ArticleViewSet)

# Nested
article_router = routers.NestedDefaultRouter(router, 'articles', lookup='article')
article_router.register('comments', CommentViewSet, basename='article-comments')

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

/articles/{id}/comments/

URL 확인

python manage.py show_urls
# 또는 개발 서버 실행 후 API root 확인

Multiple Routers

# 버전별
router_v1 = DefaultRouter()
router_v1.register('users', UserV1ViewSet)

router_v2 = DefaultRouter()
router_v2.register('users', UserV2ViewSet)

urlpatterns = [
    path('api/v1/', include(router_v1.urls)),
    path('api/v2/', include(router_v2.urls)),
]

자세한 건 drf-versioning-content-negotiation.

Custom Router

from rest_framework.routers import DefaultRouter, Route, DynamicRoute

class ReadOnlyRouter(DefaultRouter):
    """GET 만 지원"""
    routes = [
        Route(
            url=r'^{prefix}/$',
            mapping={'get': 'list'},
            name='{basename}-list',
            detail=False,
            initkwargs={'suffix': 'List'},
        ),
        Route(
            url=r'^{prefix}/{lookup}/$',
            mapping={'get': 'retrieve'},
            name='{basename}-detail',
            detail=True,
            initkwargs={'suffix': 'Detail'},
        ),
    ]

Router vs Manual URLs

flowchart TD
    Q{언제 Router?}
    Q -->|"표준 CRUD ViewSet"| R["Router"]
    Q -->|"복잡한 URL 구조"| M["수동 URLs"]
    Q -->|"1개 endpoint"| M
    Q -->|"버전별 관리"| Both["Router + 별도 include"]

다른 프레임워크

Framework자동 라우팅
DRFrouter.register
Railsresources :users
NestJS@Controller decorator
Spring Data REST@RepositoryRestResource
FastAPI없음 (수동)
Express없음 (수동)

흔한 함정

WARNING

  1. ViewSet 없이 register = ViewSet 아닌 것 넘기면 에러.
  2. basename 누락 (queryset 없는 ViewSet) = URL 이름 자동 생성 실패.
  3. Nested router 순서 = child router 를 나중에 include.
  4. @action methods 대소문자 = ['GET'] 대신 ['get'] 사용 실패.

관련 위키

이 글의 용어 (4개)
[Django] DRF ViewSet, Router, @actiondjango
정의 ViewSet은 같은 리소스의 관련 view들을 클래스 하나로 묶는다. Django의 generic views와 비슷하나 / / / / / action 메서드로 통일. Ro…
[DRF] Tutorial + Quickstart: 첫 API 서버 5분django
정의 Django REST Framework (DRF) 를 처음 사용 하는 사람을 위한 순차 시작 가이드. 5분 안에 동작하는 API 서버 완성. DRF 개요는 . Django …
[DRF] Versioning + Content Negotiationdjango
정의 Versioning = API 의 여러 버전 공존. Content Negotiation = 같은 endpoint 가 여러 형식 (JSON, XML, HTML) 응답. Ver…
[DRF] Views: APIView, GenericViews, Mixins, FBVdjango
정의 DRF View 4가지 스타일. 자유도 vs 자동화 트레이드오프. 4가지 스타일 | 스타일 | 코드량 | 자유도 | |---|---|---| | | 유사 Django FBV…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기