본문 바로가기
Django/Django REST framework

6. Routers

by hyun-am 2021. 6. 28.

Routers

리소스 라우팅을 사용하면 지정된 리소스 저장 컨트롤러에 대한 모든 공통 경로를 신속하게 선언할 수 있습니다. 인덱스에 대해 별도의 경로를 선언하는 대신, 한줄의 코드로 이를 선언합니다.


Rails와 같은 일부 웹 프레임워크는 수신 요청을 처리하는 로직에 응용 프로그램의 URL을 매핑하는 방법을 자동으로 결정하는 기능을 제공합니다.

REST 프레임워크는 Django에 대한 자동 URL 라우팅 지원을 추가하며, view 로직을 URL 집합에 연결하는 간단하고 빠르고 일관된 방법을 제공합니다.

 

Usage

router를 사용하는 간단한 예제를 보겠습니다.

from rest_framework import routers

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

 

register( )메서드는 두개의 필수 아규먼트가 있습니다.

  • prefix : 이 라우터 셋에 사용할 URL prefix
  • viewset : 뷰셋 클레스

필요한 경우 추가 아규먼트를 지정할 수 있습니다.

  • basename : 생성된 URL 이름에 사용할 기준입니다. 설정을 해제하면 viewset의 쿼리셋 특성(있는 경우)에 따라 basename이 자동으로 생성됩니다. viewset에 쿼리셋 특성이 포함되어 있지 않은 경우 viewset을 등록할 때 basename을 설정해야 합니다.

위의 예제는 다음 URL 패턴을 생성합니다.

  • URL pattern^users/$ Name: 'user-list'
  • URL pattern^users/{pk}/$ Name: 'user-detail'
  • URL pattern^accounts/$ Name: 'account-list'
  • URL pattern^accounts/{pk}/$ Name: 'account-detail'

참고 : basename 아규먼트는 view 이름 패턴의 초기 부분을 지정하는데 사용됩니다. 위의 예시에서 이는 사용자 또는 계정 부분입니다.

일반적으로 basename 아규먼트를 지정할 필요는 없지만 사용자 지정 get_queryset메서드를 정의한 viewset가 있는 경우 viewset에 .queryset 어트리뷰트 셋이 없을 수 있습니다. 해당 viewset을 등록하려고 하면 다음과 같은 오류가 표시될 수 있습니다.

'basename' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.

Using include with routers

라우터 인스턴스의 .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)),
]

여기에 namespace를 넣어서 app_name을 사용할 수 있습니다.

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')),
]

참고 : 하이퍼링크 serializers와 함께 네임스페이스를 사용하는 경우 serializers의 view_name 매개 변수가 네임스페이스를 올바르게 반영해야 합니다. 위의 예에서는 사용자 detail view에 하이퍼링크된 serializers 필드에 대해 view_name='app_name:user-detail'와 같은 매개변수를 포함해야 합니다.

자동 view_name generation에서는 %(model_name)-detail과 같은 패턴을 사용합니다. 하이퍼링크 시리얼라이저를 사용할 때 모델 이름이 실제로 충돌하지 않는 한 DRF view의 네임스페이스를 지정하지 않는 것이 좋습니다.

Routing for extra actions

viewset은 메서드를 @action decorator로 장식하여 라우팅에 대한 extra action을 표시할 수 있습니다. 이 extra actions는 제네레이트 된 라우트를 포함합니다. 예를 들어 UserView의 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 pattern: ^users/{pk}/set_password/$
  • URL name: 'user-set-password'

기본적으로 URL 패턴은 메서드 이름을 기반으로 하며 URL 이름은 ViewSet.basename 과 하이픈으로 연결된 메서드 이름의 조합입니다. 이러한 값 중 하나에 기본값을 사용하지 않으려면 @action decorator에 url_path 및 url_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 path: ^users/{pk}/change-password/$
  • URL name: 'user-change_password'

API Guide

SimpleRouter

이 라우터에는 표준 셋으로 list, create, retrieve, update, partial_update 그리고 destroy 작업에 대한 경로가 포함됩니다. 또한 viewset은 @action 데코레이터를 사용하여 라우팅할 추가 메서드를 표시할 수 있습니다.

 

기본적으로 SimpleRouter에서 생성한 URL에는 trailing slash(후행 슬래쉬)가 추가됩니다. 라우터를 인스턴스화할 때 trailing_slash(후행 슬래쉬) 아규먼트를 False로 설정하여 이 동작을 수정할 수 있습니다.

router = SimpleRouter(trailing_slash=False)

trailing_slash는 장고에서 일반적인 것이지만 Rails와 같은 일부 다른 프레임워크에서는 기본적으로 사용되지 않습니다. 일부 JS 프레임워크는 특정 라우팅 스타일을 예상할 수 있지만, 어떤 스타일을 사용할지 선택하는 것은 크게 선호할 문제입니다.

라우터는 slash 및 마침표를 제외한 문자가 포함된 lookup값과 일치합니다. 보다 제한적인(또는 관대한) lookup 패턴을 보려면 viewset에서 lookup_value_regex 어트리뷰트를 설정하면됩니다. 예를 들어 유효한 UUID로 lookup을 제한할 수 있습니다.

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

Default Router

이 라우터는 위와 같이 SimpleRouter과 유사하지만 모든 list views에 하이퍼링크를 포함하는 응답을 반환하는 기본 API 루트 view를 포함합니다. 또한 옵션인 .json 스타일 형식 suffixes에 대한 경로를 생성합니다.

마찬가지로 SimpleRouter에서 생성한 URL에는 trailing slash(후행 슬래쉬)가 추가됩니다. 라우터를 인스턴스화할 때 trailing_slash(후행 슬래쉬) 아규먼트를 False로 설정하여 이 동작을 수정할 수 있습니다.

router = DefaultRouter(trailing_slash=False)

Custom Routers

사용자 지정 라우터를 구현하는 것은 자주 해야 하는 일은 아니지만, API의 URL이 어떻게 구조화되었는지에 대한 특정 요구사항이 있는 경우 유용할 수 있습니다. 이렇게 하면 재사용 가능한 방법으로 URL 구조를 캡슐화할 수 있으므로 각 새로운 view에 대한 URL 패턴을 명시적으로 작성할 필요가 없습니다.

사용자 정의 라우터를 구현하는 가장 간단한 방법은 기존의 라우터 클래스 중 하나를 하위 클래스로 분류하는 것입니다. .routes 어트리뷰트는 각 viewset에 매핑될 URL 패턴을 템플릿으로 만드는 데 사용됩니다. .routes 어트리뷰트는 tuples라는 이름의 라우트 목록입니다.

The arguments to the Route named tuple are:

URL : 라우팅 할 URL을 나타내는 문자열입니다. 다음 형식 문자열을 포함할 수 있습니다.

  • {prefix} - The URL prefix to use for this set of routes.
  • {lookup} - The lookup field used to match against a single instance.
  • {trailing_slash} - Either a '/' or an empty string, depending on the trailing_slash argument.

mapping : HTTP 메서드 이름을 view methods에 매핑합니다.

name : reverse 호출에 사용되는 URL의 이름입니다. 다음 형식 문자열을 포함할 수 있습니다.

  • basename : 생성된 URL 이름에 사용할 기준입니다.

initkwargs : view를 인스턴스화할 때 전달해야 하는 추가적인 아규먼트의 딕셔너리 입니다. detail, basename, 그리고 suffix는 viewset Introspection용으로 예약되며, 검색 가능한 API에서 view 이름과 breadcrum링크를 생성하는 데에도 사용됩니다.

Customizing dynamic routes

@action decorator 라우팅을 커스터마이징 할 수 있습니다. .routes 목록에 DynamicRoute named tuple을 포함하여 detail 아규먼트를 list-based 그리고 detail-based 경로에 적절하게 설정합니다. .DynamicRoute 아규먼트 외에도 다음과 같은 아규먼트가 있습니다.

url : 라우팅할 URL을 나타내는 문자열입니다. Route와 동일한 형식 문자열을 포함할 수 있으며, 추가로 {url_path}형식 문자열을 사용할 수 있습니다.

name : reverse호출에 사용되는 URL이름입니다. 다음 형식을 문자열에 포함할 수 있습니다.

  • {basename} - The base to use for the URL names that are created.
  • {url_name} - The url_name provided to the @action.

initkwargs : view를 인스턴스화할 때 전달해야 하는 추가적인 아규먼트의 딕셔너리입니다.

Example

다음 예시에서는 list로 라우팅하고 retrieve action만 하며 trailing slash(후행슬래시)규칙을 사용하지 않는 예시입니다.

from rest_framework.routers import Route, DynamicRoute, SimpleRouter

class CustomReadOnlyRouter(SimpleRouter):
    """
    A router for read-only APIs, which doesn't use trailing slashes.
    """
    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={}
        )
    ]

이제 CustomReadOnlyRouter가 simple viewset을 위해 생성하는 경로를 보겠습니다.

views.py :

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    A viewset that provides the standard actions
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_field = 'username'

    @action(detail=True)
    def group_names(self, request, pk=None):
        """
        Returns a list of all the group names that the given
        user belongs to.
        """
        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

그러면 다음과 같은 url이 생성되는 것을 확인할 수 있습니다.

.routes 애트리뷰트 설정의 다른 예시는 SimpleRouter 클래스의 원본 코드를 참고하면 되겠습니다.

Advanced custom routers

전체 사용자 지정 동작을 제공하려는 경우 BaseRouter를 재정의하고 get_urls(self)메서드를 재정의할 수 있습니다. 메서드는 등록된 viewset을 검사하고 URL 패턴 목록을 반환해야 합니다. 등록된 prefix, viewset 그리고 basename 튜플은 self.registry 어트리뷰트에 액세스하여 검사할 수 있습니다.

또한 get_default_basename(self, viewset)방법을 재정의하거나 viewset을 라우터에 등록할 때 항상 명시적으로 basename 아규먼트를 설정할 수 있습니다.

ModelRouter (wq.db.rest)

wq.db package는 register_model() API를 사용하여 DefaultRouter를 확장하는 고급 ModelRouter 클래스(및 싱글톤 인스턴스)를 제공합니다. Django의 admin.site.register와 마찬가지로 rest.router.register_model에 필요한 유일한 아규먼트는 모델 클래스입니다. 모델 및 global configuration에서 URL prefix, serializer, 그리고 viewset에 대한 적절한 기본값을 추론할 수 있습니다.

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

rest.router.register_model(MyModel)

참고링크 : 

https://www.django-rest-framework.org/api-guide/routers/

 

Routers - Django REST framework

 

www.django-rest-framework.org

 

'Django > Django REST framework' 카테고리의 다른 글

8. Renderers  (0) 2021.07.07
7. Parsers  (0) 2021.06.29
5. ViewSets  (0) 2021.06.26
4-2 Generic Views (Mixin,Concrete)  (0) 2021.06.23
4-1. Generic Views (GenericAPIView)  (2) 2021.06.07

댓글