Сериализаторы в Django

С этого урока вы начнёте писать 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.





Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: