DRF Tutorial

Tutorial 5: Relationships & Hyperlinked APIs

waterclean101 2023. 2. 16. 09:58

현재 API 내의 관계는 기본 키를 사용하여 표현된다. 이 튜토리얼에서는 관계에 하이퍼링크를 사용하여 API의 응집력과 검색 가능성을 개선하겠다.


API의 루트에 대한 엔드포인트 만들기

현재 '스니펫'과 '사용자'에 대한 엔드포인트는 있지만 API에 대한 단일 진입점이 없다. 이를 만들기 위해 일반 함수 기반 뷰와 @api_view 데코레이터를 사용할 것이다. snippets/views.py에 다음을 추가한다

# snippets/views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse


@api_view(['GET'])
def api_root(request, format=None):
    return Response({
        'users': reverse('user-list', request=request, format=format),
        'snippets': reverse('snippet-list', request=request, format=format)
    })

여기서 두 가지를 주목해야 한다. 첫째, 정규화된 URL을 반환하기 위해 REST 프레임워크의 역함수를 사용하고 있다는 점, 둘째, URL 패턴은 나중에 snippets/urls.py에서 선언할 편의상의 이름으로 식별된다는 점이다.

 

highlighted snippets 위한 엔드포인트 만들기

pastebin API에서 아직 작성하지 않은 코드는 highlighting endpoints이다.

다른 모든 API 엔드포인트와 달리 JSON을 사용하지 않고 HTML 표현만 제공할 것이다. REST 프레임워크에서 제공하는 HTML renderer에는 두 가지 스타일이 있는데, 하나는 템플릿을 사용하여 렌더링된 HTML을 처리하는 것이고, 다른 하나는 미리 렌더링된 HTML을 처리하는 것이다. 두 번째 renderer가 이 엔드포인트에 사용할 renderer다.

code highlight view를 만들 때 고려해야 할 또 다른 사항은 우리가 사용할 수 있는 기존의 구체적인 generic view가 없다는 것이다. 여기서는 객체 인스턴스를 반환하는 것이 아니라 객체 인스턴스의 속성을 반환한다.

구체적인 generic view를 사용하는 대신 인스턴스를 나타내는 base class를 사용하고 자체적으로 .get() 메서드를 만들것이다. 스니펫/views.py에 다음을 추가하자.

# snippets/views.py

from rest_framework import renderers

class SnippetHighlight(generics.GenericAPIView):
    queryset = Snippet.objects.all()
    renderer_classes = [renderers.StaticHTMLRenderer]

    def get(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

 

그 다음 URLconf에 새로 만든 뷰를 추가해야 한다. snippets/urls.py에 새 API 루트와 스니펫 하이라이트에 대한 URL 패턴을 추가하자.

# snippets/urls.py

path('', views.api_root),
path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view()),

 

API 하이퍼링크

엔티티 간의 관계를 다루는 것은 웹 API 설계에서 가장 까다로운 측면 중 하나다. 관계를 표현하기 위해 선택할 수 있는 여러 가지 방법이 있다:

  • primary keys 사용.
  • 엔티티 간 하이퍼링크 사용.
  • 관련 엔티티에 고유 식별 슬러그 필드 사용.
  • 관련 엔티티의 기본 문자열 표현 사용.
  • 상위 표현 안에 관련 엔터티 중첩하기.
  • 기타 사용자 지정 표현.

REST 프레임워크는 위의 모든 스타일을 지원하며, 정방향 또는 역방향 관계에 적용하거나 일반 외래 키와 같은 사용자 지정 관리자에 적용할 수 있다.

지금은 엔티티 간에 하이퍼링크된 스타일을 사용할 것이다. 이를 위해 serializer를 수정하여 기존 모델 직렬화기 대신 하이퍼링크드 모델 serializer를 확장할 것이다.

HyperlinkedModelSerializer는 ModelSerializer와 다음과 같은 차이점이 있다:

  • 기본적으로 ID 필드를 포함하지 않는다.
  • HyperlinkedIdentityField를 사용하여 URL 필드를 포함한다.
  • 관계는 PrimaryKeyRelatedField 대신 HyperlinkedRelatedField를 사용한다.

하이퍼링크를 사용하도록 기존 serializer를 쉽게 다시 작성할 수 있다. snippets/serializers.py에 다음을 추가하자:

class SnippetSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')

    class Meta:
        model = Snippet
        fields = ['url', 'id', 'highlight', 'owner',
                  'title', 'code', 'linenos', 'language', 'style']


class UserSerializer(serializers.HyperlinkedModelSerializer):
    snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)

    class Meta:
        model = User
        fields = ['url', 'id', 'username', 'snippets']

새로운 '하이라이트' 필드도 추가했다. 이 필드는 '스니펫-디테일' URL 패턴 대신 '스니펫-하이라이트' URL 패턴을 가리킨다는 점을 제외하면 URL 필드와 동일한 유형이다.

'.json'과 같은 형식 접미사가 붙은 URL을 포함했기 때문에 하이라이트 필드에 반환되는 형식 접미사가 붙은 하이퍼링크는 '.html' 접미사를 사용해야 한다는 점도 표시해야 한다.


URL 패턴의 이름 확인하기

하이퍼링크된 API를 사용하려면 URL 패턴의 이름을 정해야 한다. 어떤 URL 패턴에 이름을 지정해야 하는지 살펴보자.

  • API의 루트는 '사용자 목록'과 '스니펫 목록'을 나타냄.
  • 스니펫 serializer에는 '스니펫 하이라이트'를 참조하는 필드가 포함되어 있다.
  • 사용자 serializer에는 '스니펫-디테일'을 참조하는 필드가 포함되어 있다.
  • 스니펫 및 사용자 serializer에는 기본적으로 '{모델_이름}-디테일'을 참조하는 'URL' 필드가 포함되며, 이 경우 'snippet-detail' 및 'user-detail'이 된다.

이러한 이름을 URLconf에 추가한 후 최종 snippets/urls.py 파일은 다음과 같이 표시된다.

# snippets/urls.py

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

# API endpoints
urlpatterns = format_suffix_patterns([
    path('', views.api_root),
    path('snippets/',
        views.SnippetList.as_view(),
        name='snippet-list'),
    path('snippets/<int:pk>/',
        views.SnippetDetail.as_view(),
        name='snippet-detail'),
    path('snippets/<int:pk>/highlight/',
        views.SnippetHighlight.as_view(),
        name='snippet-highlight'),
    path('users/',
        views.UserList.as_view(),
        name='user-list'),
    path('users/<int:pk>/',
        views.UserDetail.as_view(),
        name='user-detail')
])

 

pagination 추가하기

사용자와 snippet에 대한 list views는 많은 인스턴스를 반환할 수 있으므로  결과의 paginate을 지정하고 API 클라이언트가 각 개별 페이지를 단계별로 살펴볼 수 있도록 하고 싶다.

tutorial/settings.py 파일을 약간 수정하여 paginate을 사용하도록 기본 list style을 변경하자.

# tutorial/settings.py

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

REST 프레임워크의 설정은 모두 REST_FRAMEWORK라는 단일 사전 설정으로 네임스페이스가 지정되므로 다른 프로젝트 설정과 잘 구분된다.

필요한 경우 pagination 스타일을 사용자 지정할 수도 있지만, 여기서는 기본값을 그대로 사용하겠다.


API 탐색하기

브라우저를 열고 탐색 가능한 API로 이동하면 이제 링크를 따라가는 것만으로 API를 탐색할 수 있다.

또한 스니펫 인스턴스에서 'highlight' 링크를 볼 수 있으며, 이 링크를 클릭하면 강조 표시된 코드 HTML로 이동할 수 있다.