Spec-Zone.ru › Django REST Framework

Урок 1: Сериализация

Введение

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

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

Примечание: Код для этого урока доступен в репозитории encode/rest-framework-tutorial на GitHub. Не стесняйтесь клонировать репозиторий и увидеть код в действии.

Настройка новой среды

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

python3 -m venv env
source env/bin/activate

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

pip install django
pip install djangorestframework
pip install pygments  # We'll be using this for the code highlighting

Примечание: Чтобы выйти из виртуальной среды в любое время, просто введите deactivate. Для получения дополнительной информации см. документацию по venv.

Начало работы

Хорошо, мы готовы к программированию. Для начала давайте создадим новый проект для работы.

cd ~
django-admin startproject tutorial
cd tutorial

После этого мы можем создать приложение, которое мы будем использовать для создания простого веб-API.

python manage.py startapp snippets

Нам нужно добавить наше новое приложение snippets и приложение rest_framework в INSTALLED_APPS. Давайте отредактируем файл tutorial/settings.py.

INSTALLED_APPS = [
    ...
    'rest_framework',
    'snippets',
]

Хорошо, мы готовы к работе.

Создание модели для работы

В целях этого урока мы начнём с создания простой модели Snippet, которая используется для хранения фрагментов кода. Откройте и отредактируйте файл snippets/models.py. Примечание: Хорошие программистские практики включают комментарии. Хотя вы найдёте их в нашей версии кода для этого урока в репозитории, мы их здесь опустили, чтобы сконцентрироваться на самом коде.

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)

    class Meta:
        ordering = ['created']

Нам также нужно создать начальную миграцию для нашей модели фрагментов и синхронизировать базу данных впервые.

python manage.py makemigrations snippets
python manage.py migrate snippets

Создание класса Serializer

Первое, что нам нужно сделать для начала работы с нашим веб-API, — это предоставить способ сериализации и десериализации экземпляров фрагментов в такие представления, как json. Мы можем сделать это, объявив сериализаторы, которые работают очень похоже на формы Django. Создайте файл в каталоге snippets с именем serializers.py и добавьте следующее.

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

Первая часть класса сериализатора определяет поля, которые сериализуются/десериализуются. Методы create() и update() определяют, как создаются или изменяются полноценные экземпляры при вызове serializer.save().

Класс сериализатора очень похож на класс Django Form, и включает в себя аналогичные флаги валидации для различных полей, такие как required, max_length и default.

Флаги полей также могут контролировать, как сериализатор должен отображаться в определённых ситуациях, таких как при рендеринге в HTML. Флаг {'base_template': 'textarea.html'} выше эквивалентен использованию widget=widgets.Textarea в классе Django Form. Это особенно полезно для управления тем, как должен отображаться API с возможностью просмотра, как мы увидим позже в уроке.

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

Работа с сериализаторами

Прежде чем продолжить, мы ознакомимся с использованием нашего нового класса Serializer. Запустим Django Shell.

python manage.py shell

Хорошо, после того как мы разобрались с несколькими импортами, давайте создадим пару фрагментов кода для работы.

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print("hello, world")\n')
snippet.save()

Теперь у нас есть несколько экземпляров фрагментов, с которыми можно поработать. Давайте посмотрим на сериализацию одного из этих экземпляров.

serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

На данном этапе мы преобразовали экземпляр модели в собственные типы данных Python. Для завершения процесса сериализации мы отображаем данные в json.

content = JSONRenderer().render(serializer.data)
content
# b'{"id":2,"title":"","code":"print(\\"hello, world\\")\\n","linenos":false,"language":"python","style":"friendly"}'

Десериализация аналогична. Сначала мы парсим поток в типы данных Python…

import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)

…затем мы восстанавливаем эти типы данных в полностью заполненный экземпляр объекта.

serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}
serializer.save()
# <Snippet: Snippet object>

Обратите внимание на сходство API с работой с формами. Сходство станет ещё более очевидным, когда мы начнём писать представления, которые используют наш сериализатор.

Мы также можем сериализовать наборы запросов вместо экземпляров моделей. Для этого просто добавьте флаг many=True к аргументам сериализатора.

serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [{'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'}, {'id': 3, 'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}]

Использование ModelSerializers

Наш класс SnippetSerializer дублирует много информации, которая также содержится в модели Snippet. Было бы неплохо сделать наш код более лаконичным.

Так же, как Django предоставляет классы Form и классы ModelForm, фреймворк REST включает в себя классы Serializer и классы ModelSerializer.

Давайте рассмотрим рефакторинг нашего сериализатора с помощью класса ModelSerializer. Откройте файл snippets/serializers.py снова и замените класс SnippetSerializer следующим.

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ['id', 'title', 'code', 'linenos', 'language', 'style']

Приятная особенность сериализаторов заключается в том, что вы можете проверить все поля в экземпляре сериализатора, напечатав его представление. Откройте Django shell с python manage.py shell, затем попробуйте следующее:

from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
#    id = IntegerField(label='ID', read_only=True)
#    title = CharField(allow_blank=True, max_length=100, required=False)
#    code = CharField(style={'base_template': 'textarea.html'})
#    linenos = BooleanField(required=False)
#    language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
#    style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...

Важно помнить, что классы ModelSerializer не выполняют никаких особых магических действий, они просто обеспечивают сокращённый способ создания классов сериализаторов:

  • Автоматически определяется набор полей.
  • Простые реализации по умолчанию для методов create() и update().

Написание обычных представлений Django с использованием нашего сериализатора

Давайте посмотрим, как мы можем написать некоторые представления API, используя наш новый класс сериализатора. Пока мы не будем использовать другие функции фреймворка REST, а просто напишем представления как обычные представления Django.

Отредактируйте файл snippets/views.py и добавьте следующее.

from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

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

@csrf_exempt
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 JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

Обратите внимание, что поскольку мы хотим иметь возможность отправлять POST запросы в это представление от клиентов, у которых не будет токена CSRF, нам необходимо пометить представление как csrf_exempt. Это не то, что вы обычно хотите делать, и представления фреймворка REST фактически используют более разумное поведение, чем это, но этого достаточно для наших целей прямо сейчас.

Нам также понадобится представление, соответствующее отдельному фрагменту и позволяющее получать, обновлять или удалять фрагмент.

@csrf_exempt
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

Наконец, нам нужно подключить эти представления. Создайте файл snippets/urls.py:

from django.urls import path
from snippets import views

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>/', views.snippet_detail),
]

Нам также нужно подключить корневой urlconf в файле tutorial/urls.py, чтобы включить URL-адреса нашего приложения фрагментов.

from django.urls import path, include

urlpatterns = [
    path('', include('snippets.urls')),
]

Стоит отметить, что в данный момент мы не обрабатываем несколько краевых случаев должным образом. Если мы отправим некорректные данные json, или если запрос будет выполнен с методом, который представление не обрабатывает, то мы получим ответ с ошибкой 500 "ошибка сервера". Тем не менее, этого достаточно на данный момент.

Тестирование нашей первой попытки создания веб-API

Теперь мы можем запустить примерный сервер, который обслуживает наши фрагменты.

Выйдите из оболочки…

quit()

…и запустите сервер разработки Django.

python manage.py runserver

Validating models...

0 errors found
Django version 5.0, using settings 'tutorial.settings'
Starting Development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

В другом окне терминала мы можем протестировать сервер.

Мы можем протестировать наш API с помощью curl или httpie. Httpie — это удобный http-клиент, написанный на Python. Давайте установим его.

Вы можете установить httpie с помощью pip:

pip install httpie

Наконец, мы можем получить список всех фрагментов:

http http://127.0.0.1:8000/snippets/ --unsorted

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"
    },
    {
        "id": 3,
        "title": "",
        "code": "print(\"hello, world\")",
        "linenos": false,
        "language": "python",
        "style": "friendly"
    }
]

Или мы можем получить определённый фрагмент, указав его идентификатор:

http http://127.0.0.1:8000/snippets/2/ --unsorted

HTTP/1.1 200 OK
...
{
    "id": 2,
    "title": "",
    "code": "print(\"hello, world\")\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

Аналогично, вы можете получить тот же JSON, посетив эти URL-адреса в веб-браузере.

Где мы сейчас

Пока всё идёт хорошо. У нас есть API сериализации, который очень похож на API форм Django, и некоторые обычные представления Django.

Наши представления API на данный момент не выполняют никаких особых действий, помимо предоставления ответов json, и существуют некоторые необработанные краевые случаи обработки ошибок, которые мы хотели бы исправить, но это функционирующий веб-API.

Мы увидим, как улучшить его во второй части урока.

Copyright © 2011–present Encode OSS Ltd.
Licensed under the BSD License.
https://www.django-rest-framework.org/tutorial/1-serialization/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API