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

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

На данный момент мы имеем точки входа для snippets и users, но у нас нет единой точки входа для API. Для ее создания мы используем обычную функцию-представление с декоратором @api_view, который мы видели раньше. Добавьте в snippets/views.py:
1
from rest_framework.decorators import api_view
2
from rest_framework.response import Response
3
from rest_framework.reverse import reverse
4
5
6
@api_view(['GET'])
7
def api_root(request, format=None):
8
return Response({
9
'users': reverse('user-list', request=request, format=format),
10
'snippets': reverse('snippet-list', request=request, format=format)
11
})
Copied!
Обратите внимание на две вещи. Во-первых, мы используем функцию reverse() из состава DRF, чтобы вернуть полностью сформированные URL-ы. Во-вторых, шаблоны URL определены с помощью удобных имен, которые мы опишем позднее в snippets/urls.py.

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

Еще одна очевидная вешь для нашего API, которая не реализована - конечная точка для подсвеченного кода.
В отличие от наших других конечных точек, мы не хотим использовать здесь JSON, а, наоборот, хотим выводить HTML. Есть 2 стиля вывода HTML, реализованых в DRF: одна для рендеринга HTML, другая - для вывода предварительно срендеренного HTML. Мы будем использовать второй способ.
Следующая вещь, которую мы должны рассмотреть, когда создаем подсвеченный код - у нас нет конкретного встроенного представления, которое мы можем использовать. Мы не возвращаем экземпляр, а должны возвращать свойство объекта.
Вместо того, чтобы использовать встроенное представление, мы создадим базовый класс для представления объектов и определим собственный метод .get(). Добавьте в ваш snippets/views.py:
1
from rest_framework import renderers
2
from rest_framework.response import Response
3
4
class SnippetHighlight(generics.GenericAPIView):
5
queryset = Snippet.objects.all()
6
renderer_classes = [renderers.StaticHTMLRenderer]
7
8
def get(self, request, *args, **kwargs):
9
snippet = self.get_object()
10
return Response(snippet.highlighted)
Copied!
Как и всегда, мы должны добавить наши новые представления в конфигурацию URL-ов. Добавьте шаблон URL-а в snippets/urls.py:
1
path('', views.api_root),
Copied!
А затем добавьте шаблон URL-адреса для выделения фрагмента:
1
path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view()),
Copied!

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

Работа с отношениями между сущностями является одним из наиболее сложных аспектов проектирования API. Существует несколько разных способов, которые мы могли бы выбрать для представления отношения:
  • использование первичных ключей;
  • использование ссылок между сущностями;
  • используя уникальные поля в связанных сущностях;
  • используя стандартные строковые представления связанных сущностей;
  • вкладывания связанных сущностей внутрь родительских;
  • какая-то своя реализация.
DRF реализует все эти способы и может применять их как к прямым, так и к обратным связям или применять их к собственным менеджерам объектов, какие как встроенные внешние ключи.
В данном случае мы будем использовать ссылочные связи между сущностями. Для этого мы изменим наши сериализаторы, указав HyperlinkedModelSerializer в качестве родительского класса, вместо ModelSerializer.
Отличие класса HyperlinkedModelSerializer от класса ModelSerializer заключается в следующем:
  • по умолчанию он не включает в себя поле первичного ключа;
  • он включае в себя поле адреса, используя HyperlinkedIdentityField;
  • связи используют HyperlinkedRelatedField вместо PrimaryKeyRelatedField;
Мы можем просто переписать наши существующие сериализаторы для использования ссылок. Измените ваш snippets/serializers.py:
1
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
2
owner = serializers.ReadOnlyField(source='owner.username')
3
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
4
5
class Meta:
6
model = Snippet
7
fields = ['url', 'id', 'highlight', 'owner',
8
'title', 'code', 'linenos', 'language', 'style']
9
10
11
class UserSerializer(serializers.HyperlinkedModelSerializer):
12
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)
13
14
class Meta:
15
model = User
16
fields = ['url', 'id', 'username', 'snippets']
Copied!
Обратите внимание, что мы также добавили поле highlight. Это поле - того же типа, что и поле url, за исключением того, что указывает на шаблон URL 'snippet-highlight', вместо шаблона'snippet-detail'.
Поскольку мы включили указатели формата в шаблоны URL, такие как '.json', нам необходимо пометить поле подсвеченного кода, как возвращающее '.html' в любом случае, вне зависимости от того, что было запрошено.

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

Если мы собираемся использовать ссылочно связанный API, мы должны убедиться, что шаблоны URL у нас названы. Давайте посмотрим, какие шаблоны URL мы должны назвать. Если мы собираемся иметь API с гиперссылками, нам нужно убедиться, что мы даем имена нашим шаблонам URL. Давайте посмотрим, какие шаблоны URL нам нужно назвать.
  • Корень нашего API ссылается на 'user-list' и 'snippet-list'.
  • Наш сериализатор сниппета включает поле, которое ссылается на 'snippet-highlight'.
  • Сериализатор пользователя включает поле, которое ссылается на 'snippet-detail'.
  • Наши сериализаторы сниппетов и пользователей содержат 'url' поле, которое ссылается на '{название_модели}-detail', которое, в нашем случае будет 'snippet-detail' и 'user-detail'.
  • После добавления всех этих имен в нашу конфигурацию URL, snippets/urls.py должен выглядеть вот так:
1
from django.urls import path
2
from rest_framework.urlpatterns import format_suffix_patterns
3
from snippets import views
4
5
# API endpoints
6
urlpatterns = format_suffix_patterns([
7
path('', views.api_root),
8
path('snippets/',
9
views.SnippetList.as_view(),
10
name='snippet-list'),
11
path('snippets/<int:pk>/',
12
views.SnippetDetail.as_view(),
13
name='snippet-detail'),
14
path('snippets/<int:pk>/highlight/',
15
views.SnippetHighlight.as_view(),
16
name='snippet-highlight'),
17
path('users/',
18
views.UserList.as_view(),
19
name='user-list'),
20
path('users/<int:pk>/',
21
views.UserDetail.as_view(),
22
name='user-detail')
23
])
Copied!

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

Представления, возвращающие список пользователей и сниппетов кода могут разрастись и отдавать огромное количество объектов, поэтому, неплохо было бы включить постраничный вывод результатов и разрешить клиентам проходить по ним с помощью отдельных страниц.
Мы можем изменить стандартное поведение, изменив settings.py. Добавьте следующую настройку:
1
REST_FRAMEWORK = {
2
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
3
'PAGE_SIZE': 10
4
}
Copied!
Помните, все настройки DRF находятся в одном словаре с названием 'REST_FRAMEWORK', что позволяет их отделить от остальных настроек проекта.
Так же мы можем настроить стиль постраничного вывода, но в данном случае мы воспользуемся стандартными настройками.

Просмотр API

Если мы откроем браузер и перейдем в браузерную версию API, мы увидим, что теперь мы можем "гулять" по API, пользуясь ссылками.
Так же мы можем увидеть ссылки 'highlight' у объектов сниппетов, которые будут возвращать вам HTML представление подсвеченного кода.
В 6 уроке этого руководства мы посмотрим, как можно использовать наборы представлений(ViewSets) и матршрутизаторы (Routers), чтобы уменьшить количество кода, необходимого для построения API.
Last modified 8mo ago