SW개발/Django

[Django]Django REST Framework 튜토리얼 5 (Relationships & Hyperlinked APIs)

이번 포스팅에서는 DRF Authentication & PermissionsRelationships & Hyperlinked APIs 공식 문서를 공부하면서 번역해보려고 한다.
해석에 틀린 내용이 있을 수 있으니 이해가 안가는 부분은 아래의 공식문서를 참조하기 바란다.

 

DRF Relationships & Hyperlinked APIs 공식 Documentation

 

5 - Relationships and hyperlinked APIs - Django REST framework

At the moment relationships within our API are represented by using primary keys. In this part of the tutorial we'll improve the cohesion and discoverability of our API, by instead using hyperlinking for relationships. Right now we have endpoints for 'snip

www.django-rest-framework.org

현재 API 내의 관계는 primary key를 사용하여 표시된다. 이번 튜토리얼에서는 관계를 hyperlink를 사용해서 API의 발견성과 응집력을 향상시켜볼 것이다.

 

Creating an endpoint for the root of our API

지금까지 'snippets' 와 'users' 에 대한 enpoint를 가지고 있다. 그러나 API의 시작점은 가지고 있지 않다. 시작점을 만들기 위해 function-based view 와 @api_view 데코레이터를 사용할 것이다. 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)
    })

두 가지 주목해야될 점이 있다. 첫째로, REST framework의 reverse 기능은 완벽하게 정규화 된 URL을 반환한다.

두 번째로, URL pattern은 나중에 snippets/urls.py 에 선언한 이름을 통해 식별할 수 있다.

 

Creating an endpoint for the highlighted snippets

다른 명백한 점은 지금까지의 pasetebin API는 highlightingcode를 볼 수 있는 endpoint가 존재하지 않는 점이다.

다른 APIendpoint와는 달리 JSON 형식을 사용하지 않고, 대신 HTML 페이지를 보여줄 것이다. REST framewok는 두 가지의 HTML 렌더링 방식을 제공한다. 하나는 template을 사용하여 렌더링 하는 것이고, 나머지 하나는 이미 렌더링 된 HTML을 사용하는 것이다.

endpoint에서는 후자의 렌더링 방법을 이용할 것이다.

highlightingcodeview를 만들기 위해 고려해야할 점은 현재는 사용할만한 generic view가 없다는 점이다. objectinstance를 반환할 것이 아니라 object instance의 속성 하나를 반한활 것이기 때문이다.

generic view를 사용하는 대신에 평범한 base class를 사용하고 .get() 메서드를 만들 것이다.

snippets/views.py에 다음과 같은 내용을 추가하자.

from rest_framework import renderers
from rest_framework.response import Response

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 에 생성한 view를 연결시킬 것이다. snippets/urls.py에 다음과 같은 내용을 추가하자.

path('', views.api_root),

그리고 snippet highlight를 위한 url pattern을 추가시키자.

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

 

Hyperlinking our API

entitile(요소)들 사이에서 관계를 다루는 것은 Web API design에서 또 하나의 도전 과제이다. 관계를 표현하는 방법은 여러가지가 있다.

  • primary key 사용
  • entitle 사이의 hyperlink
  • related entity의 식별 가능한 slug 필드
  • related entity의 기본 문자열 표현
  • 부모 표현 안에 포함된 related entity (Nested)
  • 커스텀 표현

REST framework는 이 모든 방법을 지원하고, 정관계/역관계에 적용하거나 generic foreign key처럼 Custom Manager 에도 적용할 수 있다.

이번 튜토리얼에서는 entitle 사이에서 hyperlinked된 방식을 사용할 것이다. 이렇게 하려면 serializer에서 ModelSerializer를 사용하는 대신에 HyperlinkedModelSerializer를 상속받아서 사용해야 한다.

 

HyperlinkedModelSerializerModelSerializer와 비교해 아래와 같은 차이점이 있다.

  • id 필드는 기본이 아님
  • HyperlinkedIdentifyField를 사용하는 url 필드가 포함됨
  • PrimaryKeyRelatedField를 사용하는 대신에 HyperlinkedRelatedField를 사용함

기존의 serializerhyperlink를 사용하는 것은 쉽다. 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']

새롭게 추가된 'highlight' 필드에 주목하자. 이 필드는 url 필드와 같은 타입이며 'snippet-detail' URL pattern 대신에 'snippet-highlight' url pattern을 가리킨다.

앞에서 URLformat suffix로 '.json'을 붙였던 것과 마찬가지로 highlight 필드에도 format suffix'.html'을 사용했다.

 

Making sure our URL patterns are named

hyperlinked된 API를 이용하고 싶다면 URL pattern에 이름을 붙여야 한다. 이름을 지정해야 하는 URL pattern들을 살펴보자.

  • root API'user-list''snippet-list'
  • snippet serializer는 'snippet-highlight'
  • user serializer'snippet-detail'
  • snippet/user serializer'url' 필드를 포함하고 있음, 이 필드는 기본적으로 '{모델이름}-detail' 을 가리킴
  • 따라서 'snippet-detail', 'user-detail'이 됨

이러한 이름들을 URLconf에 추가하자. 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')
])

 

Adding pagination

사용자나 code snippet의 목록이 꽤 긴 경우가 있을 것이다. 따라서 결과는 paginate하여 API client에서 순서대로 읽을 수 있게 해보자.

tutorial/settings.py 파일을 열어 pagination을 사용하기 위해 약간 설정을 수정할 것이다. 다음과 같이 설정한다.

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

REST framework의 모든 설정은 REST_FRAMEWOK 라는 곳에 하나의 딕셔너리 형식으로 넣어야 한다. 이렇게 해야 다른 프로젝트의 setting과 분리될 수 있다.

필요에 따라서 pagination을 커스텀 할 수도 있다. 하지만 지금은 default로 사용할 것이다.

 

Browsing the API

browsable API를 브라우전에서 열어보면 API 여러 페이지를 볼 수 있을 것이다.

또한 snippet insatncehighlight된 버전을 HTML 형태로 볼 수 있을 것이다.

 

part 6 of tutorial 에서는 ViewSetRouter를 사용해서 API의 코드 양을 줄이는 방법을 알아볼 것이다.


이번 튜토리얼에서도 똑같이 따라하면 에러가 발생하는 부분을 찾을 수 있었다.

 

tutorial/settings.py 의 설정에서 한 줄을 제거하여 설정하자.

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

'''
변경후
'''
REST_FRAMEWORK = {
    'PAGE_SIZE': 10
}

더불어 tutorial/views.pySnippetList 클래스와 SnippetDetail 부분에서 Serializer context 에러가 발생하여 다음과 같은 추가 작업을 진행해야 한다.

tutorial/views.py 를 다음과 같이 수정하자.

'''
변경전
'''
class SnippetList(APIView):
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        # 수정할 부분
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

	

class SnippetDetail(APIView):
    def get(self, request, pk ,format=None):
        snippet = self.get_object(pk)
        # 수정할 부분
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)
    


'''
변경후
'''
class SnippetList(APIView):
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        # 추가된 부분 context
        serializer = SnippetSerializer(snippets, many=True, context={'request': request})
        return Response(serializer.data)

	

class SnippetDetail(APIView):
    def get(self, request, pk ,format=None):
        snippet = self.get_object(pk)
        # 추가된 부분 context
        serializer = SnippetSerializer(snippet, context={'request': request})
        return Response(serializer.data)
    

다음과 같이 수정하면 모두 정상적으로 작동하는 API를 확인할 수 있다.

 

728x90