본문 바로가기

Python/Django

[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를 위한 endpointHTMl 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을 얻을 수 있는 링크를 볼 수 있을 것이다.

http://127.0.0.1:8000/snippets/2/highlight