На данный момент отношения в нашем 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. Существует несколько разных способов, которые мы могли бы выбрать для представления отношения:
- использование первичных ключей;
- использование ссылок между сущностями;
- используя уникальные поля в связанных сущностях;
- используя стандартные строковые представления связанных сущностей;
- вкладывания связанных сущностей внутрь родительских;
- какая-то своя реализация.
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'
в любом случае, вне зависимости от того, что было запрошено.
Если мы собираемся использовать ссылочно связанный 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, пользуясь ссылками.
Так же мы можем увидеть ссылки 'highlight'
у объектов сниппетов, которые будут возвращать вам HTML представление подсвеченного кода.
В 6 уроке этого руководства мы посмотрим, как можно использовать наборы представлений(ViewSets
) и матршрутизаторы (Routers
), чтобы уменьшить количество кода, необходимого для построения API.