Сериализаторы
Расширение возможностей сериализаторов — задача, которую мы хотели бы решить. Однако это нетривиальная проблема, и потребуется значительная работа над дизайном.
— Рассел Киту-Мэйдж, группа пользователей Django
Сериализаторы позволяют преобразовывать сложные данные, такие как наборы запросов и экземпляры моделей, в базовые типы данных Python, которые затем легко можно отобразить в JSON, XML или других типах содержимого. Сериализаторы также обеспечивают десериализацию, позволяя преобразовывать разобранные данные обратно в сложные типы после предварительной проверки входящих данных.
Сериализаторы в REST фреймворке работают очень похоже на классы Form и ModelForm Django. Мы предоставляем класс Serializer, который предоставляет мощный и универсальный способ управления выводом ваших ответов, а также класс ModelSerializer, который предоставляет полезный ярлык для создания сериализаторов, работающих с экземплярами моделей и наборами запросов.
Объявление сериализаторов
Начнём с создания простого объекта, который мы можем использовать в примерах:
from datetime import datetime
class Comment:
def __init__(self, email, content, created=None):
self.email = email
self.content = content
self.created = created or datetime.now()
comment = Comment(email='leila@example.com', content='foo bar')
Мы объявим сериализатор, который мы можем использовать для сериализации и десериализации данных, соответствующих объектам Comment.
Объявление сериализатора очень похоже на объявление формы:
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
Сериализация объектов
Теперь мы можем использовать CommentSerializer для сериализации комментария или списка комментариев. Опять же, использование класса Serializer очень похоже на использование класса Form.
serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
На данном этапе мы преобразовали экземпляр модели в базовые типы данных Python. Для завершения процесса сериализации мы отображаем данные в json.
from rest_framework.renderers import JSONRenderer
json = JSONRenderer().render(serializer.data)
json
# b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'
Десериализация объектов
Десериализация аналогична. Сначала мы разбираем поток в базовые типы данных Python…
import io from rest_framework.parsers import JSONParser stream = io.BytesIO(json) data = JSONParser().parse(stream)
…затем восстанавливаем эти базовые типы данных в словарь валидированных данных.
serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
Сохранение экземпляров
Если мы хотим иметь возможность возвращать полные экземпляры объектов на основе валидированных данных, нам нужно реализовать один или оба метода .create() и .update(). Например:
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
def create(self, validated_data):
return Comment(**validated_data)
def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
instance.content = validated_data.get('content', instance.content)
instance.created = validated_data.get('created', instance.created)
return instance
Если ваши экземпляры объектов соответствуют моделям Django, вам также необходимо убедиться, что эти методы сохраняют объект в базе данных. Например, если Comment была моделью Django, методы могут выглядеть так:
def create(self, validated_data):
return Comment.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
instance.content = validated_data.get('content', instance.content)
instance.created = validated_data.get('created', instance.created)
instance.save()
return instance
Теперь при десериализации данных мы можем вызвать .save() для возврата экземпляра объекта на основе валидированных данных.
comment = serializer.save()
Вызов .save() либо создаст новый экземпляр, либо обновит существующий экземпляр, в зависимости от того, был ли передан существующий экземпляр при создании класса сериализатора:
# .save() will create a new instance. serializer = CommentSerializer(data=data) # .save() will update the existing `comment` instance. serializer = CommentSerializer(comment, data=data)
Оба метода .create() и .update() являются необязательными. Вы можете реализовать ни один, один или оба из них в зависимости от варианта использования вашего класса сериализатора.
Передача дополнительных атрибутов в .save()
Иногда вам нужно, чтобы код вашего представления мог вводить дополнительные данные в момент сохранения экземпляра. Эти дополнительные данные могут включать информацию, такую как текущий пользователь, текущее время или что-либо ещё, что не является частью данных запроса.
Вы можете сделать это, включив дополнительные ключевые аргументы при вызове .save(). Например:
serializer.save(owner=request.user)
Любые дополнительные ключевые аргументы будут включены в аргумент validated_data при вызове .create() или .update().
Прямое переопределение .save()
В некоторых случаях имена методов .create() и .update() могут быть неинформативными. Например, в форме связи мы, возможно, не создаём новые экземпляры, а вместо этого отправляем электронное письмо или другое сообщение.
В таких случаях вы можете вместо этого выбрать переопределение .save() напрямую, так как это более удобочитаемо и информативно.
Например:
class ContactForm(serializers.Serializer):
email = serializers.EmailField()
message = serializers.CharField()
def save(self):
email = self.validated_data['email']
message = self.validated_data['message']
send_email(from=email, message=message)
Обратите внимание, что в приведенном выше случае нам теперь нужно напрямую получить доступ к свойству сериализатора .validated_data.
Валидация
При десериализации данных всегда необходимо вызывать is_valid() перед попыткой доступа к валидированным данным или сохранением экземпляра объекта. Если возникнут ошибки валидации, свойство .errors будет содержать словарь, представляющий соответствующие сообщения об ошибках. Например:
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}
Каждый ключ в словаре будет именем поля, а значениями будут списки строк любых сообщений об ошибках, соответствующих этому полю. Ключ non_field_errors также может быть присутствовать и будет содержать любые общие ошибки валидации. Имя ключа non_field_errors может быть настраиваемым с помощью настройки REST фреймворка NON_FIELD_ERRORS_KEY.
При десериализации списка элементов ошибки будут возвращены в виде списка словарей, представляющих каждый из десериализованных элементов.
Выброс исключения при недействительных данных
Метод .is_valid() принимает необязательный флаг raise_exception, который заставит его выбросить исключение serializers.ValidationError при наличии ошибок валидации.
Эти исключения автоматически обрабатываются обработчиком исключений по умолчанию, предоставляемым REST фреймворком, и по умолчанию вернут ответы HTTP 400 Bad Request.
# Return a 400 response if the data was invalid. serializer.is_valid(raise_exception=True)
Валидация на уровне поля
Вы можете указать пользовательскую валидацию на уровне поля, добавив методы .validate_<field_name> к вашему подклассу Serializer. Они похожи на методы .clean_<field_name> в формах Django.
Эти методы принимают один аргумент — значение поля, требующего валидации.
Методы validate_<field_name> должны возвращать проверенное значение или выбрасывать исключение serializers.ValidationError. Например:
from rest_framework import serializers
class BlogPostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
content = serializers.CharField()
def validate_title(self, value):
"""
Check that the blog post is about Django.
"""
if 'django' not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
return value
Примечание: Если ваш метод <field_name> объявлен в сериализаторе с параметром required=False, то этот шаг валидации не выполнится, если поле не включено.
Валидация на уровне объекта
Для выполнения любой другой валидации, требующей доступа к нескольким полям, добавьте метод, названный .validate(), к вашему подклассу Serializer. Этот метод принимает один аргумент — словарь значений полей. Он должен выбросить исключение serializers.ValidationError при необходимости или просто вернуть проверенные значения. Например:
from rest_framework import serializers
class EventSerializer(serializers.Serializer):
description = serializers.CharField(max_length=100)
start = serializers.DateTimeField()
finish = serializers.DateTimeField()
def validate(self, data):
"""
Check that start is before finish.
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
return data
Валидаторы
Отдельные поля в сериализаторе могут включать валидаторы, объявив их в экземпляре поля, например:
def multiple_of_ten(value):
if value % 10 != 0:
raise serializers.ValidationError('Not a multiple of ten')
class GameRecord(serializers.Serializer):
score = serializers.IntegerField(validators=[multiple_of_ten])
...
Классы сериализаторов также могут включать переиспользуемые валидаторы, которые применяются ко всему набору данных поля. Эти валидаторы включаются путём объявления их в внутреннем классе Meta, например:
class EventSerializer(serializers.Serializer):
name = serializers.CharField()
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
date = serializers.DateField()
class Meta:
# Each room only has one event per day.
validators = [
UniqueTogetherValidator(
queryset=Event.objects.all(),
fields=['room_number', 'date']
)
]
Для получения дополнительной информации см. документацию по валидаторам.
Доступ к исходным данным и экземпляру
При передаче начального объекта или набора запросов в экземпляр сериализатора, объект будет доступен как .instance. Если начальный объект не передан, то атрибут .instance будет None.
При передаче данных в экземпляр сериализатора, немодифицированные данные будут доступны как .initial_data. Если аргумент data не передан, то атрибут .initial_data не будет существовать.
Частичные обновления
По умолчанию сериализаторы должны получать значения для всех обязательных полей, иначе они будут генерировать ошибки валидации. Вы можете использовать аргумент partial для разрешения частичных обновлений.
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)
Работа с вложенными объектами
Предыдущие примеры подходят для работы с объектами, которые имеют только простые типы данных, но иногда нам также нужно представлять более сложные объекты, где некоторые атрибуты объекта могут не быть простыми типами данных, такими как строки, даты или целые числа.
Класс Serializer сам по себе является типом Field и может использоваться для представления отношений, где один тип объекта вложен внутри другого.
class UserSerializer(serializers.Serializer):
email = serializers.EmailField()
username = serializers.CharField(max_length=100)
class CommentSerializer(serializers.Serializer):
user = UserSerializer()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
Если вложенное представление может необязательно принять значение None, вы должны передать флаг required=False вложенному сериализатору.
class CommentSerializer(serializers.Serializer):
user = UserSerializer(required=False) # May be an anonymous user.
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
Аналогично, если вложенное представление должно быть списком элементов, вы должны передать флаг many=True вложенному сериализатору.
class CommentSerializer(serializers.Serializer):
user = UserSerializer(required=False)
edits = EditItemSerializer(many=True) # A nested list of 'edit' items.
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
Записываемые вложенные представления
При работе с вложенными представлениями, которые поддерживают десериализацию данных, любые ошибки с вложенными объектами будут вложены под именем поля вложенного объекта.
serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}
Аналогично, свойство .validated_data будет включать вложенные структуры данных.
Написание методов .create() для вложенных представлений
Если вы поддерживаете записываемые вложенные представления, вам нужно написать методы .create() или .update(), которые обрабатывают сохранение нескольких объектов.
Следующий пример демонстрирует, как вы можете обработать создание пользователя с вложенным профилем.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ['username', 'email', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
Написание методов .update() для вложенных представлений
Для обновлений вы должны внимательно подумать о том, как обрабатывать обновления отношений. Например, если данные для отношения None, или не предоставлены, что из следующего должно произойти?
- Установить отношение на
NULLв базе данных. - Удалить связанный экземпляр.
- Игнорировать данные и оставить экземпляр как есть.
- Сгенерировать ошибку валидации.
Вот пример метода .update() для нашего предыдущего класса UserSerializer.
def update(self, instance, validated_data):
profile_data = validated_data.pop('profile')
# Unless the application properly enforces that this field is
# always set, the following could raise a `DoesNotExist`, which
# would need to be handled.
profile = instance.profile
instance.username = validated_data.get('username', instance.username)
instance.email = validated_data.get('email', instance.email)
instance.save()
profile.is_premium_member = profile_data.get(
'is_premium_member',
profile.is_premium_member
)
profile.has_support_contract = profile_data.get(
'has_support_contract',
profile.has_support_contract
)
profile.save()
return instance
Поскольку поведение вложенных операций создания и обновления может быть неоднозначным и может потребовать сложных зависимостей между связанными моделями, REST фреймворк 3 требует от вас всегда явно писать эти методы. Методы ModelSerializer .create() и .update() по умолчанию не включают поддержки записываемых вложенных представлений.
Однако доступны сторонние пакеты, такие как DRF Writable Nested, которые поддерживают автоматические записываемые вложенные представления.
Обработка сохранения связанных экземпляров в классах менеджеров моделей
Альтернативой сохранению нескольких связанных экземпляров в сериализаторе является создание пользовательских классов менеджеров моделей, которые обрабатывают создание необходимых экземпляров.
Например, предположим, что мы хотим гарантировать, что экземпляры User и экземпляры Profile всегда создаются вместе как пара. Мы можем написать пользовательский класс-менеджер, который выглядит примерно так:
class UserManager(models.Manager):
...
def create(self, username, email, is_premium_member=False, has_support_contract=False):
user = User(username=username, email=email)
user.save()
profile = Profile(
user=user,
is_premium_member=is_premium_member,
has_support_contract=has_support_contract
)
profile.save()
return user
Этот класс-менеджер теперь лучше инкапсулирует тот факт, что экземпляры пользователя и профиля всегда создаются одновременно. Наш метод .create() в классе сериализатора теперь может быть переписан для использования нового метода менеджера.
def create(self, validated_data):
return User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
is_premium_member=validated_data['profile']['is_premium_member'],
has_support_contract=validated_data['profile']['has_support_contract']
)
Дополнительные сведения об этом подходе см. в документации Django по менеджерам моделей и в этой статье блога об использовании классов моделей и менеджеров.
Обработка нескольких объектов
Класс Serializer также может обрабатывать сериализацию или десериализацию списков объектов.
Сериализация нескольких объектов
Чтобы сериализовать набор запросов или список объектов вместо одного экземпляра объекта, вы должны передать флаг many=True при создании экземпляра сериализатора. Затем вы можете передать набор запросов или список объектов для сериализации.
queryset = Book.objects.all()
serializer = BookSerializer(queryset, many=True)
serializer.data
# [
# {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'},
# {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'},
# {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
# ]
Десериализация нескольких объектов
По умолчанию поведение десериализации нескольких объектов заключается в поддержке создания нескольких объектов, но не поддержке обновления нескольких объектов. Дополнительную информацию о том, как поддерживать или настраивать любой из этих случаев, см. в документации ListSerializer ниже.
Включение дополнительного контекста
В некоторых случаях вам нужно предоставить дополнительный контекст сериализатору в дополнение к объекту, подлежащему сериализации. Один распространенный случай — если вы используете сериализатор, включающий гиперссылочные связи, для которого сериализатор должен иметь доступ к текущему запросу, чтобы правильно генерировать полные URL-адреса.
Вы можете предоставить произвольный дополнительный контекст, передав аргумент context при создании экземпляра сериализатора. Например:
serializer = AccountSerializer(account, context={'request': request})
serializer.data
# {'id': 6, 'owner': 'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
Словарь контекста можно использовать в любой логике поля сериализатора, например, в пользовательском методе .to_representation(), обратившись к атрибуту self.context.
ModelSerializer
Часто вам понадобятся классы сериализаторов, которые тесно связаны с определениями моделей Django.
Класс ModelSerializer предоставляет сокращение, которое позволяет автоматически создать класс Serializer с полями, соответствующими полям модели.
Класс ModelSerializer такой же, как обычный класс Serializer, за исключением того, что:
- Он будет автоматически генерировать набор полей для вас на основе модели.
- Он будет автоматически генерировать валидаторы для сериализатора, такие как валидаторы unique_together.
- Он включает простые реализации по умолчанию методов
.create()и.update().
Объявление ModelSerializer выглядит так:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
По умолчанию все поля модели в классе будут сопоставлены с соответствующими полями сериализатора.
Любые отношения, такие как внешние ключи в модели, будут сопоставлены с PrimaryKeyRelatedField. Обратные отношения не включаются по умолчанию, если не указаны явно, как указано в документации отношений сериализатора.
Проверка ModelSerializer
Классы сериализаторов генерируют полезные подробные строковые представления, которые позволяют вам полностью проверить состояние их полей. Это особенно полезно при работе с ModelSerializers, где вы хотите определить набор полей и валидаторов, которые автоматически создаются для вас.
Для этого откройте оболочку Django, используя python manage.py shell, затем импортируйте класс сериализатора, создайте его экземпляр и распечатайте представление объекта…
>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print(repr(serializer))
AccountSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_blank=True, max_length=100, required=False)
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
Указание полей для включения
Если вам нужен только подмножество полей по умолчанию в сериализаторе модели, вы можете сделать это, используя опции fields или exclude, так же, как и с ModelForm. Настоятельно рекомендуется явно устанавливать все поля, которые должны быть сериализованы, используя атрибут fields. Это уменьшит вероятность непреднамеренного раскрытия данных при изменении ваших моделей.
Например:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
Вы также можете установить атрибут fields в специальное значение '__all__' для указания того, что все поля в модели должны быть использованы.
Например:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = '__all__'
Вы можете установить атрибут exclude в список полей, которые нужно исключить из сериализатора.
Например:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
exclude = ['users']
В приведенном выше примере, если модель Account имела 3 поля account_name, users, и created, это приведет к тому, что поля account_name и created будут сериализованы.
Имена в атрибутах fields и exclude обычно соответствуют полям модели в классе модели.
В качестве альтернативы, имена в опциях fields могут соответствовать свойствам или методам, не принимающим аргументы, которые существуют в классе модели.
С версии 3.3.0 обязательно указать один из атрибутов fields или exclude.
Указание вложенной сериализации
По умолчанию ModelSerializer использует первичные ключи для отношений, но вы также можете легко генерировать вложенные представления, используя опцию depth:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
depth = 1
Опция depth должна быть установлена в целое число, которое указывает глубину отношений, которые необходимо пройти, прежде чем вернуться к плоскому представлению.
Если вы хотите настроить способ выполнения сериализации, вам нужно будет определить поле самостоятельно.
Явное указание полей
Вы можете добавить дополнительные поля к ModelSerializer или переопределить поля по умолчанию, объявив поля в классе, так же, как и для класса Serializer.
class AccountSerializer(serializers.ModelSerializer):
url = serializers.CharField(source='get_absolute_url', read_only=True)
groups = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Account
fields = ['url', 'groups']
Дополнительные поля могут соответствовать любому свойству или вызываемому элементу в модели.
Указание только для чтения полей
Вы можете указать несколько полей только для чтения. Вместо явного добавления каждого поля с помощью атрибута read_only=True, вы можете использовать сокращенную опцию Meta, read_only_fields.
Эта опция должна быть списком или кортежем имен полей и объявляется следующим образом:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
read_only_fields = ['account_name']
Поля модели, для которых editable=False установлено, и поля AutoField по умолчанию будут установлены для только для чтения и не нуждаются в добавлении в опцию read_only_fields.
Примечание: Существует особый случай, когда поле только для чтения является частью ограничения unique_together на уровне модели. В этом случае поле необходимо классу сериализатора для проверки ограничения, но также не должно быть доступно для редактирования пользователем.
Правильный способ решения этой проблемы — явно указать поле в сериализаторе, предоставив оба ключевых аргумента read_only=True и default=….
Один пример этого — поле только для чтения, связанное с текущим аутентифицированным пользователем User, которое unique_together с другим идентификатором. В этом случае вы объявляете поле пользователя следующим образом:
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
См. документацию по валидаторам для получения подробных сведений о классах UniqueTogetherValidator и CurrentUserDefault.
Дополнительные ключевые аргументы
Также есть сокращение, позволяющее указывать произвольные дополнительные ключевые аргументы для полей, используя опцию extra_kwargs. Как и в случае с read_only_fields, это означает, что вам не нужно явно объявлять поле в сериализаторе.
Эта опция представляет собой словарь, сопоставляющий имена полей со словарем ключевых аргументов. Например:
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(
email=validated_data['email'],
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
Пожалуйста, имейте в виду, что если поле уже явно объявлено в классе класса сериализатора, то опция extra_kwargs будет проигнорирована.
Поля отношений
При сериализации экземпляров моделей существует множество способов представления отношений. По умолчанию представление ModelSerializer использует первичные ключи связанных экземпляров.
Альтернативные представления включают сериализацию с использованием гиперссылок, сериализацию полных вложенных представлений или сериализацию с пользовательским представлением.
Полные подробности см. в документации по отношениям сериализатора.
Настройка сопоставлений полей
Класс ModelSerializer также предоставляет API, который вы можете переопределить, чтобы изменить способ автоматического определения полей сериализатора при создании экземпляра сериализатора.
Обычно, если ModelSerializer не генерирует необходимые поля по умолчанию, вы должны либо добавить их в класс явно, либо просто использовать обычный класс Serializer вместо этого. Однако в некоторых случаях вы можете создать новый базовый класс, который определяет способ создания полей сериализатора для любой заданной модели.
serializer_field_mapping
Сопоставление полей Django модели с полями сериализатора REST framework. Вы можете переопределить это отображение, чтобы изменить поля сериализатора по умолчанию, которые должны использоваться для каждого поля модели.
serializer_related_field
Это свойство должно быть классом поля сериализатора, используемым для полей отношений по умолчанию.
Для ModelSerializer по умолчанию используется serializers.PrimaryKeyRelatedField.
Для HyperlinkedModelSerializer по умолчанию используется serializers.HyperlinkedRelatedField.
serializer_url_field
Класс поля сериализатора, который должен использоваться для любого поля url в сериализаторе.
По умолчанию serializers.HyperlinkedIdentityField.
serializer_choice_field
Класс поля сериализатора, который должен использоваться для любых полей выбора в сериализаторе.
По умолчанию serializers.ChoiceField.
API field_class и field_kwargs
Следующие методы вызываются для определения класса и ключевых аргументов для каждого поля, которое должно быть автоматически включено в сериализатор. Каждый из этих методов должен возвращать кортеж из двух элементов (field_class, field_kwargs).
build_standard_field(self, field_name, model_field)
Вызывается для создания поля сериализатора, которое соответствует стандартному полю модели.
Реализация по умолчанию возвращает класс сериализатора на основе атрибута serializer_field_mapping.
build_relational_field(self, field_name, relation_info)
Вызывается для генерации поля сериализатора, сопоставленного с полем реляционной модели.
По умолчанию возвращает класс сериализатора, основанный на атрибуте serializer_related_field.
Аргумент relation_info — именованная кортеж, содержащая свойства model_field, related_model, to_many и has_through_model.
build_nested_field(self, field_name, relation_info, nested_depth)
Вызывается для генерации поля сериализатора, сопоставленного с полем реляционной модели, когда установлен параметр depth.
По умолчанию динамически создает вложенный класс сериализатора, основанный на ModelSerializer или HyperlinkedModelSerializer.
Значение nested_depth будет равно значению параметра depth минус единица.
Аргумент relation_info — именованная кортеж, содержащая свойства model_field, related_model, to_many и has_through_model.
build_property_field(self, field_name, model_class)
Вызывается для генерации поля сериализатора, сопоставленного со свойством или методом без аргументов в классе модели.
По умолчанию возвращает класс ReadOnlyField.
build_url_field(self, field_name, model_class)
Вызывается для генерации поля сериализатора для собственного поля url сериализатора. По умолчанию возвращает класс HyperlinkedIdentityField.
build_unknown_field(self, field_name, model_class)
Вызывается, когда имя поля не было сопоставлено с ни одним полем модели или свойством модели. По умолчанию генерируется ошибка, хотя подклассы могут настроить это поведение.
HyperlinkedModelSerializer
Класс HyperlinkedModelSerializer похож на класс ModelSerializer, за исключением того, что он использует гиперссылки для представления отношений, а не первичные ключи.
По умолчанию сериализатор будет включать поле url вместо поля первичного ключа.
Поле url будет представлено с помощью поля сериализатора HyperlinkedIdentityField, а любые отношения в модели будут представлены с помощью поля сериализатора HyperlinkedRelatedField.
Вы можете явно включить первичный ключ, добавив его в параметр fields, например:
class AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Account
fields = ['url', 'id', 'account_name', 'users', 'created']
Абсолютные и относительные URL-адреса
При создании экземпляра HyperlinkedModelSerializer необходимо указать текущий request в контексте сериализатора, например:
serializer = AccountSerializer(queryset, context={'request': request})
Это гарантирует, что гиперссылки могут включать соответствующий хост, чтобы результирующее представление использовало полные URL-адреса, такие как:
http://api.example.com/accounts/1/
Вместо относительных URL-адресов, таких как:
/accounts/1/
Если вам нужны относительные URL-адреса, вы должны явно передать {'request': None} в контексте сериализатора.
Как определяются гиперссылочные представления
Необходимо иметь возможность определять, какие представления должны использоваться для создания гиперссылок на экземпляры моделей.
По умолчанию ожидается, что гиперссылки соответствуют имени представления, которое соответствует шаблону '{model_name}-detail', и находит экземпляр по параметру pk.
Вы можете переопределить имя представления и поле поиска поля URL, используя либо view_name, либо lookup_field в настройке extra_kwargs, как показано ниже:
class AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Account
fields = ['account_url', 'account_name', 'users', 'created']
extra_kwargs = {
'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
'users': {'lookup_field': 'username'}
}
В качестве альтернативы вы можете явно задать поля в сериализаторе. Например:
class AccountSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='accounts',
lookup_field='slug'
)
users = serializers.HyperlinkedRelatedField(
view_name='user-detail',
lookup_field='username',
many=True,
read_only=True
)
class Meta:
model = Account
fields = ['url', 'account_name', 'users', 'created']
Подсказка: Правильное сопоставление гиперссылочных представлений и файла конфигурации URL-адресов может иногда быть немного сложным. Вывод repr экземпляра HyperlinkedModelSerializer — особенно полезный способ проверить, к каким именам представлений и полям поиска должны быть сопоставлены отношения.
Изменение имени поля URL
Имя поля URL по умолчанию — 'url'. Вы можете переопределить его глобально, используя настройку URL_FIELD_NAME.
ListSerializer
Класс ListSerializer обеспечивает поведение для сериализации и валидации нескольких объектов сразу. Вам обычно не нужно использовать ListSerializer напрямую, а следует передавать many=True при создании экземпляра сериализатора.
При создании экземпляра сериализатора и передаче many=True, будет создан экземпляр ListSerializer. Затем класс сериализатора станет дочерним классом родительского ListSerializer.
Следующий аргумент также может быть передан в поле ListSerializer или в сериализатор, которому передаётся many=True.
allow_empty
По умолчанию значение True, но может быть установлено в значение False, если вы хотите запретить пустые списки как допустимый ввод.
max_length
По умолчанию значение None, но может быть установлено в положительное целое число, если вы хотите проверить, что список содержит не более этого количества элементов.
min_length
По умолчанию значение None, но может быть установлено в положительное целое число, если вы хотите проверить, что список содержит не менее этого количества элементов.
Настройка поведения ListSerializer
Существует несколько случаев, когда вам может потребоваться настроить поведение ListSerializer. Например:
- Вы хотите обеспечить определённую валидацию списков, например, проверку того, что один элемент не конфликтует с другим элементом в списке.
- Вы хотите настроить поведение создания или обновления нескольких объектов.
В этих случаях вы можете изменить класс, используемый при передаче many=True, используя параметр list_serializer_class в классе сериализатора Meta.
Например:
class CustomListSerializer(serializers.ListSerializer):
...
class CustomSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = CustomListSerializer
Настройка создания нескольких объектов
По умолчанию реализация создания нескольких объектов заключается в простом вызове .create() для каждого элемента в списке. Если вы хотите настроить это поведение, вам необходимо настроить метод .create() в классе ListSerializer, используемом при передаче many=True.
Например:
class BookListSerializer(serializers.ListSerializer):
def create(self, validated_data):
books = [Book(**item) for item in validated_data]
return Book.objects.bulk_create(books)
class BookSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = BookListSerializer
Настройка обновления нескольких объектов
По умолчанию класс ListSerializer не поддерживает множественное обновление. Это связано с тем, что ожидаемое поведение для вставки и удаления является неоднозначным.
Чтобы поддержать множественное обновление, вам необходимо сделать это явно. При написании кода для множественного обновления помните о следующем:
- Как вы определяете, какой экземпляр должен быть обновлён для каждого элемента в списке данных?
- Как обрабатывать вставки? Являются ли они недопустимыми, или они создают новые объекты?
- Как обрабатывать удаления? Подразумевают ли они удаление объекта или удаление связи? Должны ли они игнорироваться или они недопустимы?
- Как обрабатывать порядок? Изменение позиции двух элементов подразумевает какие-либо изменения состояния или это игнорируется?
Вам необходимо добавить явное поле id в сериализатор экземпляра. По умолчанию неявно сгенерированное поле id помечено как read_only. Это приводит к тому, что оно удаляется при обновлении. После явного объявления оно будет доступно в методе update сериализатора списка.
Вот пример того, как вы можете реализовать множественное обновление:
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
# Maps for id->instance and id->data item.
book_mapping = {book.id: book for book in instance}
data_mapping = {item['id']: item for item in validated_data}
# Perform creations and updates.
ret = []
for book_id, data in data_mapping.items():
book = book_mapping.get(book_id, None)
if book is None:
ret.append(self.child.create(data))
else:
ret.append(self.child.update(book, data))
# Perform deletions.
for book_id, book in book_mapping.items():
if book_id not in data_mapping:
book.delete()
return ret
class BookSerializer(serializers.Serializer):
# We need to identify elements in the list using their primary key,
# so use a writable field here, rather than the default which would be read-only.
id = serializers.IntegerField()
...
class Meta:
list_serializer_class = BookListSerializer
Настройка инициализации ListSerializer
При создании экземпляра сериализатора с many=True, нам нужно определить, какие аргументы и ключевые аргументы должны быть переданы в метод .__init__() для дочернего класса Serializer и родительского класса ListSerializer.
По умолчанию все аргументы передаются в оба класса, за исключением validators и любых пользовательских ключевых аргументов, которые предполагается использовать для дочернего класса сериализатора.
Иногда вам может потребоваться явно указать, как должны быть созданы дочерний и родительский классы при передаче many=True. Это можно сделать с помощью метода класса many_init.
@classmethod
def many_init(cls, *args, **kwargs):
# Instantiate the child serializer.
kwargs['child'] = cls()
# Instantiate the parent list serializer.
return CustomListSerializer(*args, **kwargs)
BaseSerializer
Класс BaseSerializer, который можно использовать для лёгкой поддержки альтернативных стилей сериализации и десериализации.
Этот класс реализует тот же базовый API, что и класс Serializer.
-
.data- Возвращает исходящее примитивное представление. -
.is_valid()- Десериализует и валидирует входящие данные. -
.validated_data- Возвращает проверенные входящие данные. -
.errors- Возвращает любые ошибки при валидации. -
.save()- Сохраняет проверенные данные в экземпляре объекта.
Есть четыре метода, которые можно переопределить в зависимости от функциональности, которую должен поддерживать класс сериализатора:
-
.to_representation()- Переопределите его, чтобы поддерживать сериализацию для операций чтения. -
.to_internal_value()- Переопределите его, чтобы поддерживать десериализацию для операций записи. -
.create()и.update()- Переопределите один или оба из них, чтобы поддерживать сохранение экземпляров.
Поскольку этот класс предоставляет тот же интерфейс, что и класс Serializer, вы можете использовать его с существующими универсальными представлениями класса так же, как и для обычных сериализаторов Serializer или ModelSerializer.
Единственное отличие, которое вы заметите, когда будете это делать, заключается в том, что классы BaseSerializer не будут генерировать HTML-формы в обозреваемом API. Это связано с тем, что возвращаемые ими данные не содержат всей информации о поле, которая позволила бы отобразить каждое поле в подходящий HTML-вход.
Только для чтения BaseSerializer классы
Для реализации только-для-чтения сериализатора, использующего класс BaseSerializer, нам нужно переопределить метод .to_representation(). Давайте рассмотрим пример с использованием простой модели Django:
class HighScore(models.Model):
created = models.DateTimeField(auto_now_add=True)
player_name = models.CharField(max_length=10)
score = models.IntegerField()
Просто создать только-для-чтения сериализатор для преобразования экземпляров HighScore в примитивные типы данных.
class HighScoreSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
'score': instance.score,
'player_name': instance.player_name
}
Теперь мы можем использовать этот класс для сериализации отдельных экземпляров HighScore.
@api_view(['GET'])
def high_score(request, pk):
instance = HighScore.objects.get(pk=pk)
serializer = HighScoreSerializer(instance)
return Response(serializer.data)
Или использовать его для сериализации нескольких экземпляров:
@api_view(['GET'])
def all_high_scores(request):
queryset = HighScore.objects.order_by('-score')
serializer = HighScoreSerializer(queryset, many=True)
return Response(serializer.data)
Классы BaseSerializer для чтения и записи
Для создания сериализатора чтения-записи, в первую очередь, необходимо реализовать метод .to_internal_value(). Этот метод возвращает валидированные значения, которые будут использованы для создания экземпляра объекта, и может вызвать исключение serializers.ValidationError, если предоставленные данные имеют неверный формат.
После реализации .to_internal_value(), базовый API валидации будет доступен в сериализаторе, и вы сможете использовать .is_valid(), .validated_data и .errors.
Если вы хотите также поддерживать .save(), вам необходимо реализовать один или оба метода .create() и .update().
Вот полный пример нашего предыдущего HighScoreSerializer, обновленный для поддержки операций чтения и записи.
class HighScoreSerializer(serializers.BaseSerializer):
def to_internal_value(self, data):
score = data.get('score')
player_name = data.get('player_name')
# Perform the data validation.
if not score:
raise serializers.ValidationError({
'score': 'This field is required.'
})
if not player_name:
raise serializers.ValidationError({
'player_name': 'This field is required.'
})
if len(player_name) > 10:
raise serializers.ValidationError({
'player_name': 'May not be more than 10 characters.'
})
# Return the validated values. This will be available as
# the `.validated_data` property.
return {
'score': int(score),
'player_name': player_name
}
def to_representation(self, instance):
return {
'score': instance.score,
'player_name': instance.player_name
}
def create(self, validated_data):
return HighScore.objects.create(**validated_data)
Создание новых базовых классов
Класс BaseSerializer также полезен, если вы хотите реализовать новые обобщенные классы сериализаторов для работы с определенными стилями сериализации или для интеграции с альтернативными хранилищами.
Следующий класс является примером обобщенного сериализатора, который может обрабатывать приведение произвольных сложных объектов к примитивным представлениям.
class ObjectSerializer(serializers.BaseSerializer):
"""
A read-only serializer that coerces arbitrary complex objects
into primitive representations.
"""
def to_representation(self, instance):
output = {}
for attribute_name in dir(instance):
attribute = getattr(instance, attribute_name)
if attribute_name.startswith('_'):
# Ignore private attributes.
pass
elif hasattr(attribute, '__call__'):
# Ignore methods and other callables.
pass
elif isinstance(attribute, (str, int, bool, float, type(None))):
# Primitive types can be passed through unmodified.
output[attribute_name] = attribute
elif isinstance(attribute, list):
# Recursively deal with items in lists.
output[attribute_name] = [
self.to_representation(item) for item in attribute
]
elif isinstance(attribute, dict):
# Recursively deal with items in dictionaries.
output[attribute_name] = {
str(key): self.to_representation(value)
for key, value in attribute.items()
}
else:
# Force anything else to its string representation.
output[attribute_name] = str(attribute)
return output
Расширенное использование сериализаторов
Переопределение поведения сериализации и десериализации
Если вам нужно изменить поведение сериализации или десериализации класса сериализатора, вы можете сделать это, переопределив методы .to_representation() или .to_internal_value().
Некоторые причины, по которым это может быть полезно, включают...
- Добавление нового поведения для новых базовых классов сериализаторов.
- Незначительное изменение поведения для существующего класса.
- Улучшение производительности сериализации для часто используемого API-интерфейса, который возвращает много данных.
Вот сигнатуры этих методов:
to_representation(self, instance)
Принимает экземпляр объекта, требующий сериализации, и должен вернуть примитивное представление. Как правило, это означает возврат структуры встроенных типов данных Python. Точные типы, которые могут быть обработаны, зависят от классов рендеринга, которые вы настроили для вашего API.
Может быть переопределен для изменения стиля представления. Например:
def to_representation(self, instance):
"""Convert `username` to lowercase."""
ret = super().to_representation(instance)
ret['username'] = ret['username'].lower()
return ret
to_internal_value(self, data)
Принимает невалидированные входные данные в качестве входных и должен вернуть валидированные данные, которые будут доступны как serializer.validated_data. Значение возврата также будет передано методам .create() или .update() при вызове .save() в классе сериализатора.
Если какой-либо из проверок валидации не пройден, то метод должен вызвать исключение serializers.ValidationError(errors). Аргумент errors должен быть словарем, сопоставляющим имена полей (или settings.NON_FIELD_ERRORS_KEY) с списком сообщений об ошибках. Если вам не нужно изменять поведение десериализации, а вместо этого вы хотите предоставить проверку на уровне объекта, рекомендуется вместо этого переопределить метод .validate().
Аргумент data, переданный в этот метод, обычно будет значением request.data, поэтому тип данных будет зависеть от классов парсеров, настроенных для вашего API.
Наследование сериализаторов
Аналогично Django forms, вы можете расширять и повторно использовать сериализаторы с помощью наследования. Это позволяет вам объявлять общий набор полей или методов в родительском классе, которые затем могут быть использованы в ряде сериализаторов. Например,
class MyBaseSerializer(Serializer):
my_field = serializers.CharField()
def validate_my_field(self, value):
...
class MySerializer(MyBaseSerializer):
...
Как и классы Model и ModelForm Django, внутренний класс Meta в сериализаторах не наследуется неявно от внутренних классов Meta своих родителей. Если вы хотите, чтобы класс Meta наследовал от родительского класса, вы должны сделать это явно. Например:
class AccountSerializer(MyBaseSerializer):
class Meta(MyBaseSerializer.Meta):
model = Account
Как правило, мы рекомендуем не использовать наследование для внутренних классов Meta, а вместо этого объявлять все параметры явно.
Кроме того, следующие замечания относятся к наследованию сериализаторов:
- Применяются стандартные правила разрешения имен Python. Если у вас есть несколько базовых классов, которые объявляют внутренний класс
Meta, будет использоваться только первый. Это означает, чтоMeta, если он существует, в противном случаеMetaпервого родительского класса и т.д. -
Возможна декларативная отмена наследования
Fieldот родительского класса, установив имя наNoneв подклассе.class MyBaseSerializer(ModelSerializer): my_field = serializers.CharField() class MySerializer(MyBaseSerializer): my_field = NoneОднако этот метод можно использовать только для отказа от поля, объявленного декларативно родительским классом; он не предотвратит создание
ModelSerializerпо умолчанию. Для отказа от полей по умолчанию см. Указание полей, которые нужно включить.
Динамическое изменение полей
После инициализации сериализатора словарь полей, заданных в сериализаторе, можно получить с помощью атрибута .fields. Доступ к этому атрибуту и его изменение позволяют динамически изменять сериализатор.
Изменение аргумента fields напрямую позволяет делать интересные вещи, такие как изменение аргументов полей сериализатора во время выполнения, а не в момент объявления сериализатора.
Пример
Например, если вы хотите задать поля, которые должен использовать сериализатор, на момент его инициализации, вы можете создать класс сериализатора следующим образом:
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super().__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
Это позволит вам сделать следующее:
>>> class UserSerializer(DynamicFieldsModelSerializer):
>>> class Meta:
>>> model = User
>>> fields = ['id', 'username', 'email']
>>>
>>> print(UserSerializer(user))
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
>>>
>>> print(UserSerializer(user, fields=('id', 'email')))
{'id': 2, 'email': 'jon@example.com'}
Настройка полей по умолчанию
REST framework 2 предоставлял API для переопределения того, как класс ModelSerializer автоматически генерировал стандартный набор полей.
Этот API включал методы .get_field(), .get_pk_field() и другие.
Поскольку сериализаторы были радикально переработаны в версии 3.0, этот API больше не существует. Вы по-прежнему можете изменять созданные поля, но вам нужно обратиться к исходному коду и знать, что если внесенные изменения касаются частных частей API, они могут быть изменены.
Пакеты сторонних разработчиков
Доступны следующие пакеты сторонних разработчиков.
Django REST marshmallow
Пакет django-rest-marshmallow предоставляет альтернативную реализацию сериализаторов, использующую библиотеку python marshmallow. Он предоставляет тот же API, что и сериализаторы REST framework, и может быть использован как замена в некоторых случаях.
Serpy
Пакет serpy — это альтернативная реализация сериализаторов, созданная для скорости. Serpy сериализует сложные типы данных в простые базовые типы. Базовые типы легко преобразуются в JSON или любой другой требуемый формат.
MongoengineModelSerializer
Пакет django-rest-framework-mongoengine предоставляет класс сериализатора MongoEngineModelSerializer, который поддерживает использование MongoDB в качестве хранилища для Django REST framework.
GeoFeatureModelSerializer
Пакет django-rest-framework-gis предоставляет класс сериализатора GeoFeatureModelSerializer, который поддерживает GeoJSON для операций чтения и записи.
HStoreSerializer
Пакет django-rest-framework-hstore предоставляет HStoreSerializer для поддержки поля модели django-hstore DictionaryField и его функции schema-mode.
Dynamic REST
Пакет dynamic-rest расширяет интерфейсы ModelSerializer и ModelViewSet, добавляя параметры запроса API для фильтрации, сортировки и включения/исключения всех полей и отношений, определенных вашими сериализаторами.
Mixin динамических полей
Пакет drf-dynamic-fields предоставляет миксин для динамического ограничения полей каждого сериализатора подмножеством, указанным в параметре URL.
DRF FlexFields
Пакет drf-flex-fields расширяет ModelSerializer и ModelViewSet, чтобы предоставить часто используемые функции для динамического задания полей и расширения примитивных полей до вложенных моделей, как из параметров URL, так и из определений вашего класса сериализатора.
Расширения сериализаторов
Пакет django-rest-framework-serializer-extensions предоставляет набор инструментов для сокращения кода сериализаторов, позволяя определять поля на уровне представления/запроса. Поля могут быть включены в белый список, исключены из черного списка, и вложенные сериализаторы могут быть дополнительно развернуты.
HTML JSON Forms
Пакет html-json-forms предоставляет алгоритм и сериализатор для обработки <form> представлений в соответствии со (неактивным) спецификацией HTML JSON Form. Сериализатор обеспечивает обработку произвольно вложенных JSON-структур внутри HTML. Например, <input name="items[0][id]" value="5"> будет интерпретироваться как {"items": [{"id": "5"}]}.
DRF-Base64
DRF-Base64 предоставляет набор полей и сериализаторов моделей, которые обрабатывают загрузку файлов, закодированных в base64.
QueryFields
djangorestframework-queryfields позволяет клиентам API указывать поля, которые будут отправлены в ответе, используя параметры запроса для включения/исключения.
DRF Writable Nested
Пакет drf-writable-nested предоставляет сериализатор вложенных моделей с возможностью записи, который позволяет создавать/обновлять модели с вложенными связанными данными.
DRF Encrypt Content
Пакет drf-encrypt-content помогает шифровать данные, сериализованные с помощью ModelSerializer. Он также содержит некоторые вспомогательные функции, которые помогают шифровать данные.
Copyright © 2011–present Encode OSS Ltd.
Licensed under the BSD License.
https://www.django-rest-framework.org/api-guide/serializers/