[DRF] Routers

강민성·2024년 9월 19일

DRF API Guide

목록 보기
6/28

Routers

리소스 라우팅은 특정 리소스 컨트롤러에 대한 모든 공통 경로를 신속하게 선언할 수 있게 해줍니다. 인덱스 등의 개별 경로를 선언하는 대신, 리소스 경로는 한 줄의 코드로 이를 선언합니다.
— Ruby on Rails Documentation

Rails와 같은 일부 웹 프레임워크는 애플리케이션의 URL이 들어오는 요청을 처리하는 로직과 어떻게 연결되는지를 자동으로 결정하는 기능을 제공합니다.

REST 프레임워크는 Django에 자동 URL 라우팅을 지원하여, 뷰 로직을 일관되고 간단하게 URL 세트에 연결할 수 있는 방법을 제공합니다.

사용법

다음은 SimpleRouter를 사용하는 간단한 URL 설정 예시입니다:

from rest_framework import routers

router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = router.urls

register() 메서드에는 두 가지 필수 인자가 있습니다:

  • prefix: 이 경로 집합에 사용할 URL 접두사.
  • viewset: 뷰셋 클래스.

선택적인 추가 인자:

  • basename: 생성된 URL 이름에 사용할 기본 이름입니다. 설정되지 않은 경우, 뷰셋의 queryset 속성에 따라 자동으로 생성됩니다. 뷰셋에 queryset 속성이 없는 경우에는 basename을 명시적으로 지정해야 합니다.

위 예시는 다음과 같은 URL 패턴을 생성합니다:

  • URL 패턴: ^users/$ 이름: 'user-list'
  • URL 패턴: ^users/{pk}/$ 이름: 'user-detail'
  • URL 패턴: ^accounts/$ 이름: 'account-list'
  • URL 패턴: ^accounts/{pk}/$ 이름: 'account-detail'

basename 인자는 뷰 이름 패턴의 시작 부분을 지정하는 데 사용됩니다. 위 예에서는 user 또는 account가 이에 해당합니다.

보통은 basename 인자를 지정할 필요가 없지만, 뷰셋에 커스텀 get_queryset 메서드를 정의한 경우, 뷰셋에 .queryset 속성이 설정되지 않을 수 있습니다. 이 경우, basename 인자가 명시적으로 지정되지 않으면 오류가 발생할 수 있습니다:

'basename' 인자가 지정되지 않았고, 뷰셋에서 '.queryset' 속성을 찾을 수 없어 자동으로 이름을 결정할 수 없었습니다.

이는 모델 이름에서 자동으로 결정할 수 없기 때문에, 뷰셋을 등록할 때 basename 인자를 명시적으로 설정해야 한다는 의미입니다.

include와 라우터 사용하기

라우터 인스턴스의 .urls 속성은 표준 URL 패턴 리스트입니다. 이 URL들을 포함하는 몇 가지 다른 스타일이 있습니다.

예를 들어, 기존의 뷰 목록에 router.urls를 추가할 수 있습니다:

router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)

urlpatterns = [
    path('forgot-password/', ForgotPasswordFormView.as_view()),
]

urlpatterns += router.urls

또는 Django의 include 함수를 사용할 수도 있습니다:

urlpatterns = [
    path('forgot-password', ForgotPasswordFormView.as_view()),
    path('', include(router.urls)),
]

어플리케이션 네임스페이스와 함께 include를 사용할 수도 있습니다:

urlpatterns = [
    path('forgot-password/', ForgotPasswordFormView.as_view()),
    path('api/', include((router.urls, 'app_name'))),
]

또는 어플리케이션과 인스턴스 네임스페이스를 함께 사용할 수도 있습니다:

urlpatterns = [
    path('forgot-password/', ForgotPasswordFormView.as_view()),
    path('api/', include((router.urls, 'app_name'), namespace='instance_name')),
]

더 자세한 내용은 Django의 URL 네임스페이스 문서include API 레퍼런스를 참조하세요.

주의: 하이퍼링크된 시리얼라이저를 네임스페이싱할 경우, 시리얼라이저의 view_name 매개변수가 네임스페이스를 정확히 반영하도록 해야 합니다. 위 예시에서는 사용자 상세 뷰에 하이퍼링크된 필드의 경우 view_name='app_name:user-detail'과 같은 매개변수를 포함해야 합니다.

자동 view_name 생성은 %(model_name)-detail과 같은 패턴을 사용합니다. 모델 이름이 충돌하지 않는 한, 하이퍼링크된 시리얼라이저를 사용할 때는 Django REST Framework 뷰를 네임스페이싱하지 않는 것이 더 나을 수 있습니다.

추가 액션에 대한 라우팅

뷰셋은 메서드에 @action 데코레이터를 사용하여 추가 액션을 라우팅할 수 있습니다. 이 추가 액션들은 생성된 경로에 포함됩니다. 예를 들어, UserViewSet 클래스에서 set_password 메서드를 사용한 경우:

from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import action

class UserViewSet(ModelViewSet):
    ...

    @action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf])
    def set_password(self, request, pk=None):
        ...

다음과 같은 경로가 생성됩니다:

  • URL 패턴: ^users/{pk}/set_password/$
  • URL 이름: 'user-set-password'

기본적으로, URL 패턴은 메서드 이름을 기준으로 하며, URL 이름은 ViewSet.basename과 하이픈으로 연결된 메서드 이름의 조합입니다. 이 값들을 기본값으로 사용하지 않으려면 @action 데코레이터에 url_pathurl_name 인자를 제공할 수 있습니다.

예를 들어, 커스텀 액션의 URL을 ^users/{pk}/change-password/$로 변경하려면 다음과 같이 작성할 수 있습니다:

from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import action

class UserViewSet(ModelViewSet):
    ...

    @action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf],
            url_path='change-password', url_name='change_password')
    def set_password(self, request, pk=None):
        ...

위 예시는 이제 다음과 같은 URL 패턴을 생성합니다:

  • URL 패턴: ^users/{pk}/change-password/$
  • URL 이름: 'user-change_password'

SimpleRouter

이 라우터는 목록, 생성, 검색, 수정, 부분 수정, 삭제와 같은 표준 액션을 위한 경로를 포함합니다. 뷰셋은 @action 데코레이터를 사용하여 추가 메서드를 라우팅할 수도 있습니다.

URL 스타일HTTP 메서드액션URL 이름
{prefix}/GETlist{basename}-list
POSTcreate
{prefix}/{url_path}/GET@action(detail=False)로 데코레이트된 메서드{basename}-{url_name}
{prefix}/{lookup}/GETretrieve{basename}-detail
PUTupdate
PATCHpartial_update
DELETEdestroy
{prefix}/{lookup}/{url_path}/GET@action(detail=True)로 데코레이트된 메서드{basename}-{url_name}

기본적으로 SimpleRouter가 생성한 URL에는 슬래시가 뒤에 붙습니다. 이 동작은 라우터를 인스턴스화할 때 trailing_slash 인자를 False로 설정하여 변경할 수 있습니다. 예를 들어:

router = SimpleRouter(trailing_slash=False)

이제 URL 패턴에서 슬래시가 생략됩니다. Django에서는 슬래시가 관례이지만, Rails와 같은 일부 다른 프레임워크에서는 기본적으로 사용되지 않습니다. 어느 스타일을 사용할지는 선호도의 문제일 뿐만 아니라, 일부 JavaScript 프레임워크는 특정 라우팅 스타일을 기대할 수 있습니다.

기본적으로 SimpleRouter가 생성한 URL은 정규 표현식을 사용합니다. 이 동작은 라우터를 인스턴스화할 때 use_regex_path 인자를 False로 설정하여 변경할 수 있으며, 이 경우 경로 변환기가 사용됩니다. 예를 들어:

router = SimpleRouter(use_regex_path=False)

주의: use_regex_path=False는 Django 2.x 이상에서만 동작하며, 이 기능은 2.0.0 버전에 도입되었습니다. 릴리스 노트를 참조하세요.

라우터는 슬래시 및 마침표 문자를 제외한 모든 문자를 포함하는 조회 값을 일치시킵니다. 더 제한적이거나 덜 제한적인 조회 패턴을 원할 경우, 뷰셋에서 lookup_value_regex 속성을 설정하거나 경로 변환기를 사용할 경우 lookup_value_converter를 설정하세요. 예를 들어, 조회를 유효한 UUID로 제한할 수 있습니다:

class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    lookup_field = 'my_model_id'
    lookup_value_regex = '[0-9a-f]{32}'
    
class MyPathModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    lookup_field = 'my_model_uuid'
    lookup_value_converter = 'uuid'

DefaultRouter

이 라우터는 SimpleRouter와 유사하지만, 추가적으로 모든 목록 뷰에 대한 하이퍼링크를 포함하는 기본 API 루트 뷰를 제공합니다. 또한 선택적으로 .json 형식의 접미사를 사용하여 경로를 생성할 수 있습니다.

URL 스타일HTTP 메서드액션URL 이름
[.format]GET자동 생성된 루트 뷰api-root
{prefix}/[.format]GET목록 보기{basename}-list
POST생성
{prefix}/{url_path}/[.format]GET, 또는 methods 매개변수에 지정된 메서드@action(detail=False) 데코레이터된 메서드{basename}-{url_name}
{prefix}/{lookup}/[.format]GET세부 보기{basename}-detail
PUT업데이트
PATCH부분 업데이트
DELETE삭제
{prefix}/{lookup}/{url_path}/[.format]GET, 또는 methods 매개변수에 지정된 메서드@action(detail=True) 데코레이터된 메서드{basename}-{url_name}

SimpleRouter와 마찬가지로, URL 경로에서 슬래시를 제거하려면 trailing_slash 인수를 False로 설정하여 라우터를 인스턴스화할 수 있습니다.

router = DefaultRouter(trailing_slash=False)

커스텀 라우터

커스텀 라우터를 구현할 필요는 자주 없지만, API의 URL이 특정한 방식으로 구조화되어야 하는 경우 유용할 수 있습니다. 이를 통해 URL 구조를 재사용 가능한 방식으로 캡슐화하여 각 새로운 뷰에 대해 URL 패턴을 명시적으로 작성하지 않아도 됩니다.

가장 간단한 방법은 기존의 라우터 클래스 중 하나를 서브클래싱하는 것입니다. .routes 속성은 각 뷰셋에 매핑될 URL 패턴을 템플릿하는 데 사용됩니다. .routes 속성은 Route라는 이름의 튜플 리스트입니다.

Route 튜플에 대한 인수는 다음과 같습니다:

  • url: 라우팅될 URL을 나타내는 문자열. 다음과 같은 형식 문자열을 포함할 수 있습니다:
    • {prefix}: 이 경로 집합에 사용할 URL 접두사.
    • {lookup}: 단일 인스턴스와 일치하는 조회 필드.
    • {trailing_slash}: / 또는 빈 문자열, trailing_slash 인수에 따라 달라짐.
  • mapping: HTTP 메서드 이름과 뷰 메서드 간의 매핑.
  • name: 역방향 호출에 사용되는 URL의 이름. 다음과 같은 형식 문자열을 포함할 수 있습니다:
    • {basename}: 생성되는 URL 이름에 사용할 기본 이름.
  • initkwargs: 뷰를 인스턴스화할 때 전달할 추가 인수의 사전. detail, basename, suffix 인수는 뷰셋의 내부 조사와 브라우저 API에서 뷰 이름과 경로 링크를 생성하는 데 사용됩니다.

동적 경로 커스터마이징

@action 데코레이터가 라우팅되는 방식을 커스터마이징할 수도 있습니다. .routes 리스트에 DynamicRoute 이름의 튜플을 포함하고, 목록 기반 및 세부 기반 경로에 적절하게 detail 인수를 설정합니다. DynamicRoute의 인수는 다음과 같습니다:

  • url: 라우팅될 URL을 나타내는 문자열. Route와 같은 형식 문자열을 포함할 수 있으며, 추가적으로 {url_path} 형식 문자열을 허용합니다.
  • name: 역방향 호출에 사용되는 URL의 이름. 다음과 같은 형식 문자열을 포함할 수 있습니다:
    • {basename}: 생성되는 URL 이름에 사용할 기본 이름.
    • {url_name}: @action에 제공된 url_name.
  • initkwargs: 뷰를 인스턴스화할 때 전달할 추가 인수의 사전.

예시

다음 예시는 목록 및 세부 액션으로만 라우팅되며, 슬래시를 사용하지 않습니다.

from rest_framework.routers import Route, DynamicRoute, SimpleRouter

class CustomReadOnlyRouter(SimpleRouter):
    """
    읽기 전용 API를 위한 라우터이며, 슬래시를 사용하지 않습니다.
    """
    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'}
        ),
        DynamicRoute(
            url=r'^{prefix}/{lookup}/{url_path}$',
            name='{basename}-{url_name}',
            detail=True,
            initkwargs={}
        )
    ]

생성된 경로 보기

views.py:

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    표준 액션을 제공하는 뷰셋
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_field = 'username'

    @action(detail=True)
    def group_names(self, request, pk=None):
        """
        해당 사용자가 속한 모든 그룹 이름을 반환한다.
        """
        user = self.get_object()
        groups = user.groups.all()
        return Response([group.name for group in groups])
urls.py:

router = CustomReadOnlyRouter()
router.register('users', UserViewSet)
urlpatterns = router.urls

생성된 매핑

URLHTTP 메서드액션URL 이름
/usersGET목록 보기user-list
/users/{username}GET세부 보기user-detail
/users/{username}/group_namesGETgroup_namesuser-group-names

더 자세한 .routes 속성 설정에 대한 예시는 SimpleRouter 클래스의 소스 코드를 참조하십시오.

고급 커스텀 라우터

사용자 정의 동작을 완전히 구현하고 싶으신 경우, BaseRouter를 상속받아 get_urls(self) 메서드를 재정의할 수 있습니다. 이 메서드는 등록된 뷰셋을 검사하고 URL 패턴 목록을 반환해야 합니다. 등록된 접두사(prefix), 뷰셋(viewset), 기본 이름(basename) 튜플은 self.registry 속성을 통해 확인할 수 있습니다.

또한, get_default_basename(self, viewset) 메서드를 재정의하거나, 뷰셋을 라우터에 등록할 때마다 기본 이름(basename) 인수를 명시적으로 설정해야 할 수도 있습니다.

서드파티 패키지

다음과 같은 서드파티 패키지도 사용 가능합니다.

DRF Nested Routers

drf-nested-routers 패키지는 중첩된(nested) 리소스와 함께 작업할 수 있도록 라우터와 관계 필드를 제공합니다.

ModelRouter (wq.db.rest)

wq.db 패키지는 register_model() API를 포함한 고급 ModelRouter 클래스를 제공합니다. 이 패키지는 DefaultRouter를 확장하여 register_model()을 통해 모델 클래스를 등록하는 기능을 제공합니다. 이는 Django의 admin.site.register와 유사하게 동작하며, rest.router.register_model에 필수로 제공해야 할 유일한 인수는 모델 클래스입니다. URL 접두사, 시리얼라이저, 뷰셋에 대한 합리적인 기본값은 모델과 전역 설정에서 추론됩니다.

from wq.db import rest
from myapp.models import MyModel

rest.router.register_model(MyModel)

DRF-extensions

DRF-extensions 패키지는 중첩된 뷰셋을 만들 수 있는 라우터와, 사용자 정의 가능한 엔드포인트 이름을 가진 컬렉션 수준의 컨트롤러를 제공합니다.

Reference

DRF API Guide - Routers

profile
Back-end Junior Developer

0개의 댓글