September 10, 2019

DRF - Отношения и гиперссылки API

На данный момент отношения в нашем API представлены с использованием первичных ключей. В этой части руководства мы улучшим целостность и обнаруживаемость нашего API, вместо этого используя гиперссылки для отношений.

Создание endpoint для корня нашего API

Сейчас у нас есть endpoint для «сниппетов» и «пользователей», но у нас нет единой точки входа в наш API.

Чтобы создать его, мы будем использовать обычный 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 для возврата полностью определенных URL; во-вторых, шаблоны URL идентифицируются по вспомогательным именам, которые мы объявим позже в нашем snippets/urls.py.

Создание конечной точки для выделенных фрагментов

Другой очевидной вещью, которая все еще отсутствует в нашем API-интерфейсе для вставки, является код, подсвечивающий конечные точки. В отличие от всех других наших конечных точек API, мы не хотим использовать JSON, а просто представляем представление HTML.

Существует два стиля рендеринга HTML, предоставляемых REST: один для работы с HTML с использованием шаблонов, другой для работы с предварительно визуализированным HTML. Второй рендерер - это тот, который мы хотели бы использовать для этой конечной точки.

Еще одна вещь, которую мы должны учитывать при создании представления подсветки кода, - это то, что нет никакого конкретного конкретного универсального представления, которое мы можем использовать. Мы не возвращаем экземпляр объекта, а вместо этого свойство экземпляра объекта. Вместо использования конкретного универсального представления мы будем использовать базовый класс для представления экземпляров и создадим наш собственный метод .get ().

В ваши сниппеты / 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. Мы добавим шаблон url для нашего нового корня API в snippets/urls.py:

path('', views.api_root),

А затем добавьте шаблон URL для подсветки фрагмента:

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

Гиперссылка нашего API

Работа с отношениями между сущностями является одним из наиболее сложных аспектов проектирования веб-API. Существует несколько различных способов представления отношений:

  • Использование первичных ключей.
  • Использование гиперссылок между сущностями.
  • Использование уникального идентифицирующего поля слага в связанной сущности.
  • Использование строкового представления по умолчанию для связанной сущности.
  • Вложение связанной сущности в родительское представление.
  • Некоторые другие пользовательские представления.

Платформа REST поддерживает все эти стили и может применять их через прямые или обратные отношения или применять их к пользовательским менеджерам, таким как общие внешние ключи.

В этом случае мы хотели бы использовать стиль гиперссылки между сущностями. Для этого мы модифицируем наши сериализаторы, чтобы расширить HyperlinkedModelSerializer вместо существующего ModelSerializer.

HyperlinkedModelSerializer имеет следующие отличия от ModelSerializer:

  • Он не включает поле id по умолчанию.
  • Он включает в себя поле URL, используя HyperlinkedIdentityField.
  • В отношениях используется HyperlinkedRelatedField вместо PrimaryKeyRelatedField.

Мы можем легко переписать наши существующие сериализаторы для использования гиперссылок. В ваши 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, за исключением того, что оно указывает на шаблон URL 'snippet-highlight' вместо шаблона URL 'snippet-detail'.

Поскольку мы включили URL-адреса с суффиксами формата, такие как «.json», мы также должны указать в поле выделения, что для всех возвращаемых гиперссылок с суффиксами формата следует использовать суффикс «.html».

Убедитесь, что наши шаблоны URL названы

Если у нас будет API с гиперссылками, нам нужно убедиться, что мы назовем наши шаблоны URL. Давайте посмотрим, какие шаблоны URL нам нужно назвать.

  • Корень нашего API ссылается на «список пользователей» и «список фрагментов».
  • Наш сериализатор фрагментов содержит поле, которое ссылается на «фрагмент фрагмента».
  • Наш пользовательский сериализатор содержит поле, которое ссылается на «фрагмент кода».
  • Наш сериализатор и пользовательские сериализаторы содержат поля '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')
])

Добавление нумерации страниц

Представления списков для пользователей и фрагменты кода могут в конечном итоге возвращать довольно много экземпляров, поэтому на самом деле мы хотели бы убедиться, что разбили результаты на страницы и позволили клиенту API пройти по каждой отдельной странице.

Мы можем изменить стиль списка по умолчанию, чтобы использовать нумерацию страниц, слегка изменив наш файл tutorial/settings.py.

Добавьте следующий параметр:

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

Обратите внимание, что все настройки в REST frameworks помещены в одно пространство имен словаря с именем REST_FRAMEWORK, что помогает отделить их от других настроек вашего проекта.

Мы могли бы также настроить стиль нумерации страниц, если нам это нужно, но в этом случае мы просто будем придерживаться значения по умолчанию.

Просмотр API

Если мы откроем браузер и перейдем к API с возможностью просмотра, вы обнаружите, что теперь вы можете обойти API, просто перейдя по ссылкам. Вы также сможете увидеть ссылки «выделение» на экземплярах фрагмента, которые приведут вас к выделенным HTML-представлениям кода.

В шестой части руководства мы рассмотрим, как мы можем использовать ViewSets и Routers, чтобы уменьшить количество кода, необходимого для создания нашего API.