Курс по Django: https://stepik.org/a/183363
На предыдущем
занятии мы с вами создали простой API-запрос, используя только класс
представления. На этом занятии добавим ключевой компонент Django REST Framework в наш проект
под названием сериализатор (Serializer).
Вначале ответим
на вопрос, что это такое и зачем он вообще нужен? Как я уже отмечал, при
реализации API сайта обмен
данными выполняется посредством определенного формата. Чаще всего используют JSON кодирование,
реже XML. При
необходимости можно описать свой формат обмена данными. Правда, я лично с таким
никогда не сталкивался. В 99% случаях все же применяется JSON. Так вот, роль
сериализатора выполнять конвертирование произвольных объектов языка Python в формат JSON (в том числе
модели фреймворка Django и наборы QuerySet). И, обратно,
из JSON – в
соответствующие объекты Python. (Полагая, что используется JSON в API-запросах).
Чтобы лучше
понять, как реализована сериализация объектов, давайте для примера объявим
некий класс WomenModel (в файле women/serializers.py), который будет
имитировать класс модели фреймворка Django:
class WomenModel:
def __init__(self, title, content):
self.title = title
self.content = content
А далее, класс WomenSerializer, который будет
отвечать за сериализацию (то есть, за преобразование в формат JSON и обратно)
объектов класса WomenModel:
class WomenSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
content = serializers.CharField()
Смотрите, мы
здесь указываем, какие выбирать атрибуты (title и content) из объекта
класса WomenModel для их перевода
в JSON-формат и
обратно, при получении JSON-данных они будут автоматически
распакованы по атрибутам title и content. Причем, здесь
же в сериализаторе мы прописываем проверку (валидацию) данных. Например, поле title
не
должно превышать 255 символов.
Давайте
посмотрим, как все это будет работать. Запишем следующую тестовую функцию:
from rest_framework.renderers import JSONRenderer
def encode():
model = WomenModel('Angelina Jolie', 'Content: Angelina Jolie')
model_sr = WomenSerializer(model)
print(model_sr.data, type(model_sr.data), sep='\n')
json = JSONRenderer().render(model_sr.data)
print(json, type(json), sep='\n')
Здесь создается
объект-модель класса WomenModel, затем, он сериализуется путем
создания объекта класса WomenSerializer, на вход
которого мы передаем объект model. Далее, объект сериализованных данных
преобразуется в обычную JSON-строку и результат выводится в консоль.
Переходим,
теперь, на вкладку «Терминал», запускаем оболочку:
python manage.py shell
импортируем и
запускаем нашу функцию:
from women.serializers import encode
encode()
На выходе видим строки:
{'title': 'Angelina Jolie', 'content': 'Content: Angelina Jolie'}
<class 'rest_framework.utils.serializer_helpers.ReturnDict'>
b'{"title":"Angelina Jolie","content":"Content: Angelina Jolie"}'
<class 'bytes'>
То есть, вначале
у нас получается объект сериализации, а потом уже мы его преобразуем в обычную
байтовую строку. Вот так, на автомате базовый класс Serializer выполнил
преобразование объекта класса WomenModel в формат JSON.
Давайте теперь
посмотрим на обратный процесс преобразование из JSON-строки в
объект. Для этого определим функцию decode:
from rest_framework.parsers import JSONParser
def decode():
stream = io.BytesIO(b'{"title":"Angelina Jolie","content":"Content: Angelina Jolie"}')
data = JSONParser().parse(stream)
serializer = WomenSerializer(data=data)
serializer.is_valid()
print(serializer.validated_data)
Здесь мы, как
бы, читаем из входного потока данные в виде JSON-строки, затем,
подаем все на JSONParser и снова создаем
объект класса WomenSerializer, который
возвратит нам ссылку на упорядоченный словарь. Следующей строчкой проверяем
данные на корректности и выводим в консоль проверенные данные.
Посмотрим, как
все это работает. Снова запустим оболочку:
python manage.py shell
Импортируем
функцию decode и запустим ее:
from women.serializers import decode
decode()
Видим в консоли строку:
OrderedDict([('title', 'Angelina Jolie'), ('content', 'Content: Angelina Jolie')])
Это результат
парсинга исходной JSON-строки в объект OrderedDict языка Python. Подробнее о
работе процесса сериализации можно почитать на странице официальной
документации:
https://www.django-rest-framework.org/api-guide/serializers/
Давайте теперь
применим эту технику непосредственно для наших классов моделей. Вначале
определим сериализатор с тем же набором атрибутов, что и класс модели Women:
class WomenSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
content = serializers.CharField()
time_create = serializers.DateTimeField()
time_update = serializers.DateTimeField()
is_published = serializers.BooleanField(default=True)
cat_id = serializers.IntegerField()
Обратите
внимание, что здесь присутствует атрибут cat_id как поле с
целочисленным значением, так как на вход сериализатора будет передаваться
объект, где внешний ключ cat уже определен и сформирован как
локальный атрибут cat_id, содержащий целое значение.
После этого
переходим в файл women/views.py и в классе WomenAPIView
применим этот сериализатор:
class WomenAPIView(APIView):
def get(self, request):
w = Women.objects.all()
return Response({'posts': WomenSerializer(w, many=True).data})
def post(self, request):
post_new = Women.objects.create(
title=request.data['title'],
content=request.data['content'],
cat_id=request.data['cat_id']
)
return Response({'post': WomenSerializer(post_new).data})
В методе get() мы теперь
просто читаем все записи, представленные объектом QuerySet, и передаем эту
коллекцию в сериализатор, дополнительно указываем параметр many=True, так как на
выходе нужно сформировать список из записей таблицы (по умолчанию формируется
одна запись). Далее, класс Response «знает» как обрабатывать объект
сериализованных данных, превращая их в байтовую JSON-строку.
В методе post() происходит
примерно то же самое, только без параметра many, так как
возвращается всего одна запись.
Давайте
посмотрим, как это будет работать. Запустим тестовый веб-сервер:
python manage.py
runserver
Перейдем в
программу Postman и выполним GET-запрос для
адреса:
http://127.0.0.1:8000/api/v1/womenlist/
Как видим,
возвращается JSON-строка со
списком наших записей. Выполним теперь POST-запрос для того
же адреса. И теперь возвратились данные, содержащие одну новую добавленную
запись. Причем, мы здесь также видим значения времени, которое было
автоматически подставлено нашим сериализатором.
Однако, если в POST-запросе
передать неверные данные, то возникнет ошибка на этапе добавления записи в БД.
Мы можем обработать этот момент с помощью нашего сериализатора, следующим
образом:
def post(self, request):
serializer = WomenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
post_new = Women.objects.create(
title=request.data['title'],
content=request.data['content'],
cat_id=request.data['cat_id']
)
return Response({'post': WomenSerializer(post_new).data})
Здесь мы
формируем объект сериализации на основе принятых от клиента данных, а затем,
вызываем метод is_valid() и указываем параметр raise_exception=True для генерации
исключений (ошибок), которые будут передаваться в виде JSON-строки клиенту.
Давайте
протестируем этот функционал. Снова передадим неверные данные (например, уберем
title) и в ответе
увидим:
{"title":["Обязательное
поле."],"time_create":["Обязательное
поле."],"time_update":["Обязательное поле."]}
По идее, поля time_create и time_update формируются
автоматически и нам их передавать не нужно. Поэтому сообщения, что они
обязательны здесь не совсем корректны. Они, конечно обязательны, но передавать
клиенту их не нужно. Чтобы сериализатор корректно обрабатывал эти поля, в их
определении нужно добавить аргумент read_only=True:
class WomenSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
content = serializers.CharField()
time_create = serializers.DateTimeField(read_only=True)
time_update = serializers.DateTimeField(read_only=True)
is_published = serializers.BooleanField(default=True)
cat_id = serializers.IntegerField()
Теперь, при
отправке неверного запроса мы увидим, что требуется только одно поле title:
{"title":["Обязательное
поле."]}
Давайте, ради
интереса уберем параметр raise_exception=True и снова отправим
те же данные. Как видите, появляется HTML-страница от
фреймворка Django, сообщающая об
ошибке при добавлении записи в БД. То есть, клиент получает уже не JSON-строку, а HTML-документ, что,
в общем то, обычно считается неверным при реализации API. Поэтому всегда
при валидации прописываем аргумент raise_exception=True.
Итак, на этом
занятии мы с вами создали сериализатор на основе базового класса Serializer и научились его
применять для кодирования и декодирования данных моделей.
Курс по Django: https://stepik.org/a/183363