Skip to content

7. Relationships & Hyperlinked APIs


1. Creating an endpoint for the root of our API

  • 현재 '스니펫' 및 '사용자'에 대한 엔드포인트가 있지만 API에 대한 엔트리 포인트는 없다.
  • 먼저, 엔드포인트와 엔트리 포인트의 차이점은 다음과 같다.


1] 엔드포인트

  • http://example.com/api/login
  • http://example.com/api/accounts
  • http://example.com/api/cart/items

2] 엔트리 포인트

  • http://example.com/api


  • 현재 튜토리얼에서는 각 엔드포인트에 접근할 때 http://127.0.0.1:8000/snippets/http://127.0.0.1:8000/users/를 입력한다.
  • 이렇게 입력한다는 것은 엔트리 포인트를 의미하는 것 같지만, tutorial/urls.py 파일 내의 urlpatterns를 다음과 같이 정의했기 때문에 엔드포인트를 의미한다.


tutorial/urls.py
urlpatterns = [path("", include("snippets.urls"))]


  • include()snippets 앱의 URLConf를 사용하고 있기 때문에 snippets/urls.py 파일 내의 urlpatterns에 정의된 '/snippets/''/users/'가 바로 호출될 수 있는 것이다.


  • 다시 돌아와서, 엔트리 포인트를 생성하기 위해 함수 기반의 뷰와 앞서 소개한 @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 framework의 reverse 함수를 사용한다.
  • 그리고 URL 패턴은 나중에 snippets/urls.py 파일에서 선언할 이름으로 식별된다.


2. Creating an endpoint for the highlighted snippets

  • REST framework에서 제공하는 두 가지 스타일의 HTML 렌더러가 있다.
  • 하나는 템플릿을 사용하여 렌더링된 HTML을 처리하기 위한 것이고, 다른 하나는 미리 렌더링된 HTML을 처리하기 위한 것이다.
  • 코드 강조 뷰를 생성할 때 고려해야 할 다른 사항은 사용할 수 있는 기존의 구체적인 일반(Generic) 뷰가 없다는 것이다.
  • 현재 API는 객체 인스턴스를 반환하지 않고 대신 객체 인스턴스의 속성을 반환하고 있다.


  • 구체적인 일반 뷰를 사용하는 대신 인스턴스를 나타내는 기본 클래스를 사용하고 고유한 .get() 메서드를 만들기 위해 snippets/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),


  • 그리고 스니펫 강조에 대한 URL 패턴을 추가한다.


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


3. Hyperlinking our API

  • 엔터티 간의 관계를 처리하는 것은 웹 API 디자인의 가장 어려운 측면 중 하나이다.
  • 관계를 나타내기 위해 선택할 수 있는 여러 가지 방법이 있다.


1] 기본키 사용

2] 엔터티 간 하이퍼링크 사용

3] 관련 엔터티에서 고유한 식별 슬러그 필드 사용

4] 관련 엔터티의 기본 문자열 표현 사용

5] 상위 표현 내부에 관련 엔터티 중첩

6] 다른 사용자 정의 표현


  • REST framework는 위와 같은 스타일을 지원하며 정방향 또는 역방향 관계에 적용하거나 일반 외래키와 같은 사용자 정의 관리자에 적용할 수 있다.
  • 엔터티 간 하이퍼링크된 스타일을 사용하고 싶은 경우 이를 위해 기존 ModelSerializer 대신 HyperlinkedModelSerializer를 확장하도록 직렬 변환기를 수정한다.


  • HyperlinkedModelSerializer에는 ModelSerializer와 다른 차이점이 있는데 다음과 같다.


1] 기본적으로 id 필드를 포함하지 않는다.

2] HyperlinkedIdentityField를 사용하는 url 필드가 포함된다.

3] 관계(Relationships)는 PrimaryKeyRelatedField 대신 HyperlinkedRelatedField를 사용한다.


  • snippets/serializers.py 파일을 열어 하이퍼링크를 사용하도록 기존 직렬 변환기를 다시 작성한다.


snippets/serializers.py
from rest_framework import serializers
from snippets.models import Snippet
from django.contrib.auth.models import User

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 필드를 추가했다.
  • 이 필드는 "snippet-detail" URL 패턴 대신 "snippet-highlight" URL 패턴을 가리키는 것을 제외하고는 url 필드와 동일한 유형이다.
  • ".json"과 같은 형식 접미사 URL을 포함했기 때문에 반환되는 형식 접미사 하이퍼링크가 ".html" 접미사를 사용해야 함을 highlight 필드에 표시해야 한다.


4. Making sure our URL patterns are named

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


1] API의 루트는 "user-list""snippet-list"를 참조한다.

2] 스니펫 직렬 변환기는 "snippet-highlight"를 참조하는 필드를 포함한다.

3] 사용자 직렬 변환기는 "snippet-detail"을 참조하는 필드가 포함한다.

4] 스니펫 및 사용자 직렬 변환기에는 기본적으로 "{model_name}-detail"을 참조하는 "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

urlpatterns = [
    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"),
]

urlpatterns = format_suffix_patterns(urlpatterns)


5. Adding pagination

  • 사용자 및 코드 스니펫에 대한 목록 뷰는 많은 인스턴스를 반환할 수 있으므로 결과에 페이지를 매기고 API 클라이언트가 각 개별 페이지를 단계별로 실행할 수 있도록 해보자.
  • tutorial/settings.py 파일의 맨 아래에 다음의 설정을 추가한다.


tutorial/settings.py
REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 10,
}


  • REST framework의 설정은 모두 REST_FRAMEWORK라는 Python dict 타입의 설정으로 네임스페이스가 지정되어 다른 프로젝트 설정과 잘 구분된다.


6. Browsing the API

  • 웹 브라우저를 열고 탐색 가능한 API로 이동하면 이제 링크를 따라가면서 API를 탐색할 수 있다.

References