Урок 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/