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


источник:

  • serializers.py


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

Расширение полезности сериализаторов - это то, чем мы хотели бы заняться. Однако это не тривиальная проблема, и она потребует серьезной работы над дизайном.

— Russell Keith-Magee, Django users group

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

Сериализаторы в DRF работают очень похоже на классы Django Form и ModelForm. Мы предоставляем класс 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 можно настроить с помощью параметра DRF NON_FIELD_ERRORS_KEY.

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

Возбуждение исключения при недопустимых данных

Метод .is_valid() принимает необязательный флаг raise_exception, который заставит его поднять исключение serializers.ValidationError, если есть ошибки валидации.

Эти исключения автоматически обрабатываются обработчиком исключений по умолчанию, который предоставляет DRF, и по умолчанию будут возвращать ответы HTTP 400 Bad Request.

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

Валидация на уровне поля

Вы можете задать пользовательскую валидацию на уровне полей, добавив методы .validate_<имя_поля> в подкласс Serializer. Они аналогичны методам .clean_<имя_поля> в формах Django.

Эти методы принимают единственный аргумент, который является значением поля, требующего проверки.

Ваши методы validate_<имя_поля> должны возвращать проверенное значение или вызывать 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

Примечание: Если ваше <имя_поля> объявлено в вашем сериализаторе с параметром 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']
            )
        ]

Для получения дополнительной информации см. документацию validators.

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

При передаче исходного объекта или набора запросов экземпляру сериализатора, объект будет доступен как .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

Поскольку поведение вложенных созданий и обновлений может быть неоднозначным и может требовать сложных зависимостей между связанными моделями, DRF 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. Обратные отношения не включаются по умолчанию, если они не включены явно, как указано в документации serializer relations.

Проверка 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())

Пожалуйста, ознакомьтесь с документацией Validators Documentation для получения подробной информации о классах 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 заключается в использовании первичных ключей связанных экземпляров.

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

Более подробную информацию можно найти в документации serializer relations.

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

Класс ModelSerializer также предоставляет API, который вы можете переопределить, чтобы изменить способ автоматического определения полей сериализатора при инстанцировании сериализатора.

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

serializer_field_mapping.

Отображение полей модели Django на поля сериализатора DRF. Вы можете переопределить это отображение, чтобы изменить поля сериализатора по умолчанию, которые должны использоваться для каждого поля модели.

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

Для 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} в контексте сериализатора.

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

Необходимо определить, какие представления следует использовать для гиперссылок на экземпляры модели.

По умолчанию ожидается, что гиперссылки будут соответствовать имени представления, которое соответствует стилю '{имя_модели}-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 conf иногда может быть немного сложным. Печать 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-формы в Web-интерфейсе 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, вы можете расширять и повторно использовать сериализаторы с помощью наследования. Это позволяет вам объявить общий набор полей или методов в родительском классе, который затем может быть использован в нескольких сериализаторах. Например,

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, что и сериализаторы DRF, и может быть использован в качестве замены в некоторых случаях.

Serpy

Пакет serpy - это альтернативная реализация сериализаторов, созданная для скорости. Serpy сериализует сложные типы данных в простые нативные типы. Родные типы могут быть легко преобразованы в JSON или любой другой необходимый формат.

MongoengineModelSerializer

Пакет django-rest-framework-mongoengine предоставляет класс сериализатора MongoEngineModelSerializer, который поддерживает использование MongoDB в качестве уровня хранения данных для DRF.

GeoFeatureModelSerializer

Пакет django-rest-framework-gis предоставляет класс сериализатора GeoFeatureModelSerializer, который поддерживает GeoJSON как для операций чтения, так и для записи.

HStoreSerializer

Пакет django-rest-framework-hstore предоставляет HStoreSerializer для поддержки поля модели django-hstore DictionaryField и его функции chema-mode.

Dynamic REST

Пакет dynamic-rest расширяет интерфейсы ModelSerializer и ModelViewSet, добавляя параметры запроса API для фильтрации, сортировки, включения/исключения всех полей и отношений, определенных вашими сериализаторами.

Dynamic Fields Mixin

Пакет drf-dynamic-fields предоставляет миксин для динамического ограничения полей для сериализатора подмножеством, заданным параметром URL.

DRF FlexFields

Пакет drf-flex-fields расширяет ModelSerializer и ModelViewSet для обеспечения широко используемой функциональности для динамической установки полей и расширения примитивных полей во вложенные модели, как из параметров URL, так и из определений класса вашего сериализатора.

Serializer Extensions

Пакет django-rest-framework-serializer-extensions предоставляет набор инструментов для DRY up ваших сериализаторов, позволяя определять поля на основе каждого представления/запроса. Поля могут быть внесены в белый или черный список, а дочерние сериализаторы могут быть расширены по желанию.

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. Он также содержит некоторые вспомогательные функции. Это поможет вам зашифровать ваши данные.

Last updated