September 6, 2019

DRF - Запросы и ответы

Django rest framework - запросы и ответы

С этого момента мы начнем изучать ядро REST. Давайте рассмотрим пару основных элментов

Объекты запроса

Платформа REST представляет объект Request, который расширяет обычный HttpRequest и обеспечивает более гибкий анализ запросов. Основная функциональность объекта Request - это атрибут request.data, который похож на request.POST, но более полезен для работы с веб-API.

request.POST  # Обрабатывает только данные формы.  Работает только с  методом POST
request.data  # Обрабатывает произвольные данныу.  Работает с методоами 'POST', 'PUT' и 'PATCH'.

Объекты ответа

Платформа REST также представляет объект Response, который является типом TemplateResponse, который принимает контент и использует согласование содержимого для обеспечения правильного вида содержимого для возврата клиенту.

return Response(data) # Рендерится в тип содержимого по запросу клиента.

Коды состояния

Использование числовых кодов состояния HTTP в ваших views не всегда очевидно. К тому же легко не заметить, если вы ошиблись с кодом ошибки. Платформа REST предоставляет более явные идентификаторы для каждого кода состояния, например HTTP_400_BAD_REQUEST в модуле состояния. Это хорошая практика, использовать их, а не числовые идентификаторы.

Обертки views API

Платформа REST предоставляет две обертки, которые можно использовать для написания views API.

  1. Декоратор @api_view для работы с view на основе функций.
  2. Класс APIView для работы с представлениями на основе классов.

Эти обертки предоставляют несколько функциональных возможностей, таких как обеспечение получения экземпляров Request в вашем views и добавление контекста к объектам Response, чтобы можно было выполнить согласование содержимого.

Они также обеспечивают возвращение ответов 405 Method Not Allowed, когда это необходимо, и обработку любого исключения ParseError, которое возникает при доступе к request.data с некорректным вводом.

Собираем вместе всё вышесказанное

Хорошо, давайте продолжим и начнем использовать новые компоненты, чтобы написать несколько view. Нам больше не нужен наш класс JSONResponse в views.py, поэтому удалите его.

Теперь мы можем начать рефакторинг нашего views.py

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):
    """
    Показать все сниппеты или создать новый.
    """
    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)

Наш views стал намного лучше, по сравнению с предыдущим примером. Кода стало меньше, и теперь он очень похож на работу с Forms API. Мы также используем именованные коды состояния, что делает значения ответов более читаемыми.

Вот views для отдельного сниппета в модуле views.py:

@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    Получить, обновить или удалить сниппет.
    """
    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)

Все это должно казаться очень знакомым - оно не сильно отличается от работы с обычными views Django.

Обратите внимание, что мы больше не привязываем наши запросы или ответы к типу контента. request.data может обрабатывать входящие запросы json, но также может обрабатывать другие форматы. Точно так же мы возвращаем объекты Response с данными, но позволяем платформе REST преобразовать ответ в правильный тип контента.

Добавление необязательных окончаний формата в наши URL

Наши ответы больше не привязаны к одному типу данных. Давайте добавим поддержку окончаний формата к нашим API endpoint.

Использование окончаний формата дает нам URL-адреса, которые явно ссылаются на данный формат, и означает, что наш API сможет обрабатывать такие URL-адреса, как http://example.com/api/items/4.json.

Начнем с добавления аргумента формата к обоим views, вот так:

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 из командной строки, как мы это делали в 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
http http://127.0.0.1:8000/snippets.api   # Browsable API

Точно так же мы можем контролировать формат отправляемого запроса, используя заголовок Content-Type:

# POST с использованием данных формы
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 с использованием 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.

Что дальше?

Далее мы начнем использовать views на основе классов и увидим, как общие пред��тавления уменьшают объем кода, который нам нужно написать.