원문은 Django REST Framework 공식 페이지 🏠 www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/ 에서 확인할 수 있습니다.
현재 우리의 API에는 Snippet과 User 간의 관계를 아래와 같이 기본키(primary key)를 사용하여 표현하고 있다.
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
...
이번 튜토리얼에서는 Hyperlink를 사용하여 API의 응집력(cohesion)과 발견성(discoverability)을 향상해 보자.
Creating an endpoint for the root of our API
지금 우리에게는 snippets과 users를 위한 endpoint만 있을 뿐, 전체 API에 대한 단일 진입점이 없다. 일반적인 함수 기반 뷰와 앞에서 소개한 @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)
})
여기서 두 가지 사항에 대해 주목하자. 먼저, 정규화된 URL을 반환하기 위해 reverse 함수를 사용하고 있으며, 이 URL 패턴은 추후 우리가 snippets/urls.py 파일에 선언할 이름으로 식별될 것이라는 것이다.
Creating an endpoint for the highlighted snippets
우리가 API에서 아직까지 만들지 않은 endpoint는 바로, 하이라이트 된 Snippet을 볼 수 있는 방법이다. 하이라이트된 Snippet을 보기 위해서는 다른 API endpoint와 달리 JSON을 사용하지 않고 HTML 형태로만 제공되어야 한다.
DRF에서는 두 가지 스타일의 HTML 렌더러를 제공하고 있다. 하나는 1) 템플릿을 사용하여 렌더링 된 HTML을 처리하는 것이고, 다른 하나는 2) 사전에 이미 렌더링 된 HTML을 사용하는 것이다. 우선 두번째 렌더러를 사용하여 endpoint를 만들어 보자.
Highlighted Snippet 뷰를 만들 때 고려해야할 사항 중 하나는 우리가 사용할 만한 concrete generic view (앞서 사용했던 RetrieveAPIView 등)가 없다는 것이다. 객체 인스턴스(Snippet)를 반환하는 것이 아니라, 객체 인스턴스의 속성 중 하나(Snippet.highlighted)를 반환해야 하기 때문이다. concrete generic view를 사용하는 대신, 기본 클래스를 사용하고 자체 .get() 함수를 만들어 보자. snippets/view.py 파일에 다음 내용을 추가한다.
from rest_framework import renderers
from rest_framework.response import Response
# 기본 클래스를 사용한다.
class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
renderer_classes = [renderers.StaticHTMLRenderer]
# 자체 get() 함수를 구현한다.
def get(self, request, *args, **kwargs):
snippet = self.get_object()
# Snippet.highlighted를 반환한다.
return Response(snippet.highlighted)
지금까지 그래 왔듯이, 만들어진 view는 URLconf에 추가해야 한다. snippets/urls.py 파일을 수정하여 새로 만든 API root와 방금 만든 Highlighted Snippet 뷰의 URL 패턴을 추가해 보자.
urlpatters = [
...
path(r'', views.api_root),
path(r'snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view()),
]
Hyperlinking our API
요소 간의 관계를 다루는 것은 web API 디자인에서 가장 어려운 일 중 하나이다. 관계를 표현하는 방법에는 여러 가지가 있다.
- 기본 키(primary key)
- 하이퍼링크(hyperlink)
- 관련 요소의 고유 식별 슬러그(slug) 필드
- 관련 요소의 기본 문자열 표현
- 포함된 관계 요소에 대한 표현
- 다른 사용자화 된 표현
DRF에서는 이러한 모든 방법을 지원하며, 정방향 혹은 역방향 관계에 적용하거나, 일반 외래 키(foreign key)와 같은 사용자화 된 관리자에도 적용할 수 있다.
이 튜토리얼에서는 하이퍼링크를 사용하는 방법에 대해 안내한다. 우선, 하이퍼링크 관계를 사용하기 위해서는 Serializer를 ModelSerializer가 아닌 HyperlinkedModelSerializer로 변경해야 한다. HyperlinkedModelSerializer는 ModelSerializer와 비교하여 다음과 같은 차이점이 있다.
- id 필드(primary key)는 기본적으로 포함되지 않는다.
- HyperlinkedIdentyField를 사용하는 url 필드가 포함된다.
- 관계를 나타내기 위해 PrimaryKeyRelatedField 대신 HyperlinkedRelatedField를 사용한다.
snippets/serializers.py 파일을 수정하여 기존 Serializer를 하이퍼링크를 사용하도록 해 보자.
# HyperlinkedModelSerializer 로 변경하였다.
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
# url 필드를 추가. (highlight도 추가 함)
fields = ['url', 'id', 'highlight', 'owner',
'title', 'code', 'linenos', 'language', 'style']
class UserSerializer(serializers.HyperlinkedModelSerializer):
# PrimaryKeyRelatedField가 아닌 HyperlinkedRelatedField를 사용한다.
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)
class Meta:
model = User
# url 필드를 추가.
fields = ['url', 'id', 'username', 'snippets']
Snippet과 User 간의 관계를 하이퍼링크로 변경하는 것 외에, 새로운 필드인 highlight를 추가하였다. 이 필드는 snippet-detail url 패턴 대신 snippet-highlight 패턴을 가리키는 것 말고는 url 필드와 같은 타입니다. 앞선 튜토리얼에서 URL의 포맷 접미사로 '.json'을 붙였듯이, highlight 필드에서는 '.html'을 사용한다.
Making sure our URL patterns are named
하이퍼링크 API를 사용하려면, URL 패턴에 이름을 붙여야 한다. snippet/views.py 파일과 snippets/serializers.py 파일을 훑어보면서 어떤 패턴들의 이름을 지정해야 하는지 살펴보자,
- API의 root는 'user-list'과 'snippet-list'를 가리킨다.
- SnippetSerializer에는 'snippet-highlight'를 가리키는 필드가 있다.
- UserSerializer에는 'snippet-detail'을 가리키는 필드가 있다.
- SnippetSerialzier와 UserSerializer에는 'url' 필드가 존재한다. 이 필드는 기본적으로 '{model_name}-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
사용자나 Snippet의 목록이 꽤 길어질 수 있으므로, 결과를 여러 페이지로 나누어 API 클라이언트 측에서 각 페이지를 하나씩 차례로 읽을 수 있도록 만들어 보자.
다음과 같이 tutorial/settings.py 파일을 수정하여 페이징을 설정할 수 있다.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
DRF의 설정은 프로젝트의 다른 설정들과 잘 분리될 수 있도록 REST_FRAMEWORK 이름의 딕셔너리로 추가해야 한다. 필요한 경우, 페이징 스타일을 사용자 정의할 수도 있다.
Browsing the API
브라우저를 열고 API root로 이동하면, 이제 모든 API를 둘러볼 수 있다. 또한 Snippet 인스턴스에서 highlight 링크가 추가되었으며, 이를 따라 들어가면 HTML 형태의 Highlighted Snippet 뷰도 확인할 수 있다.
'일하는 > Cloud, Web' 카테고리의 다른 글
Fast API (0) | 2021.02.03 |
---|---|
Django REST Framework : (6) ViewSets & Routers (0) | 2020.11.09 |
Django REST Framework : (4) Authentication & Permissions (0) | 2020.10.12 |
Django REST Framework : (3) Class-based Views (0) | 2020.09.18 |
Elastic Stack (ELK Stack) 이란? (0) | 2020.09.17 |