Django rest framework это поле не может быть пустым
Меня зовут Стас Гаранжа, я выпускник курса «Python-разработчик» в Яндекс.Практикуме. Я хочу помочь начинающим разработчикам, которые приступили к изучению Django Rest Framework (DRF) и хотят разобраться, как устроен этот фреймворк.
Я готовлю цикл статей, в которых расскажу о разных сторонах работы DRF. У меня пока нет значимого практического опыта для описания всех изюминок при работе с этим фреймворком, поэтому в основе статьи — исследование, обобщение и по возможности непротиворечивое изложение того, что у DRF под капотом.
В этой статье разберёмся, как сделать REST API на базе Django Rest Framework, чтобы получить по GET-запросу набор записей из базы данных (БД). Иными словами, рассмотрим, как DRF работает на чтение (о том, как с помощью него создавать, изменять и удалять записи в БД, поговорим в отдельной статье).
Общую схему решения этой задачи мы рассмотрим в первой части статьи. Вторая будет посвящена детальному разбору процесса сериализации данных.
Несколько вводных замечаний:
- Учебный проект, на основе которого даны все примеры в статье, можно найти в репозитории на Гитхабе.
- Стиль и объём изложения рассчитаны на тех, кто не знаком с DRF и только начинает свой путь в разработке.
- Предполагается, что читатель в общих чертах уже знаком с Django и знает основы ООП на Python.
Надеюсь, статья станет хорошим подспорьем изучения DRF и работы с его документацией, прояснит процесс сериализации данных и даст уверенность, что любая магия исчезает, стоит только покопаться под капотом конкретной библиотеки.
Что нужно ещё
Итак, мы испытали сериалайзер и посмотрели, как пропущенный через него набор табличных записей был преобразован в json-формат.
Чтобы сайт начал отдавать сериализованные данные, остаётся описать контроллер (view) и указать url-маршрут — эндпоинт, при обращении к которому сайт будет отдавать данные о столичных городах.
Создаём сериалайзер
Каждая запись в таблице Capital — объект. И как у любого объекта, у записи есть свои атрибуты. Изучим их на примере первой записи о столице Норвегии, воспользовавшись атрибутом __dict__ . Нам доступен словарь, который хранит информацию о динамических (writable) атрибутах объекта:
Каждое поле модели Capital — атрибут объекта конкретной записи. При этом поле author , которое через внешний ключ связано с моделью User и содержит id объектов из неё, в атрибуте записи и в БД получает приставку _id .
Сериалайзер поможет достать данные из нужных атрибутов (полей) записи и сформировать упорядоченный python-словарь — объект класса OrderedDict . Отмечу, что в Python с версии 3.7 и «обычные» словари стали сохранять порядок вставки пар «ключ — значение».
Для сериалайзера нужно описать поля: каждое поле будет отвечать за извлечение и представление данных из корреспондирующего поля табличной записи.
Важный момент: здесь мы рассматриваем сериалайзер на основе базового класса Serializer , чтобы лучше понять принципы его работы. На более высоком уровне абстракции есть класс ModelSerializer , который позволяет частично уйти от ручного создания полей. В этой статье он не рассматривается.
Нас интересуют данные, которые есть в трёх полях каждой табличной записи:
- поле capital_city ,
- поле capital_population ,
- поле author .
Значит, в сериалайзере должно быть тоже три атрибута-поля.
При создании поля сериалайзера нужно определиться с названием поля и его типом. Назвать поля сериалайзера можно как угодно: именно эти названия будут ключами в словаре, в который сериалайзер преобразует запись из таблицы.
Вот примеры трёх вариантов названий полей сериалайзера:
Но как сериалайзер понимает, в каком порядке стыковать собственные поля с полями табличной записи? Например, если поле сериалайзера условно называется a , то как он определяет, что его нужно состыковать с полем записи capital_city ?
Теперь нужно выбрать тип поля сериалайзера. Его нужно соотнести с тем, какие данные извлекаются из корреспондирующего поля табличной записи. Дело в том, что у каждого поля сериалайзера есть собственный метод to_representation . Как следует из названия, задача метода — представить извлечённые из записи данные в определённом виде.
Например, есть поле serializers.IntegerField . Посмотрим на его метод to_representation :
Очевидно, этот тип поля сериалайзера нельзя выбирать для данных из табличной записи о названии столицы: int('Осло') вызовет ValueError. А вот для данных о численности населения — самое то.
Выберем следующие типы полей сериалайзера:
О соотношении полей сериалайзера и полей Django-моделей можно прочитать в документации DRF.
Код сериалайзера разместим в том же приложении, где находится Django-модель, под именем serializers.py:
В поле CharField указан необязательный параметр max_length , благодаря которому задаётся максимально допустимая длина передаваемого значения. О других параметрах поля написано в документации.
Пропущенный через сериалайзер набор табличных записей доступен в атрибуте сериалайзера data . Посмотрим на содержимое этого атрибута, создав тестовый вариант сериалайзера.
Усиливаем валидацию
В предыдущей статье мы говорили о многоступенчатой системе валидации в DRF-сериалайзере. Попрактикуемся в создании валидаторов на разных этапах проверки входных данных.
Валидатор для конкретного поля. Допустим, нас интересуют только писатели, которые родились не позднее XX века. Для проверки этого условия добавим аргумент validators в поле birthdate .
Метавалидатор. Мы хотим, чтобы сочетание «имя – отчество – фамилия» было уникальным. Здесь пригодится UniqueTogetherValidator , который следует объявить в классе Meta сериалайзера, а до этого — импортировать из rest_framework.validators .
Заключительная валидация методом validate. Напоследок мы хотим проверить, что имя, фамилия и отчество не повторяются между собой.
Напомню, что в attrs находится упорядоченный словарь с прошедшими все предыдущие проверки данными.
Задача
id | country | capital_city | capital_population | author (FK) |
---|---|---|---|---|
1 | Norway | Oslo | 693500 | 1 |
2 | Sweden | Stockholm | 961600 | 1 |
3 | Finland | Helsinki | 655300 | 1 |
4 | Iceland | Reykjavik | 128800 | 1 |
Поле author через внешний ключ (foreign key) связано с моделью User, в которой есть вся информация о пользователе с конкретным id.
Мы хотим получить информацию из базы данных, не открывая сайт в браузере, а сделав запрос из другого Python-приложения.
В каком виде нужно получить информацию:
- Набор информации должен быть списком из Python-словарей: ключ — название поля записи в таблице Capital, значение — содержимое конкретного поля.
- Названия стран нас не интересуют — нам нужны названия столиц, численность населения, а также имя сотрудника, который внёс запись в базу. Имя получаем через id автора, указанный в поле author .
- Для передачи по сети полученные из БД данные должны быть конвертированы в json-формат.
Таким образом, каждую запись, которая при извлечении из базы данных является Python-объектом, принимающее приложение после декодирования json-строки должно получать в виде словаря:
В этом и состоит одно из назначений API — дать возможность различным приложениям доставать из БД сайта информацию в виде структуры данных, которую дальше можно обрабатывать.
Маршрут (эндпоинт)
Здесь та же схема действий, как в классическом Django. Подключаем маршруты приложения capitals:
Прописываем сам маршрут в приложении capitals и связываем маршрут с контроллером:
API для сайта на Django: общая схема
Поле 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 или относилось к классу SlugField .
В предыдущей статье мы разобрали, что при работе сериалайзера на запись в поле любого класса работает метод to_internal_value . Вот его исходный код для класса SlugRelatedField :
Код показывает, какие атрибуты нам следует передать, а именно:
- queryset — набор записей (разумеется, из связанной модели);
- slug_field — имя уникального поля в связанной модели, которое будет использоваться для ORM-запроса .get . Всегда можно указать pk или id , но у нас есть уникальное поле name , поэтому выберем его.
Что касается набора записей, мы будем искать объект Town по всем записям, поэтому передадим Town.objects.all() . Но можно сократить его до Town.objects , потому что all() будет вызван под капотом.
Добавляем сериалайзеру возможность записывать валидированные данные в БД
DRF-класс Serializer наследует от класса BaseSerializer , у которого есть метод `save`. Но вызвать его напрямую мы пока не можем. Чтобы метод заработал, внутри класса нашего сериалайзера нужно описать два метода:
-
с логикой сохранения в БД новой записи; с логикой обновления в БД существующей записи.
Примеры этих методов есть в документации.
Сейчас нам достаточно записывать в БД лишь новые данные, поэтому определим только метод create :
Теперь при вызове у экземпляра сериалайзера метода .save() без аргументов он вернёт новую запись. Вызывать метод save можно только после получения валидированных данных — другими словами, после вызова is_valid .
Разработчики DRF отмечают: логика save абсолютно не исчерпывается созданием или обновлением записи в БД. Можно вообще не описывать методы create и update , а целиком переопределить сам save . Например, чтобы при его вызове валидированные данные отправлялись по электронной почте.
Подбираем корреспондирующие поля сериалайзера
Каждое поле сериалайзера мы назовём так же, как поле модели, которое оно обслуживает. Это позволит не указывать дополнительный атрибут source .
Поле birth_date
В django-модели поле birth_date относится к классу DateField . Одноимённый класс предусмотрен и среди полей сериалайзера.
При объявлении поля DateField можно передать два необязательных аргумента:
- format — формат, в котором дата будет возвращаться при работе сериалайзера на чтение;
- input_formats — список или кортеж допустимых форматов передачи даты при работе сериалайзера на запись.
Поскольку сейчас мы акцентируемся на работе сериалайзера на запись, рассмотрим подробнее второй аргумент. Если его не передать, то применится настройка из-под капота DATE_INPUT_FORMATS . Она позволяет передать дату в формате iso-8601 — проще говоря, строкой вида YYYY-MM-DD .
Чтобы глобально переопределить это поведение, нужно прописать в настройках DRF собственные форматы. Они должны отвечать требованиям Python-модуля datetime .
Все настройки DRF прописываются в settings.py django-проекта, а именно в словаре REST_FRAMEWORK . Перенастроим формат передачи строки с датой:
Для большей выразительности примера мы переопределим формат ввода/вывода даты не глобально, а прямо в поле сериалайзера.
Под капотом DateField переданная строка превратится в объект класса datetime.date с помощью datetime.datetime.strptime .
Контроллер
Во views.py создадим класс контроллера. Нам понадобятся следующие инструменты DRF:
Внутри контроллера описываем один метод — get. Почему он называется именно так?
Пример: если поступил GET-запрос, то будет задействован метод get контроллера.
В методе get опишем ту же логику, что и в файле с пробным запуском сериалайзера:
- Подготовить набор записей.
- Создать экземпляр сериалайзера, который может обрабатывать не отдельную запись, а их набор ( many=True ).
- Отрендерить в json-формат данные, полученные от сериалайзера.
В отличие от файла serializer_example_2.py , где мы явно прописывали json-рендер и вызывали у него метод render , в коде контроллера ничего такого нет. Но рендер всё равно отработает: его работа описана под капотом внутри класса-родителя APIView .
После того как отработал метод get, работа контроллера выглядит так:
- Объект ответа, который вернул метод get ( return Response( ), передаётся в метод finalize_response родительского класса APIView .
- В методе finalize_response объекту ответа добавляются атрибуты:
- accepted_renderer — им как раз выступает объект JSONRenderer,
- accepted_media_type — 'application/json',
- context .
Благодаря этим атрибутам формируется rendered_content : у экземпляра JSONRenderer срабатывает метод render , который возвращает байтовую строку с данными в json-формат. Она помещается в атрибут ответа content .
Поля firstname, lastname и patronymic
Заглянем в метод __init__ этого же класса, чтобы определиться с аргументами при создании поля.
- allow_blank трогать не будем — нам нужны данные, поэтому пусть остаётся дефолтный False .
- trim_whitespace обрежет пробелы перед текстом и после него. Нам это подходит, поэтому атрибут тоже не трогаем и оставляем дефолтный True .
- max_length нам нужен, потому что такой же валидатор стоит у каждого текстового поля модели Writer . Поскольку по дефолту он не задан, объявим его явно и приведём лимит по количеству символов — такой же, как и в модели.
- min_length нам не потребуется, потому что у нас нет ограничений по минимальному количеству символов для полей модели.
Получаем следующий код:
4 ответа
С помощью @ user2719875 я разобрался с решением. Чтобы получить желаемое поведение - позволяя пользователю вводить пользовательскую строку, по умолчанию использовать функцию генератора строк, если ввод не производится, - мне нужно было определить значение по умолчанию в самом сериализаторе, а не в модели. Используя extra_kwargs для выбора нужного поля 'link', я объявляю функцию по умолчанию здесь:
Интересно, что он ищет функцию link_generator в самом models.py, поэтому код хранится там, над моделью. Не уверен, что это лучшая практика, но это работает. Удален default=link_generator из самой модели, как показано ниже:
Работает отлично. Спасибо за помощь, всем!
Попробуйте изменить ваш сериализатор на это:
Я на самом деле удивлен, что когда вы печатаете ваш сериализатор, DRF говорит, что поле link не является обязательным, потому что в соответствии с вашей моделью оно должно быть обязательным (потому что blank=True не установлен).
Проблема с первого взгляда, которую я вижу в вашем коде, заключается в опции по умолчанию. Должна быть сама функция (без скобок). Измените это на что-то вроде этого:
Надеюсь, это поможет!
при выполнении: for, users = '', обязательное поле для получения ошибки сериализатора.
Здесь требуется = False и пусто = True не работает,
У меня есть модель под названием Package. Я пытаюсь сохранить новые экземпляры и обновить существующие экземпляры в своей базе данных с помощью ModelSerializer из Django Rest Framework. Я хочу сделать GET-запрос всех пакетов с сервера API и сохранить их как новые объекты или обновить существующие в моей базе данных.
Я успешно создаю новые объекты Package в своей базе данных, однако все поля пусты.
Я не получаю ошибок от s.errors . При первой попытке использования этого метода я получил несколько ошибок, что некоторые поля не могли быть пустыми. Я временно разрешил пустые значения в этих полях, и ошибки исчезли. Что-то заставляет сериализатор сохранять нулевое значение для каждого поля?
Единственные поля, которые имеют значения, - это поля с атрибутом choices= и набором default= .
Спасибо за помощь.
ОБНОВИТЬ: Я пробовал пройти процесс в оболочке, что дает более четкое представление о том, что происходит:
Почему я теряю все ценности в этом процессе? Когда я бегу
Я получаю каждый dict с правильными парами ключ: значение, имеющими значения.
Точно так же следующий оператор также выводит правильные значения:
response содержит данные? он может быть просто пустым, поэтому ваши поля тоже будут пустыми
@AlexandrTatarinov Да, я получил ответ, которого ожидал от запроса GET. Если я распечатаю результат, он распечатает список, содержащий 12 упорядоченных слов, из которых я пытаюсь создать объекты модели.
Сейчас кажется, что проблема связана с тем, что s.is_valid () возвращает пустой OrderedDict, но я не могу понять, как это происходит.
Это показывает, что data , который вы предоставили сериализатору, также имеет значения None , поэтому сериализатор просто удаляет их из validated_data . Не можете найти ничего подозрительного в вашем коде, вы уверены, что передаете dict с данными в сериализатор? в любом случае он не должен мутировать data .
@AlexandrTatarinov Запуск его в отладчике показывает, что в dict, который я передаю сериализатору, есть правильные данные: s = PackageSerializer(data=item) s: PackageSerializer(data= Поля не устанавливаются в None, пока не будет возвращено validated_data.
Вау, я потерялся. Какую версию DRF вы используете? Вы можете привести мне какой-нибудь минимально воспроизводимый пример?
Большую часть времени, когда вы имеете дело с валидацией в REST framework, вы просто будете полагаться на валидацию поля по умолчанию или писать явные методы валидации для сериализатора или классов полей.
Однако иногда вы захотите поместить свою логику валидации в повторно используемые компоненты, чтобы ее можно было легко повторно использовать во всей кодовой базе. Этого можно добиться с помощью функций-валидаторов и валидаторов-классов.
Валидация в сериализаторах Django REST framework обрабатывается немного иначе, чем валидация в классе Django ModelForm .
С помощью ModelForm валидация выполняется частично в форме и частично на экземпляре модели. В REST framework валидация полностью выполняется в классе сериализатора. Это выгодно по следующим причинам:
Легко переключаться между использованием классов ModelSerializer и Serializer . Любое поведение валидации, используемое для ModelSerializer , легко воспроизвести.
Печать repr экземпляра сериализатора покажет вам, какие именно правила валидации он применяет. В экземпляре модели не вызывается дополнительное скрытое поведение валидации.
Когда вы используете ModelSerializer , все это выполняется автоматически. Если вы хотите перейти к использованию классов Serializer вместо этого, вам необходимо явно определить правила валидации.
В качестве примера того, как REST framework использует явную валидацию, мы возьмем простой класс модели, у которого есть поле с ограничением уникальности.
Вот базовый ModelSerializer , который мы можем использовать для создания или обновления экземпляров CustomerReportRecord :
Интересным моментом здесь является поле reference . Мы видим, что ограничение уникальности явно принудительно применяется валидатором в поле сериализатора.
Из-за этого более явного стиля REST framework включает несколько классов валидаторов, которые недоступны в ядре Django. Эти классы подробно описаны ниже.
Этот валидатор можно использовать для принудительного применения ограничения unique=True для полей модели. Он принимает один обязательный аргумент и необязательный аргумент messages :
lookup - поиск, используемый для поиска существующего экземпляра с проверяемым значением. По умолчанию - 'exact' .
Этот валидатор может использоваться для наложения ограничений unique_together на экземплярах модели. У него есть два обязательных аргумента и один необязательный аргумент messages :
fields обязательно - список или кортеж имен полей, которые должны составлять уникальный набор. Они должны существовать как поля в классе сериализатора.
Примечание: класс UniqueTogetherValidator всегда накладывает неявное ограничение, согласно которому все поля, к которым он применяется, всегда обрабатываются как обязательные. Поля со значением default являются исключением, поскольку они всегда предоставляют значение, даже если они опущены при вводе пользователем.
Эти валидаторы могут использоваться для обеспечения соблюдения ограничений unique_for_date , unique_for_month и unique_for_year для экземпляров модели. Они принимают следующие аргументы:
field обязательно - имя поля, уникальность которого будет проверяться в заданном диапазоне дат. Оно должно существовать как поле в классе сериализатора.
date_field обязательно - Имя поля, которое будет использоваться для определения диапазона дат для ограничения уникальности. Оно должно существовать как поле в классе сериализатора.
Поле даты, которое используется для проверки, всегда должно присутствовать в классе сериализатора. Вы не можете просто полагаться на класс модели default=. , потому, что значение, используемое для значения по умолчанию, не будет сгенерировано до тех пор, пока не будет запущена валидация.
Есть несколько стилей, которые вы можете использовать для этого в зависимости от того, как вы хотите, чтобы ваш API вел себя. Если вы используете ModelSerializer , вы, вероятно, просто будете полагаться на значения по умолчанию, которые REST framework генерирует для вас, но если вы используете Serializer или просто хотите более явный контроль, используйте один из стилей, показанных ниже.
Продолжаем цикл статей, посвящённый основам Django Rest Framework. В предыдущей статье мы подробно рассмотрели, как сериалайзер валидирует входные данные.
В этой статье мы закрепим теорию на простом примере, а также затронем те вопросы, которые не успели рассмотреть раньше:
- какое поле сериалайзера выбрать для ForeignKey -поля модели;
- как сериалайзер работает с датами;
- как устроен метод save сериалайзера.
А ещё мы напишем контроллер, который будет обрабатывать запросы к API на создание записи в БД.
Важное замечание: мы по-прежнему работаем с базовым классом сериалайзера, не переходя к более высокому уровню абстракции ModelSerializer . Это нужно, чтобы глубже понимать принципы работы DRF и при необходимости провести тонкую настройку сериалайзера. О ModelSerializer мы подробно поговорим в следующей статье.
Исходный код учебного проекта для этой статьи доступен в GitHub.
Сериалайзер в действии
Обратимся к файлу serializer_example_1.py . Он имитирует работу сериалайзера без необходимости запускать сервер и делать запрос к сайту. После клонирования учебного проекта и установки зависимостей (шаги 1—6 из ридми) достаточно запустить файл как обычный Python-скрипт и посмотреть в консоли результат его работы.
В serializer_example_1.py созданы классы с данными об авторах и о столицах для записей в таблицах:
Созданы объекты соответствующих записей:
Объединены записи в список по подобию кверисета из Django-модели:
Объявлен класс сериалайзера: код идентичен тому, который был приведён выше для class CapitalSerializer(serializers.Serializer) . Затем создали его экземпляр:
При создании мы передали сериалайзеру набор записей, которые нужно преобразовать. Они передаются в аргументе instance .
Кроме того, мы указали аргумент many со значением True . Дело в том, что логика работы сериалайзера с одной записью и с набором записей разная. Указывая many=True , мы включаем логику обработки набора записей. В чём она заключается, расскажу во второй части статьи при детальном разборе работы сериалайзера.
Выведем в консоль содержимое атрибута data сериалайзера:
Каждая запись из набора превратилась в упорядоченный словарь класса OrderedDict . Он находится в Python-модуле collections . Поэтому, если взглянуть на строки импорта в исходном коде restframework.serializers , можно увидеть:
В каждом OrderedDict содержится информация только из тех полей табличных записей, которые были состыкованы с полями сериалайзера. Данных о содержимом поля country нет — сериалайзер не настроен доставать эту информацию, потому что мы не создавали корреспондирующего поля в сериалайзере.
Отображаем (рендерим) информацию в формате json
Нам понадобится рендер — объект класса JSONRenderer . В файле serializer_example_2.py мы дополнили импорт — помимо модуля сериалайзеров из restframework мы импортировали модуль рендеров.
Далее необходимо создать экземпляр рендера нужного типа и вызвать у него метод render :
В результате мы увидим байтовую строку с массивом json-объектов:
Эта байтовая строка и будет передаваться по сети в атрибуте ответа content , а принимающее приложение будет её декодировать в список из Python-словарей и вытаскивать нужную информацию из каждого.
Контроллер и Browsable API
В первой статье, где демонстрировался пример работы DRF на чтение, мы использовали самый простой контроллер на основе класса APIView . Задействуем его и в этот раз — понадобится лишь дописать логику метода post .
Для работы с POST -запросами в Browsable API есть удобная вкладка HTML form , которая предоставляет отдельное поле в форме для каждого поля сериалайзера. Чтобы эта вкладка отрендерилась в шаблоне, в контроллере должен присутствовать атрибут get_serializer или serializer_class . Для высокоуровневых контроллеров, начиная с GenericAPIView , эти атрибуты есть под капотом.
Поскольку мы используем «голый» APIView , то допишем необходимый атрибут самостоятельно.
Посмотрим на представление нашего API в браузере:
Сверху видим ответ на GET -запрос: в базе пока нет записи ни об одном писателе. Ниже есть удобная форма для отправки POST -запроса. Заполним и отправим её. Результат:
Итак, мы разобрались, как создать API для валидации входных данных с последующей их записью в базу данных. В следующей статье мы поднимемся на один уровень абстракции вверх и рассмотрим, как устроен класс ModelSerializer .
Browsable API — удобный инструмент для тестирования API на DRF
Django Rest Framework позволяет посмотреть в браузере, какую информацию будет отдавать API при обращении к конкретному маршруту (эндпоинту). Достаточно ввести маршрут в адресную строку, и откроется страница с данными о запросе и результате его выполнения. За такое отображение отвечает BrowsableAPIRenderer.
Итак, мы рассмотрели, как сделать API на базе DRF, чтобы получить по GET-запросу набор записей из Django-модели. Во второй части подробно разберём работу сериалайзера на чтение.
Если у вас появились вопросы по решению задачи, пишите в комментариях.
Я использую Django REST Framework для запуска процессов CRUD в моей базе данных / моделях Django. Моя модель хранения клиентских данных довольно проста, и только одно поле вызывает у меня проблемы: CharField для хранения пользовательской или автоматически сгенерированной строки.
ОБНОВЛЕНИЕ: Добавление кода сериализатора с предлагаемым улучшением - довольно просто:
Тем не менее, при отправке я получаю эту ошибку каждый раз. Что заставляет думать, что это поле обязательно для заполнения? ? Опять же, я хочу позволить пользователю оставить это поле пустым и запустить функцию link_generator, если он это сделает. Если я прямо скажу blank=True , как показано ниже:
. link_generator не запускается, и в результате сохраняется пустая строка. Для чего это стоит, вот код link_generator, который я хочу запустить в случае, если пользователь оставляет ввод пустым (sidenote: я объявил это выше модели - это плохая практика? Где это должно быть объявлено):
Есть идеи? Я в замешательстве - просто не понимаю, почему это должно быть помечено так. Я относительно новичок в Django и REST в целом - спасибо за любую помощь.
Объявляем класс сериалайзера
Смотрим, какие в модели есть поля, куда будут записываться пришедшие в запросе данные. В примере класс модели показан в сокращённом варианте.
Итак, нам нужно, чтобы в POST-запросе пришли данные для пяти полей.
Решаем задачу с помощью Django Rest Framework
Задача решается в два шага:
- Сложный объект (набор записей из Django-модели) нужно превратить в более простую структуру, в нашем случае в список словарей. Понадобится сериалайзер.
- Сериализованные данные для дальнейшей передачи по сети нужно перевести (отрендерить) в json-формат — универсальный текстовый формат передачи данных, не зависящий от языка реализации. Понадобится рендер.
Небольшое отступление о json. Базовые структуры данных на python кодируются в json и декодируются обратно следующим образом:
Python | JSON | Пример Python | Пример JSON |
---|---|---|---|
dict | object | ||
list, tuple | array | ['элемент1', 'элемент2'], ('элемент1', 'элемент2') | ["элемент1", "элемент2"] |
str | string | 'элемент1' | "элемент1" |
int, float, int- & float-derived Enums | number | 5, 4.2 | 5, 4.2 |
True | true | True | true |
False | false | False | false |
None | null | None | null |
Собираем все поля вместе и проверяем работу сериалайзера
Код нашего сериалайзера получился таким:
Сериалайзер может работать и на запись, и на чтение — 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 будет таким:
Все поля сериалайзера отработали штатно. Ура!
API в действии
Чтобы посмотреть, как работает API, можно:
Осталось выполнить шаги 2 и 3.
Если всё отработало штатно, в корневой директории проекта появится файл capitals.txt со следующим содержимым:
Несмотря на то, что пример наивный, он показывает главное: как мы научили
веб-приложение отдавать информацию из базы данных в ответ на запрос, который поступает не от человека через браузер, а от другого приложения. И далее — как это приложение использует полученную информацию.
Читайте также: