Пример API для записи данных
В этом разделе мы закрепим теорию из предыдущей части на простом примере, а также затронем ранее не рассмотренные вопросы:
какое поле сериалайзера выбрать для
ForeignKey-поля модели;как сериалайзер работает с датами;
как устроен метод
saveсериалайзера;напишем контроллер, который будет обрабатывать запросы к API на создание записи в БД.
Важное замечание: мы по-прежнему работаем с базовым классом сериалайзера, не переходя к более высокому уровню абстракции — ModelSerializer. Это нужно, чтобы глубже понимать принципы работы DRF и суметь, при необходимости, провести тонкую настройку сериалайзера. О ModelSerializer мы очень подробно поговорим в следующей статье.
Исходный код учебного проекта для этой статьи доступен в гитхаб.
Объявляем класс сериалайзера
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
firstname, lastname, patronymicОдноименные поля модели ожидают обычные строковые значения, поэтому для корреспондирующих полей сериалайзера выберем класс serializers.Charfield.
Заглянем в метод __init__ класса, чтобы определиться с аргументами при создании поля.
allow_blankтрогать не будем, нам нужны данные, устраивает дефолтныйFalse.trim_whitespaceобрежет пробелы перед и после текста, нам это подходит, поэтому атрибут также не трогаем, оставляем дефолтныйTrue.max_lengthнам нужен, т.к. такой же валидатор стоит у каждого текстового поля моделиWriter. Поскольку по дефолту он не задан, объявим его явно и приведем лимит по количеству символов (такой же как и в модели).min_lengthнам не требуется, ограничений по минимальному количеству символов для полей модели нет.
Получаем следующий код:
Поле birth_place
birth_placeОдноименное поле модели относится к классу ForeignKey и связано с моделью Town.
Нам нужно не просто передать какое-то значение (тот же текст), которое сразу запишется в базу. Нам нужно, чтобы по этому значению была извлечен объект записи из модели Town.
Для работы с полями с отношениями DRF предоставляет несколько классов полей.
Нам подходит класс SlugRelatedField. Описание из исходного кода: A read-write field that represents the target of the relationship by a unique 'slug' attribute. Слово slug может немного путать. Под slug здесь понимается любое уникальное поле модели с любым названием (совсем не обязательно, чтобы оно называлось slug или относилось к классу SlugField).
В предыдущей статье мы разобрали, что при работе сериалайзера на запись в поле любого класса работает метод to_internal_value.
Вот его исходный код для класса SlugRelatedField:
Код даёт понимание, какие атрибуты нам следует передать. Нужны:
queryset— набор записей (разумеется, из связанной модели);slug_field— имя уникального поля в связанной модели, которое будет использоваться для ORM-запроса.get. Всегда можно указатьpkилиid, но у нас есть уникальное полеname, выберем его.
Что касается набора записей, мы будем искать объект Town по всем записям, поэтому передадим Town.objects.all(), хотя вполне можно сократить до Town.objects, т.к. all() будет вызван под капотом.
Итог:
Поле birth_date
birth_dateВ джанго-модели поле birth_date относится к классу DateField. Одноименный класс предусмотрен и среди полей сериалайзера.
При объявлении поля DateField можно передать два необязательных аргумента:
format— формат, в котором дата будет возвращаться при работе сериалайзера на чтение;input_formats— список или кортеж допустимых форматов передачи даты при работе сериалайзера на запись.
Поскольку акцент в статье на работу сериалайзера на запись, рассмотрим подробнее второй аргумент. Если его не передать, то используется настройка из-под капота DATE_INPUT_FORMATS, которая позволяет передавть дату в формате iso-8601, проще говоря, строкой вида YYYY-MM-DD.
Чтобы глобально переопределить это поведение, нужно прописать в настройках DRF собственные форматы (они должны отвечать требованиям Python-модуля datetime).
Все настройки DRF прописываются в settings.py джанго-проекта, в словаре REST_FRAMEWORK. Перенастроим формат передачи строки с датой:
Для большей выразительности примера мы переопределим формат ввода/вывода даты не глобально, а прямо в поле сериалайзера.
Под капотом 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, у которого есть метод save. Но вызвать его напрямую мы пока не можем. Чтобы метод заработал, внутри класса нашего сериалайзера нужно описать методы:
Примеры этих методов есть в документации.
Сейчас нам достаточно лишь записывать в БД новые данные, поэтому определим только метод create:
Теперь при вызове у экземпляра сериалайзера метода .save() (аргументы не нужны) и он вернет новую запись. Вызывать метод save можно только после получения валидированных данных, т.е. после вызова is_valid.
Разработчики DRF отмечают: логика save абсолютно не исчерпывается созданием или обновлением записи в БД. Можно вообще не описывать методы create и update, а целиком переопределить сам save. Например, для того, чтобы при его вызове валидированные данные отправлялись по электронной почте.
Контроллер и Browsable API
В первой статье, где демонстрировался пример работы DRF на чтение, мы использовали самый простой контроллер на основе класса APIView. Задействуем его и в этот раз — понадобится лишь дописать логику метода post
Для работы с POST-запросами в Browsable API есть удобная вкладка HTML form, предоставляющее для каждого поля сериалайзера отдельное поле в форме.
Чтобы эта вкладка отрендерилась в шаблоне в контроллере должен присутствовать атрибут get_serializer или serializer_class.
Для высокоуровневых контроллеров, начиная с GenericAPIView эти атрибуты есть под капотом. Поскольку мы используем "голый" APIView, то допишем необходимый атрибут самостоятельно.
Посмотрим на представление нашего API в браузере

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

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