С этого урока вы начнёте писать REST API на основе Django REST framework (DRF). В общем виде задача для RESTful API выглядит так: в БД проекта есть данные, и мы хотим управлять ими посредством запросов; архитектура API должна базироваться на принципах REST.
Самый простой API должен реализовать как минимум шесть типичных действий для выбранных моделей:
- создание нового объекта;
- получение информации об одном объекте;
- удаление объекта;
- полное замещение существующего объекта;
- изменение одного или нескольких полей объекта;
- получение списка объектов.
Интерфейс для людей: веб-страницы
Ранее в ваших Django-проектах обработка запросов происходила так:
- в приложение прилетает HTTP-запрос,
- запрос передаётся в urls.py и, в зависимости от запрошенного адреса, отправляется в соответствующую view-функцию или view-класс;
- view-функция или view-класс при необходимости взаимодействует с базой данных;
- функция
render
передаёт данные, сгенерированные во View, в HTML-шаблон и создаёт HTML-страницу; - готовая HTML-страница отдаётся в качестве HTTP-ответа.
В результате клиент получает информацию из БД, неразрывно связанную с её оформлением: данные встроены в HTML-страницу, а HTML — это способ представить информацию в формате, удобном для чтения человеком: это «интерфейс для людей».
Интерфейс для машин: JSON
В свою очередь API, это интерфейс для программ, и он работает с чистыми данными, получает и передаёт их в машиночитаемом виде. Информацию, с которой работает API, обрабатывают программы, следовательно, HTML-шаблоны в этой системе не требуются.
При работе API данные должны преобразовываться из формата в формат: информация приходит в полях запроса; эти данные должны быть преобразованы в объекты Python и обработаны, а ответ должен вернуться (согласно архитектуре REST) в формате JSON.
Такое преобразование данных из формата в формат называют сериализацией.
Сериализация
Для начала определим, как именно должны трансформироваться данные. Для экспериментов создадим объект класса Post: по своему смыслу он будет похож на модель Post в Yatube, но это будет не модель, а обычный Python-класс с тремя полями — author
(строка), text
(строка) и pub_date
(дата).
from datetime import datetime
# Класс Post
class Post():
def __init__(self, author, text, pub_date=None):
self.author = author
self.text = text
self.pub_date = pub_date or datetime.now()
# Экземпляр класса Post
post = Post(author='Робинзон Крузо',
text='23 ноября. Закончил работу над лопатой и корытом.',
pub_date ='1659-11-23T18:02:33.123543Z'
)
Этот объект можно представить в виде JSON:
# Экземпляр класса в формате JSON
{
"author": "Робинзон Крузо",
"text": "23 ноября. Закончил работу над лопатой и корытом.",
"pub_date": "1659-11-23T18:02:33.123543Z"
}
Десериализация и валидация
Преобразование данных требуется не только для создания ответа, но и при получении запроса к API.Данные из запроса необходимо проверить на соответствие ожиданиям (валидировать, как при работе с формами) и затем преобразовать в Python-объекты. Трансформация данных из запроса в объекты Питона называется десериализацией.Что будет, если в запросе пришёл такой JSON?
# JSON-объект
{
"author": 1,
"text": "23 ноября. Закончил работу над лопатой и корытом.",
"pub_date": "1659-11-23T18:02:33.123543Z"
}
API должен сериализовать его в экземпляр класса Post, но сперва — валидировать.
В поле author
ожидалась строка, но в объекте JSON пришло целочисленное значение. Это значение не соответствует ожиданиям, JSON не пройдёт валидацию и не будет конвертирован, Python-объект не будет создан. Валидация спасла сервис от ошибки.
И что в результате?
На практике весь цикл работы API, от получения запроса до отправки ответа, выглядит так:
- получение запроса,
- валидация данных из запроса,
- десериализация (преобразование данных из запроса в Python-объекты),
- обработка запроса (какие-то манипуляции с полученными данными или запрос к БД),
- сериализация данных, отправляемых клиенту (из Python в JSON),
- отправка ответа.
Классы-сериализаторы
В Django REST framework есть классы, которые способны выполнить все три необходимые операции с данными: валидировать, десериализовать запрос и сериализовать ответ. Эти классы называются сериализаторы.
Сериализаторы могут работать с моделями Django и с обычными Python-классами.
- Для работы с обычными Python-классами сериализаторы наследуют от класса Serializer.
- Сериализаторы, работающие с моделями, наследуются от ModelSerializer.
Эти классы чем-то схожи с Form и ModelForm: при получении запроса с их помощью данные валидируют и конвертируют в Python-объекты, а при ответе преобразуют возвращаемые данные в необходимый формат.
Первый сериализатор
Сериализуем пост Робинзона: напишем класс-сериализатор PostSerializer и с его помощью конвертируем объект класса Post в JSON.PostSerializer будет наследоваться от класса Serializer (ведь Post — это не модель, а обычный класс Python).
В сериализаторе PostSerializer опишем:
- поля, которые должны вернуться в ответе;
- типы этих полей: чтобы сериализатор мог правильно конвертировать данные, он должен «знать», какие типы данных ожидает то или иное поле класса Post;
- параметры этих полей (например, максимальную и минимальную длину строки для CharField).
# Класс Post
class Post():
def __init__(self, author, text, pub_date=None):
self.author = author
self.text = text
self.pub_date = pub_date or datetime.now()
# Создаём сериализатор, наследник предустановленного класса Serializer
class PostSerializer(serializers.Serializer):
# Описываем поля и их типы
author = serializers.CharField(max_length=200)
text = serializers.CharField(max_length=5000)
pub_date = serializers.DateTimeField()
Поля в сериализаторе обычно называют так же, как и поля того класса, с которым предстоит работать.
По умолчанию сериализатор будет искать в переданном объекте поля с такими же названиями, как и его собственные. Эти же имена послужат ключами в результирующем словаре после конвертации данных.
Если имена полей в сериализаторе и в объекте, переданном в сериализатор, будут различаться — возникнет ошибка AttributeError
.
Полный перечень типов полей для сериализаторов доступен в официальной документации.
Теперь передадим в конструктор сериализатора Python-объект, сериализуем его и на выходе получим JSON.
Преобразование Python-объекта в JSON происходит в два шага: сначала сериализатор из данных объекта создаст Python-словарь (он будет доступен через serializer.data
), а затем этот словарь будет преобразован непосредственно в JSON.
В коде сериализация будет выглядеть так:
from datetime import datetime
from rest_framework import serializers
# Импорт класса, который конвертирует Python-словарь в JSON
from rest_framework.renderers import JSONRenderer
class Post():
def __init__(self, author, text, pub_date=None):
self.author = author
self.text = text
self.pub_date = pub_date or datetime.now()
# Создаём сериализатор, наследник предустановленного класса Serializer
class PostSerializer(serializers.Serializer):
# Описываем поля и их типы
author = serializers.CharField(max_length=200)
text = serializers.CharField(max_length=5000)
pub_date = serializers.DateTimeField()
# Создаём объект класса Post
post = Post(author='Робинзон Крузо',
text='23 ноября. Закончил работу над лопатой и корытом.',
pub_date ='1659-11-23T18:02:33.123543Z'
)
# Создаём экземпляр сериализатора PostSerializer
# Передаём в него объект класса Post
serializer = PostSerializer(post)
# serializer.data содержит Python-словарь:
# {'author': 'Робинзон Крузо', 'text': '23 ноября. Закончил работу над лопатой и корытом.', 'pub_date': '1659-11-23T18:02:33.123543Z'}
# Рендерим JSON из Python-словаря:
json_object = JSONRenderer().render(serializer.data)
# Готово! Переменная json_object содержит JSON, созданный из Python-словаря
Работа с моделями: ModelSerializer
Чаще всего работа с данными в Django осуществляется через модели, и в таких случаях сериализатор наследуется от класса ModelSerializer.
При создании такого сериализатора во внутреннем классе Meta
нужно указать модель, с которой должен работать сериализатор, и список тех полей модели, которые нужно сериализовать. С внутренним классом Meta
вы уже встречались при работе с ModelForm, в ModelSerializer его назначение аналогично.
Для наследников ModelSerializer нет необходимости описывать типы полей и их параметры: сериализатор сам их определит, взяв за основу поля указанной модели.
Сериализатор для модели Comment:
...
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField()
created = models.DateTimeField("created", auto_now_add=True)
...
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
# Указываем поля модели, с которыми будет работать сериализатор
# (поля модели, не указанные в этом списке, сериализатор будет игнорировать).
# Для перечисления полей можно использовать список или кортеж.
fields = ('id', 'post', 'author', 'text', 'created')
Чтобы сериализатор работал со всеми полями модели без исключения, вместо перечисления полей в fields можно указать fields = '__all__'
.
Вместо fields можно применить настройку exclude: в этом случае сериализатор будет работать со всеми полями модели, за исключением перечисленных.
Хорошая практика — явно указывать все поля, которые должны быть сериализованы. Это уменьшит вероятность непреднамеренного обнародования данных при изменении ваших моделей. «Явное лучше неявного».
Классы-сериализаторы принято выносить в отдельный файл serializers.py.