Пример API для записи данных

В этом разделе мы закрепим теорию из предыдущей части на простом примере, а также затронем ранее не рассмотренные вопросы:

  • какое поле сериалайзера выбрать для ForeignKey-поля модели;

  • как сериалайзер работает с датами;

  • как устроен метод save сериалайзера;

  • напишем контроллер, который будет обрабатывать запросы к API на создание записи в БД.

Важное замечание: мы по-прежнему работаем с базовым классом сериалайзера, не переходя к более высокому уровню абстракции — ModelSerializer. Это нужно, чтобы глубже понимать принципы работы DRF и суметь, при необходимости, провести тонкую настройку сериалайзера. О ModelSerializer мы очень подробно поговорим в следующей статье.

Исходный код учебного проекта для этой статьи доступен в гитхабarrow-up-right.

Объявляем класс сериалайзера

from rest_framework import serializers


class WriterSerializer(Serializer):
    pass

Смотрим, какие поля у нас есть в модели, куда будут записываться пришедшие в запросе данные (класс модели приведен в сокращении).

class Writer(models.Model):
    firstname = models.CharField(max_length=100...)
    lastname = models.CharField(max_length=100...)
    patronymic = models.CharField(max_length=100...)
    birth_place = models.ForeignKey(to=Town...)
    birth_date = models.DateField(...)

Итак, нам нужно, чтобы в POST-запросе пришли данные для 5-ти полей.

Подбираем корреспондирующие поля сериалайзера

Каждое поле сериалайзера мы назовем также как поле модели, которое оно обслуживает. Это позволит не указывать дополнительный атрибут source и сразу записывать в базу прошедшие через сериалайзер данные.

Поля firstname, lastname, patronymic

Одноименные поля модели ожидают обычные строковые значения, поэтому для корреспондирующих полей сериалайзера выберем класс serializers.Charfield.

Заглянем в метод __init__arrow-up-right класса, чтобы определиться с аргументами при создании поля.

  • allow_blank трогать не будем, нам нужны данные, устраивает дефолтный False.

  • trim_whitespace обрежет пробелы перед и после текста, нам это подходит, поэтому атрибут также не трогаем, оставляем дефолтный True.

  • max_length нам нужен, т.к. такой же валидатор стоит у каждого текстового поля модели Writer. Поскольку по дефолту он не задан, объявим его явно и приведем лимит по количеству символов (такой же как и в модели).

  • min_length нам не требуется, ограничений по минимальному количеству символов для полей модели нет.

Получаем следующий код:

Поле birth_place

Одноименное поле модели относится к классу ForeignKey и связано с моделью Town.

Нам нужно не просто передать какое-то значение (тот же текст), которое сразу запишется в базу. Нам нужно, чтобы по этому значению была извлечен объект записи из модели Town.

Для работы с полями с отношениями DRF предоставляет несколько классов полейarrow-up-right.

Нам подходит класс SlugRelatedField. Описание из исходного кодаarrow-up-right: A read-write field that represents the target of the relationship by a unique 'slug' attribute. Слово slug может немного путать. Под slug здесь понимается любое уникальное поле модели с любым названием (совсем не обязательно, чтобы оно называлось slug или относилось к классу SlugField).

В предыдущей статье мы разобрали, что при работе сериалайзера на запись в поле любого класса работает метод to_internal_value. Вот его исходный кодarrow-up-right для класса SlugRelatedField:

Код даёт понимание, какие атрибуты нам следует передать. Нужны:

  • queryset — набор записей (разумеется, из связанной модели);

  • slug_field — имя уникального поля в связанной модели, которое будет использоваться для ORM-запроса .get. Всегда можно указать pk или id, но у нас есть уникальное поле name, выберем его.

Что касается набора записей, мы будем искать объект Town по всем записям, поэтому передадим Town.objects.all(), хотя вполне можно сократить до Town.objects, т.к. all() будет вызван под капотомarrow-up-right.

Итог:

Поле birth_date

В джанго-модели поле birth_date относится к классу DateField. Одноименный класс предусмотренarrow-up-right и среди полей сериалайзера.

При объявлении поля DateField можно передать два необязательных аргумента:

  • format — формат, в котором дата будет возвращаться при работе сериалайзера на чтение;

  • input_formats — список или кортеж допустимых форматов передачи даты при работе сериалайзера на запись.

Поскольку акцент в статье на работу сериалайзера на запись, рассмотрим подробнее второй аргумент. Если его не передать, то используется настройка из-под капота DATE_INPUT_FORMATS, которая позволяет передавть дату в формате iso-8601, проще говоря, строкой вида YYYY-MM-DD.

Чтобы глобально переопределить это поведение, нужно прописать в настройках DRF собственные форматы (они должны отвечать требованиямarrow-up-right Python-модуля datetime).

Все настройки DRFarrow-up-right прописываются в settings.py джанго-проекта, в словаре REST_FRAMEWORK. Перенастроим формат передачи строки с датой:

Для большей выразительности примера мы переопределим формат ввода/вывода даты не глобально, а прямо в поле сериалайзера.

Под капотомarrow-up-right DateField переданная строка превратится с помощью datetime.datetime.strptime в объект класса datetime.date.

Собираем все поля вместе и проверяем работу сериалайзера

Код нашего сериалайзера получился таким:

Сериалайзер может работать и на запись, и на чтение, read only или write only полей нет. Проверим.

Создадим сериалайзер на чтение, понадобится запись из базы и аргумент instance (в каких случаях нужен тот или иной аргумент при создании сериалайзера мы подробно рассматривали в предыдущей статье).

Результат:

Создадим сериалайзер на запись. Понадобится словарь с входными данными и аргумент data.

Посмотрим, что в validated_data (обработанные сериалайзером даннные, готовые к записи в БД):

Видим, что в birth_place теперь не строка "Воронеж", а объект модели Town, готовый для записи в поле с внешним ключом birth_place. А в поле birth_date не строка с датой, а объект класса datetime.date.

Попробуем передать невалидные данные, например, для поля birth_date придёт строка в неправильном формате (допустим, 22/10/1870). is_valid вернет False, а словарь errors будет таким:

Все поля сериалайзера отработали штатно.

Усиливаем валидацию

В предыдущей статье мы очень подробно рассказали о многоступенчатой системе валидации в DRF-сериалайзере. Попрактикуемся в прикручивании валидаторов на разных этапах проверки входных данных.

Валидатор для конкретного поля. Допустим, нас интересуют только писатели, родившиеся не позднее 20 века. Для проверки этого условия добавим аргумент validators в поле birthdate.

Метавалидатор. Мы хотим, чтобы сочетание "имя-отчество-фамилия" было уникальным. Здесь пригодится UniqueTogetherValidator, который следует объявить в классе Meta сериалайзера (а до этого импортировать из rest_framework.validators).

Заключительная валидация методом validate. Напоследок мы хотим проверить, что имя, фамилия и отчество не повторяются между собой.

Напомню, что в attrs находится упорядоченный словарь с прошедшими все предыдущие проверки данными.

Добавляем сериалайзеру возможность записывать валидированные данные в БД

DRF-класс Serializer наследует от класса BaseSerializer, у которого есть метод savearrow-up-right. Но вызвать его напрямую мы пока не можем. Чтобы метод заработал, внутри класса нашего сериалайзера нужно описать методы:

Примеры этих методов есть в документацииarrow-up-right.

Сейчас нам достаточно лишь записывать в БД новые данные, поэтому определим только метод create:

Теперь при вызове у экземпляра сериалайзера метода .save() (аргументы не нужны) и он вернет новую запись. Вызывать метод save можно только после полученияarrow-up-right валидированных данных, т.е. после вызова is_valid.

Разработчики DRF отмечаютarrow-up-right: логика save абсолютно не исчерпывается созданием или обновлением записи в БД. Можно вообще не описывать методы create и update, а целиком переопределить сам save. Например, для того, чтобы при его вызове валидированные данные отправлялись по электронной почте.

Контроллер и Browsable API

В первой статье, где демонстрировался пример работы DRF на чтение, мы использовали самый простой контроллер на основе класса APIView. Задействуем его и в этот раз — понадобится лишь дописать логику метода post

Для работы с POST-запросами в Browsable API есть удобная вкладка HTML form, предоставляющее для каждого поля сериалайзера отдельное поле в форме.

Чтобы эта вкладка отрендерилась в шаблонеarrow-up-right в контроллере должен присутствоватьarrow-up-right атрибут get_serializer или serializer_class.

Для высокоуровневых контроллеров, начиная с GenericAPIView эти атрибуты есть под капотом. Поскольку мы используем "голый" APIView, то допишем необходимый атрибут самостоятельно.

Посмотрим на представление нашего API в браузере

image

Сверху видим ответ на GET-запрос, в базе пока нет записи ни об одном писателе. Ниже удобная форма для отправки POST-запроса.

Заполним и отправим её. Результат

image

Итак, на примере мы разобрались, как создать API для валидации входных данных с последующей их записью в базу данных. В следующей статье мы поднимемся на один уровень абстракции вверх и рассмотим, как устроен класс ModelSerializer.

Last updated