Cериализаторы

Cериализаторы

Нам бы очень хотелось расширить применение сериализаторов, но это не тривиальная проблема, и она требует серьезной работы.
— Russell Keith-Magee, Django users group
Сериализаторы позволяют преобразовывать сложные данные, такие как querysets и экземпляры моделей, в нативные типы данных Python, которые затем могут быть легко срендерены в JSON, XML или другие типы контента. Сериализаторы также обеспечивают десериализацию, позволяя преобразовать спарсенные данные обратно в сложные типы после проверки входящих данных.
Сериализаторы в REST framework работают аналогично классам Django Form и ModelForm. Мы предоставляем класс Serializer, который дает вам мощный, общий способ управления вашими ответами, а также класс ModelSerializer- полезный и быстрый способ создания сериализаторов, которые имеют дело с экземплярами модели и querysets.

Объявление сериализаторов

Сперва создадим простой проект, на котором будем демонстрировать примеры:
1
from datetime import datetime
2
3
class Comment(object):
4
def __init__(self, email, content, created=None):
5
self.email = email
6
self.content = content
7
self.created = created or datetime.now()
8
9
comment = Comment(email='[email protected]', content='foo bar')
Copied!
Мы объявим сериализатор, который мы можем использовать для сериализации и десериализации данных, соответствующих объектам Comment.
Объявление сериализатора очень похоже на объявление формы:
1
from rest_framework import serializers
2
3
class CommentSerializer(serializers.Serializer):
4
email = serializers.EmailField()
5
content = serializers.CharField(max_length=200)
6
created = serializers.DateTimeField()
Copied!

Сериализация объектов

Теперь мы можем использовать CommentSerializer для сериализации комментария или списка комментариев. Опять же, использование класса Serializer очень похоже на использование класса Form.
1
serializer = CommentSerializer(comment)
2
serializer.data
3
# {'email': '[email protected]', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
Copied!
На этом этапе мы преобразовали экземпляр модели в нативные типы данных Python. Чтобы завершить процесс сериализации, мы рендерим данные в json.
1
from rest_framework.renderers import JSONRenderer
2
3
json = JSONRenderer().render(serializer.data)
4
json
5
# b'{"email":"[email protected]","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'
Copied!

Десериализация объектов

Аналогично проходит десериализация. Сначала мы парсим поток в нативные типы данных Python ...
1
from django.utils.six import BytesIO
2
from rest_framework.parsers import JSONParser
3
4
stream = BytesIO(json)
5
data = JSONParser().parse(stream)
Copied!
...затем мы сохраняем эти нативные типы данных в словарь валидных данных.
1
serializer = CommentSerializer(data=data)
2
serializer.is_valid()
3
# True
4
serializer.validated_data
5
# {'content': 'foo bar', 'email': '[email protected]', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
Copied!

Сохранение экземпляров

Если мы хотим иметь возможность возвращать полные экземпляры объектов на основе проверенных данных, нам нужно реализовать один или оба метода .create() и update(). Например:
1
class CommentSerializer(serializers.Serializer):
2
email = serializers.EmailField()
3
content = serializers.CharField(max_length=200)
4
created = serializers.DateTimeField()
5
6
def create(self, validated_data):
7
return Comment(**validated_data)
8
9
def update(self, instance, validated_data):
10
instance.email = validated_data.get('email', instance.email)
11
instance.content = validated_data.get('content', instance.content)
12
instance.created = validated_data.get('created', instance.created)
13
return instance
Copied!
Если экземпляры объектов соответствуют моделям Django, то вам также нужнр убедиться, что эти методы сохраняют объект в базе данных. Например, если Comment был моделью Django, методы могут выглядеть так:
1
def create(self, validated_data):
2
return Comment.objects.create(**validated_data)
3
4
def update(self, instance, validated_data):
5
instance.email = validated_data.get('email', instance.email)
6
instance.content = validated_data.get('content', instance.content)
7
instance.created = validated_data.get('created', instance.created)
8
instance.save()
9
return instance
Copied!
Теперь при десериализации данных мы можем вызвать .save(), чтобы вернуть экземпляр объекта на основе проверенных данных.
1
comment = serializer.save()
Copied!
При вызове .save() будет создаваться либо новый экземпляр, либо обновляться существующий экземпляр, в зависимости от того, был ли передан существующий экземпляр при создании экземпляра класса сериализатора:
1
# .save() will create a new instance.
2
serializer = CommentSerializer(data=data)
3
4
# .save() will update the existing `comment` instance.
5
serializer = CommentSerializer(comment, data=data)
Copied!
Оба метода .create() и .update() являются необязательными. Вы можете применять их вмете, по отдельности, либо вообще отказаться от них, в зависимости от конкретного случая использования вашего класса сериализатора.

Передача дополнительных атрибутов в .save()

Иногда вам нужно, чтобы код представления мог добавлять дополнительные данные в момент сохранения экземпляра. Эти дополнительные данные могут включать в себя информацию, такую как текущий пользователь, текущее время или что-либо еще, что не является частью данных запроса.
Вы можете сделать это, указав дополнительные аргументы ключевого слова при вызове .save(). Например:
1
serializer.save(owner=request.user)
Copied!
Любые дополнительные аргументы ключевого слова будут включены в аргумент validated_data при вызове .create() или .update().

Переопределение .save() напрямую.

В некоторых случаях имена методов .create() и .update() могут не иметь смысла. Например, в форме контакта мы можем не создавать новые экземпляры, а вместо этого отправлять электронную почту или другое сообщение.
В этих случаях вы можете переопределить .save() напрямую, в целях читабельности и прозрачности.
Например:
1
class ContactForm(serializers.Serializer):
2
email = serializers.EmailField()
3
message = serializers.CharField()
4
5
def save(self):
6
email = self.validated_data['email']
7
message = self.validated_data['message']
8
send_email(from=email, message=message)
Copied!
Обратите внимание, что в приведенном выше случае нам теперь нужно напрямую получить доступ к свойству serialval.validated_data.

Проверка (Validation)

При десериализации данных вам всегда нужно вызватьis_valid(), прежде чем пытаться получить доступ к проверенным данным или сохранить экземпляр объекта. Если возникнут какие-либо ошибки проверки, свойство .errors будет содержать словарь, представляющий сообщения об ошибках. Например:
1
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
2
serializer.is_valid()
3
# False
4
serializer.errors
5
# {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}
Copied!
Каждый ключ в словаре будет именем поля, а значениями будут списками строк любых сообщений об ошибках, соответствующих этому полю. Также может присутствовать ключ non_field_errors, который перечисляет любые общие ошибки валидности. Имя ключа non_field_errors может быть настроено с использованием параметра NON_FIELD_ERRORS_KEY REST.
При десериализации списка элементов ошибки будут возвращаться в виде списка словарей, представляющих каждый из десериализованных элементов.

Получение исключения по недействительным данным

Метод .is_valid() принимает необязательный флаг raise_exception, который заставит его вызвать исключение serializers.ValidationError, в случае, если есть ошибки проверки.
Эти исключения автоматически обрабатываются обработчиком исключений, который предоставляет REST framework, и будет возвращать ответы HTTP 400 Bad Request по умолчанию.
1
# Return a 400 response if the data was invalid.
2
serializer.is_valid(raise_exception=True)
Copied!

Проверка на уровне поля

Вы можете указать настраиваемую проверку на уровне поля, добавив методы .validate_<field_name> в ваш подкласс Serializer. Они аналогичны методам .clean_<field_name> в формах Django.
Эти методы принимают один аргумент, который является значением поля, требующим проверки.
Методы .validate_<field_name> должны возвращать проверенное значение или вызывать serializers.ValidationError. Например:
1
from rest_framework import serializers
2
3
class BlogPostSerializer(serializers.Serializer):
4
title = serializers.CharField(max_length=100)
5
content = serializers.CharField()
6
7
def validate_title(self, value):
8
"""
9
Check that the blog post is about Django.
10
"""
11
if 'django' not in value.lower():
12
raise serializers.ValidationError("Blog post is not about Django")
13
return value
Copied!
Примечание: Если ваш <field_name> объявлен в вашем сериализаторе с параметром required = False, то этот шаг проверки не будет выполняться, если поле не включено.

Проверка на уровне объекта

Чтобы выполнить любую другую проверку, требующую доступа к нескольким полям, добавьте метод под названием .validate() в ваш подкласс Serializer. Этот метод принимает один аргумент, который является словарем значений полей. При необходимости он должен вызвать serializers.ValidationError или просто вернуть проверенные значения. Например:
1
from rest_framework import serializers
2
3
class EventSerializer(serializers.Serializer):
4
description = serializers.CharField(max_length=100)
5
start = serializers.DateTimeField()
6
finish = serializers.DateTimeField()
7
8
def validate(self, data):
9
"""
10
Check that the start is before the stop.
11
"""
12
if data['start'] > data['finish']:
13
raise serializers.ValidationError("finish must occur after start")
14
return data
Copied!

Валидаторы

В отдельные поля в сериализаторе можно включить валидаторы, объявив их в экземпляре поля, например:
1
def multiple_of_ten(value):
2
if value % 10 != 0:
3
raise serializers.ValidationError('Not a multiple of ten')
4
5
class GameRecord(serializers.Serializer):
6
score = IntegerField(validators=[multiple_of_ten])
7
...
Copied!
Классы сериализаторов могут также включать повторно используемые валидаторы, которые применяются к полному набору данных поля. Эти валидаторы подключаются путем объявления их во внутреннем мета-классе, например:
1
class EventSerializer(serializers.Serializer):
2
name = serializers.CharField()
3
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
4
date = serializers.DateField()
5
6
class Meta:
7
# Each room only has one event per day.
8
validators = UniqueTogetherValidator(
9
queryset=Event.objects.all(),
10
fields=['room_number', 'date']
11
)
Copied!

Доступ к исходным данным и экземпляру

При передаче исходного объекта или queryset в экземпляр сериализатора объект будет доступен как .instance. Если не было передано никакого начального объекта, то атрибут .instance будет None.
При передаче данных в экземпляр сериализатора немодифицированные данные будут доступны как .initial_data. Если аргумент ключевого слова данных не передается, атрибут .initial_data не будет создан.

Частичные обновления

По умолчанию, сериализаторам должны быть переданы значения для всех обязательных полей, в противном случае они вызовут ошибку валидации. Вы можете использовать аргумент partial, чтобы разрешить частичные обновления.
1
# Update `comment` with partial data
2
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
Copied!

Работа с вложенными объектами

Предыдущие примеры хорошо подходят для работы с объектами, которые имеют только простые типы данных, но иногда нам также нужно иметь возможность представлять более сложные объекты, где некоторые атрибуты объекта могут быть не простыми типами данных, такими как строки, даты или целые числа.
Класс Serializer сам по себе является типом Field и может использоваться для представления отношений, в которых один тип объекта вложен внутри другого.
1
class UserSerializer(serializers.Serializer):
2
email = serializers.EmailField()
3
username = serializers.CharField(max_length=100)
4
5
class CommentSerializer(serializers.Serializer):
6
user = UserSerializer()
7
content = serializers.CharField(max_length=200)
8
created = serializers.DateTimeField()
Copied!
Если вложенное представление может опционально принимать значение None, вы должны передать флаг required = False вложенному сериализатору.
1
class CommentSerializer(serializers.Serializer):
2
user = UserSerializer(required=False) # May be an anonymous user.
3
content = serializers.CharField(max_length=200)
4
created = serializers.DateTimeField()
Copied!
Аналогично, если вложенное представление должно быть списком элементов, вы должны передать флаг many = True вложенному сериализатору.
1
class CommentSerializer(serializers.Serializer):
2
user = UserSerializer(required=False)
3
edits = EditItemSerializer(many=True) # A nested list of 'edit' items.
4
content = serializers.CharField(max_length=200)
5
created = serializers.DateTimeField()
Copied!

Writable вложенные представления

При работе с вложенными представлениями, которые поддерживают десериализацию данных, любые ошибки с вложенными объектами будут вложены под именем поля вложенного объекта.
1
serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
2
serializer.is_valid()
3
# False
4
serializer.errors
5
# {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}
Copied!
Аналогично, свойство .validated_data будет включать вложенные структуры данных.

Написание методов .create() для вложенных представлений

Если вы собираетесь поддерживать writable вложенные представления, вам потребуется написать методы .create() или .update(), которые обрабатывают сохранение нескольких объектов.
В следующем примере показано, как можно обрабатывать создание пользователя с вложенным объектом профиля.
1
class UserSerializer(serializers.ModelSerializer):
2
profile = ProfileSerializer()
3
4
class Meta:
5
model = User
6
fields = ('username', 'email', 'profile')
7
8
def create(self, validated_data):
9
profile_data = validated_data.pop('profile')
10
user = User.objects.create(**validated_data)
11
Profile.objects.create(user=user, **profile_data)
12
return user
Copied!

Написание методов .update() для вложенных представлений

Вам нужно будет тщательно подумать о том, как обрабатывать обновления отношений. Например, что из следующего должно произойти, если данные отношений равняются None?
  • Отношение устанавливается как NULL в базе данных.
  • Связанный экземпляр удаляется.
  • Данные игнорируются и экземпляр остается таким, какой он есть.
  • Вызов ошибки проверки.
Ниже приведен пример метода update() на основе нашего UserSerializer.
1
def update(self, instance, validated_data):
2
profile_data = validated_data.pop('profile')
3
# Unless the application properly enforces that this field is
4
# always set, the follow could raise a `DoesNotExist`, which
5
# would need to be handled.
6
profile = instance.profile
7
8
instance.username = validated_data.get('username', instance.username)
9
instance.email = validated_data.get('email', instance.email)
10
instance.save()
11
12
profile.is_premium_member = profile_data.get(
13
'is_premium_member',
14
profile.is_premium_member
15
)
16
profile.has_support_contract = profile_data.get(
17
'has_support_contract',
18
profile.has_support_contract
19
)
20
profile.save()
21
22
return instance
Copied!
Поскольку поведение вложенных create и update может быть неоднозначным и требующим зависимостей между связанными моделями, REST framework 3 требует, чтобы вы всегда записывали эти методы явно. Стандартные методы ModelSerializer .create() и .update() не включают поддержку writable вложенных представлений.

Обработка связанных с сохранением экземпляров в классах менеджера моделей

Альтернативой сохранению нескольких связанных экземпляров в сериализаторе является создание пользовательских классов диспетчера моделей, которые обрабатывают создание правильных экземпляров.
Предположим, что нам нужно убедиться, что экземпляры User и экземпляры Profile всегда создаются парно. Мы могли бы написать собственный класс менеджера, который выглядел бы примерно так:
1
class UserManager(models.Manager):
2
...
3
4
def create(self, username, email, is_premium_member=False, has_support_contract=False):
5
user = User(username=username, email=email)
6
user.save()
7
profile = Profile(
8
user=user,
9
is_premium_member=is_premium_member,
10
has_support_contract=has_support_contract
11
)
12
profile.save()
13
return user
Copied!
Этот класс менеджера теперь лучше инкапсулирует, что экземпляры пользователей и экземпляры профиля всегда создаются одновременно. Наш метод .create()в классе сериализатора теперь может быть переписан для использования нового метода менеджера.
1
def create(self, validated_data):
2
return User.objects.create(
3
username=validated_data['username'],
4
email=validated_data['email']
5
is_premium_member=validated_data['profile']['is_premium_member']
6
has_support_contract=validated_data['profile']['has_support_contract']
7
)
Copied!
Для дополнительной информации по данному методу смотрите документацию Джанго по менеджерам моделей, а также этот пост, посвященный использованию классов моделей и менеджеров.

Работа с несколькими объектами

Класс Serializer также может обрабатывать сериализацию или десериализацию списков объектов.

Сериализация нескольких объектов

Чтобы сериализовать queryset или список объектов вместо экземпляра одного объекта, вы должны передать флаг many = True при создании экземпляра сериализатора. Затем вы можете передать queryset или список объектов для сериализации.
1
queryset = Book.objects.all()
2
serializer = BookSerializer(queryset, many=True)
3
serializer.data
4
# [
5
# {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'},
6
# {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'},
7
# {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
8
# ]
Copied!

Десериализация нескольких объектов

Стандартный процесс десериализации нескольких объектов поддерживает создание нескольких объектов, но не поддерживает обновление нескольких объектов. Дополнительные сведения о поддержке или настройке любого из этих случаев см в ListSerializer ниже.

Включение дополнительного контекста

В некоторых случаях вам необходимо предоставить дополнительный контекст для сериализатора в дополнение к сериализуемому объекту. Распространенный случай, когда вы используете сериализатор, который включает в себя отношения-гиперссылки, это подразумевает, что сериализатор имеет доступ к текущему запросу, чтобы он мог правильно генерировать полные URL-адреса.
Вы можете предоставить произвольный дополнительный контекст, передав аргумент context при создании экземпляра сериализатора. Например:
1
serializer = AccountSerializer(account, context={'request': request})
2
serializer.data
3
# {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
Copied!
Словарь контекста может использоваться в любой логике поля сериализатора, такой как пользовательский метод .to_representation(), путем доступа к атрибуту self.context.

ModelSerializer

Часто вам нужны классы сериализатора, которые тесно связаны с определениями моделей Django.
Класс ModelSerializer позволяет автоматически создавать класс Serializer с полями, соответствующими полям Model.
Класс ModelSerializer совпадает с обычным классом Serializer, за исключением того, что:
  • Он автоматически генерирует набор полей на основе модели.
  • Он автоматически генерирует валидаторы для сериализатора, такие как unique_together.
  • Он включает простые стандартные реализации .create() и .update().
Объявление ModelSerializer выглядит так:
1
class AccountSerializer(serializers.ModelSerializer):
2
class Meta:
3
model = Account
4
fields = ('id', 'account_name', 'users', 'created')
Copied!
По умолчанию все поля модели в классе будут сопоставлены с соответствующими полями сериализатора.
Любые отношения, такие как внешние ключи модели, будут сопоставлены с PrimaryKeyRelatedField. Обратные отношения не включаются по умолчанию, если только это не сделано явно, как указано в документации по отношениям сериализатора.

Проверка ModelSerializer

Классы сериализатора генерируют полезные verbose строки представления, которые позволяют вам полностью проверить состояние ваших полей. Это особенно полезно при работе с ModelSerializers, где вы хотите определить, какой набор полей и валидаторов автоматически создается для вас.
Для этого откройте оболочку Django, используя python manage.py shell, затем импортируйте класс сериализатора, создайте экземпляр и выполните print представления объекта ...
1
>>> from myapp.serializers import AccountSerializer
2
>>> serializer = AccountSerializer()
3
>>> print(repr(serializer))
4
AccountSerializer():
5
id = IntegerField(label='ID', read_only=True)
6
name = CharField(allow_blank=True, max_length=100, required=False)
7
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
Copied!

Определение полей для включения

Если вы хотите, чтобы подмножество полей по умолчанию использовалось в модельном сериализаторе, то можете сделать используя опции fields или exclude, аналогично тому, как бы вы это сделали с ModelForm. Настоятельно рекомендуется явно указать все поля, которые должны быть сериализованы с использованием атрибута fields. Это уменьшит вероятность непреднамеренного обнародования данных при изменении ваших моделей.
Например:
1
class AccountSerializer(serializers.ModelSerializer):
2
class Meta:
3
model = Account
4
fields = ('id', 'account_name', 'users', 'created')
Copied!
Вы также можете установить специальное значения __all__ в качестве атрибут полей, чтобы указать, что должны использоваться все поля в модели.
Например:
1
class AccountSerializer(serializers.ModelSerializer):
2
class Meta:
3
model = Account
4
fields = '__all__'
Copied!
Вы можете установить атрибут exclude в список полей, которые должны быть исключены из сериализатора.
Например:
1
class AccountSerializer(serializers.ModelSerializer):
2
class Meta:
3
model = Account
4
exclude = ('users',)
Copied!
В приведенном выше примере, если модель Account имеет 3 поляaccount_name, users, и created, это приведет к тому, что резудьтат полейaccount_name и created будет сериализирован.
Имена в атрибутах fields и exclude обычно отображаются в полях модели в классе модели.
В качестве альтернативы имена в параметрах fields могут отображаться в свойствах или методах, которые не принимают аргументов, существующих в классе модели.

Определение вложенной сериализации

По умолчанию ModelSerializer использует первичные ключи для отношений, но вы можете легко создавать вложенные представления, используя параметр depth:
1
class AccountSerializer(serializers.ModelSerializer):
2
class Meta:
3
model = Account
4
fields = ('id', 'account_name', 'users', 'created')
5
depth = 1
Copied!
Параметр depth должен принимать целочисленное значение, указывающее глубину отношений, которые должны быть пройдены, прежде чем возвращаться к flat представлению.
Если вы хотите настроить способ сериализации, вам необходимо определить поле самостоятельно.

Явное указание полей

Вы можете добавить дополнительные поля в ModelSerializer или переопределить поля по умолчанию, объявив поля в классе, аналогично тому как это делается в классе Serializer.
1
class AccountSerializer(serializers.ModelSerializer):
2
url = serializers.CharField(source='get_absolute_url', read_only=True)
3
groups = serializers.PrimaryKeyRelatedField(many=True)
4
5
class Meta:
6
model = Account
Copied!
Дополнительные поля могут соответствовать любому свойству или вызываемому объекту на модели.

Определение read only полей

Вы можете указать несколько полей только для чтения. Вместо того, чтобы явно добавлять каждое поле с атрибутом read_only = True, вы можете использовать опцию Meta shortcut, read_only_fields.
Этот параметр должен быть списком или кортежем, содержащим имя полей и объявляется следующим образом:
1
class AccountSerializer(serializers.ModelSerializer):
2
class Meta:
3
model = Account
4
fields = ('id', 'account_name', 'users', 'created')
5
read_only_fields = ('account_name',)
Copied!
Поля модели с editable = False и поля AutoField будут установлены по умолчанию только для чтения и не должны добавляться в параметр read_only_fields.
Примечание: Есть особый случай, когда поле только для чтения является частью ограничения unique_together на уровне модели. В этом случае поле требуется классу сериализатора для проверки ограничения, но также не должно быть доступно для редактирования пользователем.
Правильный способ справиться с этим - указать поле явно на сериализаторе, предоставляя как аргументы read_only = True, так и default = ...
Одним из примеров этого является read-only отношение к аутентифицированному User, который unique_together с другим идентификатором. В этом случае вы должны объявить поле пользователя следующим образом:
1
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
Copied!

Дополнительные ключевые документы

Существует также shortcut, позволяющий указать произвольные дополнительные аргументы ключевых слов в полях, используя опцию extra_kwargs. Как и в случае с read_only_fields, это означает, что вам не нужно явно объявлять поле в сериализаторе.
Этот параметр является словарем, который соотносит имена полей к словарю аргументов ключевых слов. Например:
1
class CreateUserSerializer(serializers.ModelSerializer):
2
class Meta:
3
model = User
4
fields = ('email', 'username', 'password')
5
extra_kwargs = {'password': {'write_only': True}}
6
7
def create(self, validated_data):
8
user = User(
9
email=validated_data['email'],
10
username=validated_data['username']
11
)
12
user.set_password(validated_data['password'])
13
user.save()
14
return user
Copied!

Реляционные поля

При сериализации экземпляров модели существует несколько разных способов представления отношений. Для ModelSerializer представление по умолчанию заключается в использовании первичных ключей соответствующих экземпляров.
Альтернативные представления включают сериализацию с использованием гиперссылок, сериализацию полных вложенных представлений или сериализацию с пользовательским представлением.

Настройка соотношения полей

Класс ModelSerializer также показывает API, который вы можете переписать для того, чтобы изменить автоматическое определение полей сериализатора при его инициализации.
Обычно, если ModelSerializer не генерирует поля, которые вам нужны по умолчанию, вы должны либо добавить их в класс явно, либо просто использовать обычный класс Serializer. Однако в некоторых случаях вам может понадобиться создать новый базовый класс, который определяет, как поля сериализатора создаются для любой модели.
1
.serializer_field_mapping
Copied!
Соотношение классов модели Django к классам сериализатора REST framework. Вы можете переопределить это соотношение, чтобы изменить стандартные классы сериализатора, которые должны использоваться для каждого класса модели.
1
.serializer_related_field
Copied!
Это свойство должно быть классом поля сериализатора, который по умолчанию используется для реляционных полей.
Для ModelSerializer по умолчанию используется PrimaryKeyRelatedField.
Для HyperlinkedModelSerializer это значение по умолчанию равняется serializers.HyperlinkedRelatedField.
1
serializer_url_field
Copied!
Класс поля сериализатора, который должен использоваться для любого поля url в сериализаторе.
По умолчанию serializers.HyperlinkedIdentityField
1
serializer_choice_field
Copied!
Класс поля сериализатора, который должен использоваться для любых полей выбора в сериализаторе.
По умолчанию serializers.ChoiceField

field_class и field_kwargs API

Следующие методы вызываются для определения аргументов класса и ключевого слова для каждого поля, которое должно автоматически включаться в сериализатор. Каждый из этих методов должен возвращать два кортежа (field_class, field_kwargs).
1
.build_standard_field(self, field_name, model_field)
Copied!
Вызывается для создания поля сериализатора, которое отображается в поле реляционной модели.
Реализация по умолчанию возвращает класс сериализатора на основе атрибута serializer_field_mapping.
1
.build_relational_field(self, field_name, relation_info)
Copied!
Вызывается для создания поля сериализатора, которое соотносится с полем реляционной модели.
Реализация по умолчанию возвращает класс сериализатора на основе атрибута serializer_relational_field.
Аргумент relation_info - это именованный кортеж, который содержит свойства model_field, related_model, to_many и through_model.
1
.build_nested_field(self, field_name, relation_info, nested_depth)
Copied!
Вызывается для создания поля сериализатора, которое соотносится с полем реляционной модели, когда задан параметр depth.
Реализация по умолчанию динамически создает вложенный класс сериализатора на основе ModelSerializer, либо HyperlinkedModelSerializer.
Значение nested_depth будет принимать значение параметра depth минус единица.
Аргумент relation_info - это именованный кортеж, который содержит свойства model_field, related_model, to_many и through_model.
1
.build_property_field(self, field_name, model_class)
Copied!
Вызывается для создания поля сериализатора, которое соотносится с методом свойства или с нулевом аргументом в классе модели.
Реализация по умолчанию возвращает класс ReadOnlyField.
1
.build_url_field(self, field_name, model_class)
Copied!
Вызывается для создания поля сериализатора для собственного поля url.
Реализация по умолчнию возвращает класс HyperlinkedIdentityField.
1
.build_unknown_field(self, field_name, model_class)
Copied!
Вызывается, когда имя поля не соотносится ни с одним из полей моделей или свойством модели. По умолчанию реализация вызывает ошибку, хотя с помощью подклассов можно настраивать это поведение.

HyperlinkedModelSerializer

Класс HyperlinkedModelSerializer похож на класс ModelSerializer, за исключением того, что для представления отношений он использует гиперссылки, а не первичные ключи.
По умолчанию сериализатор будет содержать поле url вместо поля первичного ключа.
Поле url будет представлено с использованием поля сериализатора HyperlinkedIdentityField, и любые отношения в модели будут представлены с использованием поля сериализатора HyperlinkedRelatedField.
Вы можете явно включить первичный ключ, добавив его в опцию полей, например:
1
class AccountSerializer(serializers.HyperlinkedModelSerializer):
2
class Meta:
3
model = Account
4
fields = ('url', 'id', 'account_name', 'users', 'created')
Copied!

Абсолютные и относительные URL

При создании экземпляра HyperlinkedModelSerializer вы должны включить текущий запрос в контекст сериализатора, например:
1
serializer = AccountSerializer(queryset, context={'request': request})
Copied!
Это гарантирует, что гиперссылки могут содержать соответствующее имя хоста, таким образом в конечном представлении используются полностью определенные URL-адреса, например:
1
http://api.example.com/accounts/1/
Copied!
А не относительные:
1
/accounts/1/
Copied!
Если вы все-таки хотите использовать относительные URL, то для этого вам нужно явно передать {'request': None} в контекст сериализатора.

Как определяются гиперссылочные представления

Должен быть способ определения того, какие представления следует использовать в качестве гиперссылки на экземпляры модели.
Предполагается, что по умолчанию гиперссылки будут соответствовать имени представления, которое соответствует стилю '{model_name} -detail', и ищет экземпляр по аргументу pk.
Вы можете переопределить имя поля поля URL и поле поиска с помощью опций view_name и lookup_field в параметрах extra_kwargs:
1
class AccountSerializer(serializers.HyperlinkedModelSerializer):
2
class Meta:
3
model = Account
4
fields = ('account_url', 'account_name', 'users', 'created')
5
extra_kwargs = {
6
'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
7
'users': {'lookup_field': 'username'}
8
}
Copied!
В качестве альтернативы вы можете явно установить поля в сериализаторе. Например:
1
class AccountSerializer(serializers.HyperlinkedModelSerializer):
2
url = serializers.HyperlinkedIdentityField(
3
view_name='accounts',
4
lookup_field='slug'
5
)
6
users = serializers.HyperlinkedRelatedField(
7
view_name='user-detail',
8
lookup_field='username',
9
many=True,
10
read_only=True
11
)
12
13
class Meta:
14
model = Account
15
fields = ('url', 'account_name', 'users', 'created')
Copied!
Совет. Правильное сопоставление гиперссылочных представлений и URL conf порой может быть непромтой задачей. Чтобы узнать к каким именам представлений и lookup полям должны соотноситься отношения можно с помощью print вывести repr экземпляра HyperlinkedModelSerializer.

Изменение имени поля URL

По умолчанию имя поля URL значится как 'url'. Вы можете переопределить это глобально, используя параметр URL_FIELD_NAME.

ListSerializer

Класс ListSerializer обеспечивает поведение для последовательной и одновременной проверки нескольких объектов. Обычно вам не нужно использовать ListSerializer напрямую но вместо этого нужно просто передать аргумент many = True при создании экземпляра сериализатора.
Когда инициализируется сериализатор и передается аргумент many = True, создается экземпляр ListSerializer. Затем класс сериализатора становится потомком родительского ListSerializer
Следующий аргумент также может быть передан в поле ListSerializer или сериализатор, которому передается many = True:
1
allow_empty
Copied!
По умолчанию True, но может быть равным False, если вы хотите запретить пустые списки в качестве допустимого ввода.

Настройка поведения ListSerializer

Есть ряд случаев, когда вам может потребоваться настроить поведение ListSerializer. Например:
  • Вы хотите обеспечить определенную проверку списков, например проверку того, что один элемент не конфликтует с другим элементом в списке.
  • Вы хотите настроить процесс создания или обновления нескольких объектов.
Для этих случаев вы можете изменить класс, который используется, при передаче аргумента many=True , используя опцию list_serializer_class в классе Meta сериализатора.
Например:
1
class CustomListSerializer(serializers.ListSerializer):
2
...
3
4
class CustomSerializer(serializers.Serializer):
5
...
6
class Meta:
7
list_serializer_class = CustomListSerializer
Copied!

Настройка нескольких create

По умолчанию несколько объектов можно создать просто вызвав .create() для каждого элемента в списке. Если вы хотите настроить это поведение, вам нужно настроить метод .create() в классе ListSerializer, который используется, когда передается аргумент many=True.
Например:
1
class BookListSerializer(serializers.ListSerializer):
2
def create(self, validated_data):
3
books = [Book(**item) for item in validated_data]
4
return Book.objects.bulk_create(books)
5
6
class BookSerializer(serializers.Serializer):
7
...
8
class Meta:
9
list_serializer_class = BookListSerializer
Copied!

Настройка нескольких update

По умолчанию класс ListSerializer не поддерживает несколько update. Это связано с тем, что поведение, которое следует ожидать при вставках и удалениях, неоднозначно.
Чтобы поддерживать нескольких обновлений, вам нужно сделать это явно. При написании кода множественного обновления обязательно учитывайте следующее:
  • Как вы определяете, какой экземпляр должен быть обновлен для каждого элемента в списке данных?
  • Как следует обрабатывать вставки? Они недействительны или создают новые объекты?
  • Как следует обрабатывать удаление? Оно подразумевают удаление объекта или удаление отношений? Следует ли его игнорировать, или считать недействительным?
  • Как следует обрабатывать сортировку? Изменяет ли положение двух элементов любое изменение состояния или игнорируется?
Вам нужно будет добавить явное поле id в сериализатор экземпляра. По умолчанию неявно сгенерированное поле id помечено как read_only. Это приводит к его удалению при обновлении. Как только вы объявите его явно, он будет доступен в списке методов обновления сериализатора.
Пример того, как вы можете реализовать несколько обновлений:
1
class BookListSerializer(serializers.ListSerializer):
2
def update(self, instance, validated_data):
3
# Maps for id->instance and id->data item.
4
book_mapping = {book.id: book for book in instance}
5
data_mapping = {item['id']: item for item in validated_data}
6
7
# Perform creations and updates.
8
ret = []
9
for book_id, data in data_mapping.items():
10
book = book_mapping.get(book_id, None)
11
if book is None:
12
ret.append(self.child.create(data))
13
else:
14
ret.append(self.child.update(book, data))
15
16
# Perform deletions.
17
for book_id, book in book_mapping.items():
18
if book_id not in data_mapping:
19
book.delete()
20
21
return ret
22
23
class BookSerializer(serializers.Serializer):
24
# We need to identify elements in the list using their primary key,
25
# so use a writable field here, rather than the default which would be read-only.
26
id = serializers.IntegerField()
27
...
28
29
class Meta:
30
list_serializer_class = BookListSerializer
Copied!
Возможно, в следующую версию фреймворка будет включен пакет сторонних разработчиков, который обеспечивал бы некоторую автоматическую поддержку для нескольких операций обновления, аналогичную поведению allow_add_remove, которое присутствовало в REST framework 2.

Настройка инициализации ListSerializer

Когда создается экземпляр сериализатора с many=True , нам нужно определить, какие аргументы и аргументы ключевых слов должны быть переданы методу .__init__() для дочернего класса Serializer и для родительского класса ListSerializer.
Реализация по умолчанию - передать все аргументы обоим классам, за исключением валидаторов, и любые пользовательские аргументы ключевых слов, оба из которых предназначены для дочернего класса сериализатора.
Иногда вам может потребоваться явно указать, каким образом следует создать экземпляр дочернего и родительского классов при передаче аргументов many=True. Вы можете сделать это, используя метод класса many_init.
1
@classmethod
2
def many_init(cls, *args, **kwargs):
3
# Instantiate the child serializer.
4
kwargs['child'] = cls()
5
# Instantiate the parent list serializer.
6
return CustomListSerializer(*args, **kwargs)
Copied!

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.
Read-only BaseSerializer классы
Чтобы реализовать read-only сериализатор с использованием класса BaseSerializer, нам просто нужно переопределить метод .to_representation(). Давайте рассмотрим пример с использованием простой модели Django:
1
class HighScore(models.Model):
2
created = models.DateTimeField(auto_now_add=True)
3
player_name = models.CharField(max_length=10)
4
score = models.IntegerField()
Copied!
Нет ничего сложного в том, чтобы создать сериализатор только для чтения для преобразования экземпляров HighScore в примитивные типы данных.
1
class HighScoreSerializer(serializers.BaseSerializer):
2
def to_representation(self, obj):
3
return {
4
'score': obj.score,
5
'player_name': obj.player_name
6
}
Copied!
Теперь мы можем использовать этот класс для сериализации отдельных экземпляров HighScore:
1
@api_view(['GET'])
2
def high_score(request, pk):
3
instance = HighScore.objects.get(pk=pk)
4
serializer = HighScoreSerializer(instance)
5
return Response(serializer.data)
Copied!
Или использовать его для сериализации нескольких экземпляров:
1
@api_view(['GET'])
2
def all_high_scores(request):
3
queryset = HighScore.objects.order_by('-score')
4
serializer = HighScoreSerializer(queryset, many=True)
5
return Response(serializer.data)
Copied!

Read-write BaseSerializer классы

Чтобы создать сериализатор чтения и записи, сначала необходимо реализовать метод .to_internal_value(). Этот метод возвращает проверенные значения, которые будут использоваться для создания экземпляра объекта, и может вызвать ValidationError, если предоставленные данные находятся в неправильном формате.
После того, как вы внедрили .to_internal_value(), базовый API проверки будет доступен в сериализаторе, и вы сможете использовать .is_valid(), .validated_data и .errors.
Если помимо этого вам требуется поддержка .save (), вам также необходимо реализовать один или оба метода .create() и .update().
Вот полный пример нашего предыдущего HighScoreSerializer, который был обновлен для поддержки операций чтения и записи.
1
class HighScoreSerializer(serializers.BaseSerializer):
2
def to_internal_value(self, data):
3
score = data.get('score')
4
player_name = data.get('player_name')
5
6
# Осуществляется проверка данных.
7
if not score:
8
raise ValidationError({
9
'score': 'This field is required.'
10
})
11
if not player_name:
12
raise ValidationError({
13
'player_name': 'This field is required.'
14
})
15
if len(player_name) > 10:
16
raise ValidationError({
17
'player_name': 'May not be more than 10 characters.'
18
})
19
20
# Возвращает проверенные значения. Они будут доступны
21
# в качестве свойства `.validated_data` .
22
return {
23
'score': int(score),
24
'player_name': player_name
25
}
26
27
def to_representation(self, obj):
28
return {
29
'score': obj.score,
30
'player_name': obj.player_name
31
}
32
33
def create(self, validated_data):
34
return HighScore.objects.create(**validated_data)
Copied!

Создание новых базовых классов

Класс BaseSerializer также полезен, если вы хотите внедрять новые общие классы сериализатора для работы с определенными стилями сериализации или для интеграции с альтернативными обработчиками хранилищ.
Следующий класс является примером универсального сериализатора, который может преобразовывать произвольные объекты в примитивные представления.
1
class ObjectSerializer(serializers.BaseSerializer):
2
"""
3
A read-only serializer that coerces arbitrary complex objects
4
into primitive representations.
5
"""
6
def to_representation(self, obj):
7
for attribute_name in dir(obj):
8
attribute = getattr(obj, attribute_name)
9
if attribute_name('_'):
10
# Ignore private attributes.
11
pass
12
elif hasattr(attribute, '__call__'):
13
# Ignore methods and other callables.
14
pass
15
elif isinstance(attribute, (str, int, bool, float, type(None))):
16
# Primitive types can be passed through unmodified.
17
output[attribute_name] = attribute
18
elif isinstance(attribute, list):
19
# Recursively deal with items in lists.
20
output[attribute_name] = [
21
self.to_representation(item) for item in attribute
22
]
23
elif isinstance(attribute, dict):
24
# Recursively deal with items in dictionaries.
25
output[attribute_name] = {
26
str(key): self.to_representation(value)
27
for key, value in attribute.items()
28
}
29
else:
30
# Force anything else to its string representation.
31
output[attribute_name] = str(attribute)
Copied!

Продвинутое использование сериализаторов

Переопределение процесса сериализации и десериализации

Если вам необходимо изменить поведение сериализации или десериализации класса сериализатора, вы можете сделать это, переопределив методы .to_representation() или .to_internal_value().
Некоторые причины, почему это может быть полезно ...
  • Добавление нового поведения для новых классов базового класса.
  • Несущественное изменение поведения существующего класса.
  • Улучшение производительности сериализации для часто используемой конечной точки API, которая возвращает большое количество данных.
  • Подписи для этих методов заключаются в следующем:
У этих методов следующие подписи:
1
.to_representation(self, obj)
Copied!
Принимает экземпляр объекта, который требует сериализации, и возвращает примитивное представление. Обычно это означает возврат структуры встроенных типов данных Python. Точные типы, которые можно обрабатывать, будут зависеть от классов рендеринга, которые вы настроили для вашего API.
1
.to_internal_value(self, data)
Copied!
Принимает невалидные входящие данные и возвращает проверенные данные, которые будут доступны как serializer.validated_data. Возвращаемое значение также будет передано методам .create() или .update(), если в классе сериализатора вызывается .save().
Если какая-либо проверка не выполняется, тогда метод должен вызвать