Skip to content

Latest commit

 

History

History
168 lines (119 loc) · 11.6 KB

relations-and-hyperlinks.md

File metadata and controls

168 lines (119 loc) · 11.6 KB

Урок 5: Отношения и ссылочные связи

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

Создание корневой точки входа для API

На данный момент мы имеем точки входта для snippets и users, но у нас нет единой точки входа для 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)
    })

Обратите внимание на две вещи. Во-первых, мы используем функцию reverse() из состава DRF, чтобы вернуть полностью сформированные URL-ы. Во-вторых, шаблоны URL определены с помощью удобных имен, которые мы опишем позднее в snippets/urls.py.

Создание конечной точки для подсвеченных сниппетов

Еще одна очевидная вешь для нашего API, которая не реализована - конечная точка для подсвеченного кода.

В отличие от наших других конечных точек, мы не хотим использовать здесь JSON, а, наоборот, хотим выводить HTML. Есть 2 стиля вывода HTML, реализованых в DRF: одна для рендеринга HTML, другая - для вывода предварительно срендеренного HTML. Мы будем использовать второй способ.

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

Вместо того, чтобы использовать встроенное представление, мы создадим базовый класс для представления объектов и определим собственный метод .get(). Добавьте в ваш snippets/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)

Как и всегда, мы должны добавить наши новые представления в конфигурацию URL-ов. Добавьте шаблон URL-а в snippets/urls.py:

url(r'^$', views.api_root),
And then add a url pattern for the snippet highlights:

url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),

Связывание ссылками нашего API

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

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

DRF реализует все эти способы и может применять их как к прямым, так и к обратным связям или применять их к собственным менеджерам объектов, какие как встроенные внешние ключи.

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

Отличие класса HyperlinkedModelSerializer от класса ModelSerializer заключается в следующем:

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

Поскольку мы включили указатели формата в шаблоны URL, такие как '.json', нам необходимо пометить поле подсвеченного кода, как возвращающее '.html' в любом случае, вне зависимости от того, что было запрошено.

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

Если мы собираемся использовать ссылочно связанный API, мы должны убедиться, что шаблоны URL у нас названы. Давайте посмотрим, какие шаблоны URL мы должны назвать. If we're going to have a hyperlinked API, we need to make sure we name our URL patterns. Let's take a look at which URL patterns we need to name.

Корень нашего API ссылается на 'user-list' и 'snippet-list'. Наш сериализатор сниппета включает поле, которое ссылается на 'snippet-highlight'. Сериализатор пользователя включает поле, которое ссылается на 'snippet-detail'. Наши сериализаторы сниппетов и пользователей содержат поле, которое ссылается на '{название_модели}-detail', которое, в нашем случае будет 'snippet-detail' и 'user-detail'. После добавления всех этих имен в нашу конфигурацию URL, snippets/urls.py должен выглядеть вот так:

from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

# API endpoints
urlpatterns = format_suffix_patterns([
    url(r'^$', views.api_root),
    url(r'^snippets/$',
        views.SnippetList.as_view(),
        name='snippet-list'),
    url(r'^snippets/(?P<pk>[0-9]+)/$',
        views.SnippetDetail.as_view(),
        name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
        views.SnippetHighlight.as_view(),
        name='snippet-highlight'),
    url(r'^users/$',
        views.UserList.as_view(),
        name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$',
        views.UserDetail.as_view(),
        name='user-detail')
])

# Login and logout views for the browsable API
urlpatterns += [
    url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),
]

Добавляем постраничный вывод

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

Мы можем изменить стандартное поведение, изменив settings.py. Добавьте следующую настройку:

REST_FRAMEWORK = {
    'PAGE_SIZE': 10
}

Помните, все настройки DRF находятся в одном словаре с названием 'REST_FRAMEWORK', что позволяет их отделить от остальных настроек проекта.

Так же мы можем настроить стиль постраничного вывода, но в данном случае мы воспользуемся стандартными настройками.

Просмотр API

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

Так же мы можем увидеть ссылки 'highlight' у объектов сниппетов, которые будут возвращать вам HTML представление подсвеченного кода.

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