[Django REST framework] 5. Relationships & Hyperlinked APIs
https://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/
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
지금까지 우리는 relationship을 primary key을 사용하여 표현하였다면,
이제는 hyperlinking for relationships를 사용하여 API의 cohesion&discoverability를 향상시킬 것이다.
API root를 위한 endpoint 만들기
우리는 2개의 endpoints를 가지고 있다: 'snippets', 'users'
이둘을 위한 single entry point를 만들어보자
이를 위해, regular function-based view를 사용할 것이다: @api_view decorator
# 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)
})
* reverse: REST framework가 제공하는 기능으로, fully-qualified URLs를 return한다
* URL patterns는 뒤에서 선언할 간단한 names로 식별될 것이다
URLconf에 view를 추가하자
# snippets/urls.py
urlpatterns = [
path('', views.api_root), # root
path('snippets/', views.SnippetList.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),
]
highlighted snippets를 위한 endpoint 만들기
지금까지의 API endpoints는 JSON representation을 사용했으나,
이번 highlighted snippets를 위한 endpoint는 HTMl representation을 사용할 것이다
* representation이란? -> [오늘공부] - [2020/04/10] REST/ resource / representation
REST framework가 제공하는 HTML renderer은 2가지 스타일이 있다:
1. HTML renderer using templates
2. pre-rendered HTML
여기서는 2번째 방법을 사용하도록 하자
또한, 고려해야할 것은,
code highlighting ivew를 만들 때, 우리가 사용할 concrete generic view(예: ListCreateAPIView ...)가 없다는 것이다.
우리는 object instance가 아닌 property(속성) of an object instance를 return해야한다
concrete generic view를 사용하지 않고,
base class를 사용하고, .get() 메소드를 만들 것이다.
# 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에 view를 추가하자
# snippets/urls.py
urlpatterns = [
path('', views.api_root), # root
path('snippets/', views.SnippetList.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),
path('snippets/<int:pk>/highlight', views.SnippetHighlight.as_view()), # SnippetHighlight
]
API에 Hyperlinking하기
entity들 사이에 relationships을 다루는 일은 Web API design에 있어 어려운 부분 중 하나이다.
relationship을 표현하는데 있어 여러 방법들이 있다:
- primary keys 사용
- entities 사이에 hyperlinking 사용
- related entity에 고유의 식별 가능한 slug field 사용
- related entity의 default string representation 사용
- related entity를 부모 representation에 넣기
- custom representation 사용
REST framework는 이 모든 방법을 지원하고, 이를 forward/reverse relationships 혹은 custom managers(예: generic foreign keys)에 적용할 수 있다.
여기서는 hyperlinked style between entities를 사용하도록 할 것이다.
ModelSerializer 대신 HyperlinkedModelSerializer을 사용하자
HyperlinkedModelSerializer은 이런 특징이 있다:
1. id field를 디폴트로 가지고 있지 않는다
2. url field를 포함하는데, HyperlinkedIdentityField로 가능하다.
3. relationship은 HyperlinkedRelatedField를 사용한다(PrimaryKeyRelatedField 대신에)
snippets/serializers.py를 수정해보자
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
from django.contrib.auth.models import User
class SnippetSerializer(serializers.HyperlinkedModelSerializer): # HyperlinkedModelSerializer
owner = serializers.ReadOnlyField(source='owner.username') # owner field 추가
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html') # hyperlinked
class Meta:
model = Snippet
fields = ['url', 'id', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style'] # url, highlight 추가
class UserSerializer(serializers.HyperlinkedModelSerializer): # HyperlinkedModelSerializer
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True) # hyperlinked
class Meta:
model = User
fields = ['url', 'id', 'username', 'snippets'] # url 추가
URL patters가 named되었는지 확인하기
hyperlinked API를 사용한다면, 다음의 URL patterns에 이름을 붙여줘야한다:
- API root는 'user-list'와 'snippet-list'를 참조해야한다
- snippet serializer은 'snippet-highlight'을 참조하는 field를 포함해야한다
- user serializer은 'snippet-detail'을 참조하는 field를 포함해야한다
- snippet&user serializer은 'url' field를 포함해야하는데, 이는 기본적으로 '{model_name}-detail'(여기서는 'snippet-detail'과 'user-detail')을 참조한다.
결론적으로, snippets/urls.py는 다음과 같아야 한다
from django.urls import path
from snippets import views
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = format_suffix_patterns([
path('', views.api_root), # 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'), # SnippetHighlight
path('users/', views.UserList.as_view(), name='user-list'),
path('users/<int:pk>/', views.UserDetail.as_view(), name='user-detail'),
])
pagination 추가하기
Pagination이란 paging이라고도 하며, document를 여러 페이지로 나누는 것을 말한다.
즉, users와 code snippets를 위한 list views가 꽤 많은 instances를 return할 수 있기에, 이를 좀더 작은 덩어리를 나눠서 제공하는 것을 말한다.
default list style을 바꿈으로써 간단히 해결할 수 있다. tutorial/settings.py에 다음 코드를 추가해보자
# rest-framework
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
Browsing the API
테스트해보자
> python manage.py runserver
http://127.0.0.1:8000/snippets/에서 snippets의 리스트를 볼 수 있고, highlight field에서 highlighted code HTML representations을 얻을 수 있는 링크를 볼 수 있을 것이다.