Учебник 2: Запросы и ответы¶
С этого момента мы действительно начнем освещать суть фреймворка REST. Давайте представим несколько основных строительных блоков.
Объекты запроса¶
Фреймворк REST вводит объект Request
, который расширяет обычный HttpRequest
, и обеспечивает более гибкий разбор запроса. Основной функциональностью объекта Request
является атрибут request.data
, который похож на request.POST
, но более полезен для работы с Web API.
request.POST # Only handles form data. Only works for 'POST' method.
request.data # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods.
Объекты реагирования¶
Рамка REST также вводит объект Response
, который является типом TemplateResponse
, который принимает нерендерированное содержимое и использует согласование содержимого для определения правильного типа содержимого для возврата клиенту.
return Response(data) # Renders to content type as requested by the client.
Коды состояния¶
Использование числовых кодов состояния HTTP в ваших представлениях не всегда обеспечивает очевидное чтение, и легко не заметить, если вы ошиблись с кодом ошибки. REST framework предоставляет более явные идентификаторы для каждого кода состояния, такие как HTTP_400_BAD_REQUEST
в модуле status
. Хорошая идея - использовать их повсеместно, а не использовать числовые идентификаторы.
Обертывание представлений API¶
Фреймворк REST предоставляет две обертки, которые можно использовать для написания представлений API.
Декоратор
@api_view
для работы с представлениями, основанными на функциях.Класс
APIView
для работы с представлениями, основанными на классах.
Эти обертки предоставляют несколько функциональных возможностей, таких как обеспечение получения экземпляров Request
в вашем представлении и добавление контекста к объектам Response
, чтобы можно было выполнить согласование содержимого.
Обертки также обеспечивают такое поведение, как возврат ответов 405 Method Not Allowed
, когда это необходимо, и обработку любых исключений ParseError
, возникающих при обращении к request.data
с неправильно сформированным вводом.
Собираем все вместе¶
Итак, давайте начнем использовать эти новые компоненты, чтобы немного рефакторизовать наши представления.
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@api_view(['GET', 'POST'])
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Наше представление экземпляра является улучшением по сравнению с предыдущим примером. Оно немного лаконичнее, и код теперь очень похож на тот, который мы использовали при работе с Forms API. Мы также используем именованные коды состояния, что делает значения ответов более очевидными.
Вот вид для отдельного фрагмента в модуле views.py
.
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Все это должно казаться очень знакомым - это не сильно отличается от работы с обычными представлениями Django.
Обратите внимание, что мы больше не привязываем наши запросы или ответы к определенному типу содержимого. request.data
может обрабатывать входящие запросы json
, но может обрабатывать и другие форматы. Точно так же мы возвращаем объекты ответа с данными, но позволяем REST-фреймворку преобразовать ответ в нужный нам тип содержимого.
Добавление необязательных суффиксов формата к нашим URL-адресам¶
Чтобы воспользоваться тем фактом, что наши ответы больше не привязаны к одному типу содержимого, давайте добавим поддержку суффиксов формата в наши конечные точки API. Использование суффиксов формата дает нам URL, которые явно ссылаются на определенный формат, и означает, что наш API сможет обрабатывать такие URL, как http://example.com/api/items/4.json.
Начните с добавления аргумента в виде ключевого слова format
к обоим представлениям, как показано ниже.
def snippet_list(request, format=None):
и
def snippet_detail(request, pk, format=None):
Теперь немного обновите файл snippets/urls.py
, чтобы добавить набор format_suffix_patterns
в дополнение к существующим URL.
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Нам не обязательно добавлять эти дополнительные шаблоны url, но это дает нам простой и чистый способ ссылаться на определенный формат.
Как он выглядит?¶
Перейдите к тестированию API из командной строки, как мы это делали в tutorial part 1. Все работает примерно одинаково, хотя мы получили более приятную обработку ошибок при отправке некорректных запросов.
Мы можем получить список всех сниппетов, как и раньше.
http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
...
[
{
"id": 1,
"title": "",
"code": "foo = **"bar**"**n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print(**"hello, world**")**n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
Мы можем контролировать формат ответа, который мы получаем обратно, либо с помощью заголовка Accept
:
http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
Или путем добавления суффикса формата:
http http://127.0.0.1:8000/snippets.json # JSON suffix
http http://127.0.0.1:8000/snippets.api # Browsable API suffix
Аналогично, мы можем контролировать формат отправляемого запроса, используя заголовок Content-Type
.
# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"id": 3,
"title": "",
"code": "print(123)",
"linenos": false,
"language": "python",
"style": "friendly"
}
# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"
{
"id": 4,
"title": "",
"code": "print(456)",
"linenos": false,
"language": "python",
"style": "friendly"
}
Если вы добавите переключатель --debug
к вышеприведенным запросам http
, вы сможете увидеть тип запроса в заголовках запросов.
Теперь перейдите и откройте API в веб-браузере, посетив http://127.0.0.1:8000/snippets/.
Удобство просмотра¶
Поскольку API выбирает тип содержимого ответа на основе запроса клиента, он по умолчанию возвращает представление ресурса в формате HTML, когда ресурс запрашивается веб-браузером. Это позволяет API возвращать полностью доступное для веб-браузера представление HTML.
Наличие веб-браузерного API - это огромный выигрыш в удобстве использования, он значительно упрощает разработку и использование вашего API. Это также значительно снижает барьер для входа в систему для других разработчиков, желающих ознакомиться с вашим API и работать с ним.
Дополнительную информацию о функции API с возможностью просмотра и ее настройке см. в теме browsable api.