Сделаем простую сериализацию как в официальном сайте django-rest-framework.org. Оригинал страницы находится на странице Tutorial 1: Serialization. Я решил перевести статью на русский язык. Мы пойдём по простому пути. Этот путь покажет различные компоненты, которые составляют Rest framework. Это даст вам полное представление о том, как все сочетается и работает.
Для начала создадим виртуальную среду для работы нашего апи.
python3 -m venv env
source env/bin/activate
Теперь, когда мы находимся в виртуальной среде, мы можем установить наши пакеты.
# установка django
pip install django
# установка djangorestframework
pip install djangorestframework
# для визуального выделения кода
pip install pygments
Создайте новый проект для работы. Если у вас нет tutorial, то обязательно создадите этот раздел приложения.
cd ~
django-admin startproject tutorial
cd tutorial
Создадим новый раздел для снипетов.
python manage.py startapp snippets
Добавляю раздел в работающие приложения snippets. Без этой настройки работать снипеты не будут.
# settings.py
INSTALLED_APPS = (
...
'rest_framework',
'snippets.apps.SnippetsConfig',
)
Для целей этого урока мы начнем с создания простой модели Snippet (фрагмент), который используется для хранения фрагментов кода. Отредактируйте snippets/models.py файл. Примечание: хорошая практика программирования включает комментарии. Основная масса комментариев находится в нашей версии репозитория этого учебника. Мы опустили их здесь, чтобы сосредоточиться на самом коде.
#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
Первое, что нам нужно начать с нашего веб-API, - это предоставить способ сериализации и десериализации экземпляров фрагмента в представления в формате json. Мы можем сделать это, объявив сериализаторы, которые работают как формы Django. Создайте файл в каталоге snippets с именем serializers.py и добавьте следующее.
# 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
Первая часть класса serializer определяет поля, которые сериализуются / десериализуются. Методы create() и update() определяют, как создаются или изменяются полноценные экземпляры при вызове serializer.save().
Класс сериализатора очень похож на класс формы Django и включает похожие флаги проверки в различных полях, таких как required, max_length и default.
Флаги полей также могут управлять отображением сериализатора в определенных обстоятельствах, например при отрисовке в HTML. {'Base_template': 'textarea.html'} флаг выше эквивалентен использованию widget=widgets.Textarea в классе Form Django. Это особенно полезно для управления тем, как должен отображаться просматриваемый API, как мы увидим позже в учебнике.
Мы также можем сэкономить время, используя класс ModelSerializer, как мы увидим позже, но пока мы сохраним наше определение сериализатора явно.
Прежде чем мы пойдем дальше, мы познакомимся с использованием нашего нового класса сериализаторов. Давайте проникнем в оболочку Django.
#консоль django
python manage.py shell
Хорошо, как только у нас будет несколько импортов библиотек, давайте создадим пару фрагментов кода для работы.
#консоль django
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()
Теперь у нас есть несколько примеров фрагментов, чтобы играть. Давайте посмотрим на сериализацию одного из этих примеров.
#консоль django
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
На данный момент мы перевели экземпляр модели в собственные типы данных Python. Чтобы завершить процесс сериализации, мы визуализируем данные в json.
#консоль django
content = JSONRenderer().render(serializer.data)
content
# b'{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'
Десериализация аналогична. Сначала мы парсим поток в собственные типы данных Python...
#консоль django
import io
stream = io.BytesIO(content)
data = JSONParser().parse(stream)
...затем мы восстанавливаем эти собственные типы данных в полностью заполненный экземпляр объекта.
#консоль django
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
#
Обратите внимание, насколько похож API на работу с формами. Сходство должно стать еще более очевидным, когда мы начнем писать представления, использующие наш сериализатор.
Мы также можем сериализовать queryset вместо экземпляров модели. Для этого мы просто добавляем флаг many=True в аргументы сериализатора.
#консоль django
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
Наш класс SnippetSerializer реплицирует много информации, которая также содержится в Snippet модели. Было бы неплохо, если бы мы могли сделать наш код более кратким.
Точно так же, как Django предоставляет классы Form и классы ModelForm, Rest framework включает классы Serializer и ModelSerializer.
Давайте рассмотрим рефакторинг нашего сериализатора с использованием класса ModelSerializer. Открыть файл snippets/serializers.py снова и замените класс SnippetSerializer следующим.
#snippets/serializers.py
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
Одним из хороших свойств сериализаторов является то, что вы можете проверить все поля в экземпляре сериализатора, распечатав его представление. Откройте оболочку Django с помощью python manage.py shell, затем попробуйте следующее:
#django 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 не делают ничего особенно волшебного, они просто ярлык для создания классов сериализатора:
Давайте посмотрим, как мы можем написать некоторые представления API, используя наш новый класс сериализатора. На данный момент мы не будем использовать другие функции Rest framework, мы просто напишем представления как обычные представления Django.
Изменить snippets/views.py файл и добавьте следующее.
# 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)
Обратите внимание, что, поскольку мы хотим иметь возможность публиковать это представление от клиентов, у которых не будет токена CSRF, нам нужно пометить представление csrf_exempt. Это не то, что вы обычно хотите делать, и представления Rest framework на самом деле используют более разумное поведение, чем это, но это будет сделано для наших целей прямо сейчас.
Нам также понадобится представление, которое соответствует отдельному фрагменту и может использоваться для извлечения, обновления или удаления фрагмента.
@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)
Наконец, нам нужно подключить эти вьюхи. Я немного отошел от предложенного решения в оригинальной статье и изменил urls. В оригинальной статье сделано по-другому. Создайте файл snippets/urls.py:
# snippets/urls.py
from django.urls import path
from snippets import views
urlpatterns = [
path('', views.snippet_list),
path('<int:pk>/', views.snippet_detail),
]
#в оригинальной статье
#urlpatterns = [
# path('snippets/', views.snippet_list),
# path('snippets/<int:pk>/', views.snippet_detail),
#]
Нам также нужно подключить корневой urlconf, в tutorial/urls.py файл, чтобы включить URL-адреса вашего приложения snippet.
# tutorial/urls.py
from django.urls import path, include
urlpatterns = [
path('snippets', include('snippets.urls')),
]
# в оригинальной статье
#urlpatterns = [
# path('', include('snippets.urls')),
#]
Стоит отметить, что есть несколько крайних случаев, с которыми мы не имеем дело должным образом на данный момент. Если мы отправим неправильный json или если запрос будет сделан с помощью метода, который представление не обрабатывает, мы получим ответ 500 "ошибка сервера". Пусть будет так.
Теперь мы можем запустить пример сервера, который обслуживает наши фрагменты.
Выйдите из консоли django...
quit()
...и запустите сервер разработки Django.
python manage.py runserver
Validating models...
0 errors found
Django version 1.11, using settings 'tutorial.settings'
Development server is running 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/
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:
http http://127.0.0.1:8000/snippets/2/
HTTP/1.1 200 OK
...
{
"id": 2,
"title": "",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
Аналогично, вы можете отобразить тот же json, посетив эти URL-адреса в веб-браузере.
Пока все в порядке, у нас есть API сериализации, которое очень похоже на API Django Forms, и некоторые обычные представления Django.
Наши представления API не делают ничего особенного на данный момент, кроме обслуживания ответов json, и есть некоторые крайние случаи обработки ошибок, которые мы все равно хотели бы очистить, но это функционирующий веб-API.