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

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

‒ Russell Keith-Magee, Django users group

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

Сериализаторы в REST framework работают очень похоже на классы Form и ModelForm Django. Мы предоставляем класс Serializer, который дает вам мощный, общий способ управления выводом ваших ответов, а также класс ModelSerializer, который предоставляет полезный ярлык для создания сериализаторов, работающих с экземплярами моделей и наборами запросов.

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

Давайте начнем с создания простого объекта, который мы можем использовать для примера:

from datetime import datetime

class Comment:
    def __init__(self, email, content, created=None):
        self.email = email
        self.content = content
        self.created = created or datetime.now()

comment = Comment(email='leila@example.com', content='foo bar')

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

Объявление сериализатора очень похоже на объявление формы:

from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

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

Теперь мы можем использовать CommentSerializer для сериализации комментария или списка комментариев. Опять же, использование класса Serializer очень похоже на использование класса Form.

serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}

На этом этапе мы перевели экземпляр модели в собственные типы данных Python. Для завершения процесса сериализации мы выводим данные в json.

from rest_framework.renderers import JSONRenderer

json = JSONRenderer().render(serializer.data)
json
# b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'

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

Десериализация аналогична. Сначала мы разбираем поток на собственные типы данных Python…

import io
from rest_framework.parsers import JSONParser

stream = io.BytesIO(json)
data = JSONParser().parse(stream)

затем мы восстанавливаем эти родные типы данных в словарь проверенных данных.

serializer = CommentSerializer(data=data) serializer.is_valid() # True serializer.validated_data # {„content“: „foo bar“, „email“: „leila@example.com“, „created“: datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}

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

Если мы хотим иметь возможность возвращать полные экземпляры объектов на основе проверенных данных, нам необходимо реализовать один или оба метода .create() и .update(). Например:

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

    def create(self, validated_data):
        return Comment(**validated_data)

    def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.content = validated_data.get('content', instance.content)
        instance.created = validated_data.get('created', instance.created)
        return instance

Если ваши экземпляры объектов соответствуют моделям Django, вы также захотите убедиться, что эти методы сохраняют объект в базе данных. Например, если Comment является моделью Django, методы могут выглядеть следующим образом:

def create(self, validated_data):
    return Comment.objects.create(**validated_data)

def update(self, instance, validated_data):
    instance.email = validated_data.get('email', instance.email)
    instance.content = validated_data.get('content', instance.content)
    instance.created = validated_data.get('created', instance.created)
    instance.save()
    return instance

Теперь при десериализации данных мы можем вызвать .save(), чтобы вернуть экземпляр объекта, основанный на проверенных данных.

comment = serializer.save()

Вызов .save() приведет либо к созданию нового экземпляра, либо к обновлению существующего, в зависимости от того, был ли передан существующий экземпляр при инстанцировании класса сериализатора:

# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

Методы .create() и .update() являются необязательными. Вы можете реализовать либо ни один из них, либо один, либо оба, в зависимости от того, как будет использоваться ваш класс сериализатора.

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

Иногда вы хотите, чтобы код представления мог вводить дополнительные данные в момент сохранения экземпляра. Эти дополнительные данные могут включать информацию о текущем пользователе, текущем времени или что-нибудь еще, что не является частью данных запроса.

Вы можете сделать это, включив дополнительные аргументы ключевого слова при вызове .save(). Например:

serializer.save(owner=request.user)

Любые дополнительные аргументы ключевых слов будут включены в аргумент validated_data при вызове .create() или .update().

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

В некоторых случаях имена методов .create() и .update() могут не иметь смысла. Например, в контактной форме мы можем не создавать новые экземпляры, а отправлять электронное или другое сообщение.

В этих случаях вы можете вместо этого выбрать прямое переопределение .save(), как более читаемое и осмысленное.

Например:

class ContactForm(serializers.Serializer):
    email = serializers.EmailField()
    message = serializers.CharField()

    def save(self):
        email = self.validated_data['email']
        message = self.validated_data['message']
        send_email(from=email, message=message)

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

Валидация

При десериализации данных всегда нужно вызывать is_valid() перед попыткой получить доступ к проверенным данным или сохранить экземпляр объекта. Если возникнут ошибки валидации, свойство .errors будет содержать словарь, представляющий сообщения об ошибках. Например:

serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}

Каждый ключ в словаре будет именем поля, а значения будут списками строк любых сообщений об ошибках, соответствующих этому полю. Также может присутствовать ключ non_field_errors, который будет перечислять любые общие ошибки валидации. Имя ключа non_field_errors может быть настроено с помощью параметра NON_FIELD_ERRORS_KEY REST framework.

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

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

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

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

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

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

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

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

Ваши методы validate_<field_name> должны возвращать подтвержденное значение или вызывать ошибку serializers.ValidationError. Например:

from rest_framework import serializers

class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()

    def validate_title(self, value):
        """
        Check that the blog post is about Django.
        """
        if 'django' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Django")
        return value

Примечание: Если ваше <field_name> объявлено в вашем сериализаторе с параметром required=False, то этот шаг проверки не будет выполняться, если поле не включено.


Валидация на уровне объекта

Чтобы выполнить любую другую проверку, требующую доступа к нескольким полям, добавьте метод под названием .validate() в ваш подкласс Serializer. Этот метод принимает единственный аргумент, который является словарем значений полей. При необходимости он должен вызывать сигнал serializers.ValidationError или просто возвращать проверенные значения. Например:

from rest_framework import serializers

class EventSerializer(serializers.Serializer):
    description = serializers.CharField(max_length=100)
    start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, data):
        """
        Check that start is before finish.
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

Валидаторы

Отдельные поля сериализатора могут включать валидаторы, например, путем объявления их в экземпляре поля:

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
    score = 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 documentation.

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

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

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

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

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

# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)

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

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

Класс Serializer сам является типом Field , и может быть использован для представления отношений, где один тип объекта вложен в другой.

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField(max_length=100)

class CommentSerializer(serializers.Serializer):
    user = UserSerializer()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

Если вложенное представление может опционально принимать значение None, вы должны передать флаг required=False в сериализатор вложенного представления.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)  # May be an anonymous user.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

Аналогично, если вложенное представление должно быть списком элементов, необходимо передать флаг many=True в сериализатор вложенного представления.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    edits = EditItemSerializer(many=True)  # A nested list of 'edit' items.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

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

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

serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}

Аналогично, свойство .validated_data будет включать вложенные структуры данных.

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

Если вы поддерживаете вложенные представления с возможностью записи, вам нужно написать методы .create() или .update(), которые обрабатывают сохранение нескольких объектов.

В следующем примере показано, как можно создать пользователя с вложенным объектом профиля.

class UserSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer()

    class Meta:
        model = User
        fields = ['username', 'email', 'profile']

    def create(self, validated_data):
        profile_data = validated_data.pop('profile')
        user = User.objects.create(**validated_data)
        Profile.objects.create(user=user, **profile_data)
        return user

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

Для обновлений вам необходимо тщательно продумать, как обрабатывать обновления отношений. Например, если данные для отношения None , или не предоставлены, что из нижеперечисленного должно произойти?

  • Установите отношение NULL в базе данных.

  • Удалить связанный экземпляр.

  • Игнорируйте данные и оставьте экземпляр как есть.

  • Вызвать ошибку валидации.

Вот пример для метода .update() на нашем предыдущем классе UserSerializer.

def update(self, instance, validated_data):
    profile_data = validated_data.pop('profile')
    # Unless the application properly enforces that this field is
    # always set, the following could raise a `DoesNotExist`, which
    # would need to be handled.
    profile = instance.profile

    instance.username = validated_data.get('username', instance.username)
    instance.email = validated_data.get('email', instance.email)
    instance.save()

    profile.is_premium_member = profile_data.get(
        'is_premium_member',
        profile.is_premium_member
    )
    profile.has_support_contract = profile_data.get(
        'has_support_contract',
        profile.has_support_contract
     )
    profile.save()

    return instance

Поскольку поведение вложенных созданий и обновлений может быть неоднозначным и может требовать сложных зависимостей между связанными моделями, REST framework 3 требует, чтобы вы всегда писали эти методы явно. Методы по умолчанию ModelSerializer .create() и .update() не включают поддержку записываемых вложенных представлений.

Однако существуют сторонние пакеты, такие как DRF Writable Nested, которые поддерживают автоматическую запись вложенных представлений.

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

Класс 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.

Классы сериализаторов генерируют полезные строки представления, которые позволяют вам полностью просмотреть состояние их полей. Это особенно полезно при работе с 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

Дополнительные поля могут соответствовать любому свойству или вызываемому объекту модели.

Указание полей, доступных только для чтения

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

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

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

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

Для ModelSerializer это значение по умолчанию равно PrimaryKeyRelatedField.

Для HyperlinkedModelSerializer это значение по умолчанию равно serializers.HyperlinkedRelatedField.

Класс поля сериализатора, который должен использоваться для любого поля url на сериализаторе.

По умолчанию serializers.HyperlinkedIdentityField

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

По умолчанию serializers.ChoiceField

Следующие методы вызываются для определения класса и аргументов ключевых слов для каждого поля, которое должно быть автоматически включено в сериализатор. Каждый из этих методов должен возвращать кортеж из двух значений (field_class, field_kwargs).

Вызывается для генерации поля сериализатора, которое сопоставляется со стандартным полем модели.

Реализация по умолчанию возвращает класс сериализатора на основе атрибута serializer_field_mapping.

Вызывается для генерации поля сериализатора, которое сопоставляется с полем реляционной модели.

Реализация по умолчанию возвращает класс сериализатора на основе атрибута serializer_related_field.

Аргумент relation_info представляет собой именованный кортеж, содержащий model_field , related_model , to_many и has_through_model свойства.

Вызывается для генерации поля сериализатора, которое сопоставляется с полем реляционной модели, когда установлена опция depth.

Реализация по умолчанию динамически создает вложенный класс сериализатора на основе ModelSerializer или HyperlinkedModelSerializer.

Значение nested_depth будет равно значению опции depth, минус один.

Аргумент relation_info представляет собой именованный кортеж, содержащий model_field , related_model , to_many и has_through_model свойства.

Вызывается для генерации поля сериализатора, которое сопоставляется со свойством или методом с нулевым аргументом класса модели.

Реализация по умолчанию возвращает класс ReadOnlyField.

Вызывается для генерации поля сериализатора для собственного поля сериализатора url. Реализация по умолчанию возвращает класс HyperlinkedIdentityField.

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


HyperlinkedModelSerializer

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

По умолчанию сериализатор будет включать поле url вместо поля первичного ключа.

Поле url будет представлено с помощью поля сериализатора HyperlinkedIdentityField, а любые отношения в модели будут представлены с помощью поля сериализатора HyperlinkedRelatedField.

Вы можете явно включить первичный ключ, добавив его, например, к опции fields:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ['url', 'id', 'account_name', 'users', 'created']

Абсолютные и относительные URL-адреса

При инстанцировании HyperlinkedModelSerializer вы должны включить текущий request в контекст сериализатора, например:

serializer = AccountSerializer(queryset, context={'request': request})

Это гарантирует, что гиперссылки могут включать соответствующее имя хоста, так что результирующее представление использует полные URL-адреса, такие как:

http://api.example.com/accounts/1/

Вместо относительных URL-адресов, таких как:

/accounts/1/

Если вы хотите использовать относительные URL, вы должны явно передать {'request': None} в контексте сериализатора.

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

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

По умолчанию предполагается, что гиперссылки соответствуют имени представления, которое соответствует стилю '{model_name}-detail' , и ищет экземпляр по аргументу ключевого слова pk.

Вы можете переопределить имя представления поля URL и поле поиска, используя одну или обе опции view_name и lookup_field в параметре extra_kwargs, как показано ниже:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ['account_url', 'account_name', 'users', 'created']
        extra_kwargs = {
            'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
            'users': {'lookup_field': 'username'}
        }

В качестве альтернативы вы можете явно задать поля в сериализаторе. Например:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name='accounts',
        lookup_field='slug'
    )
    users = serializers.HyperlinkedRelatedField(
        view_name='user-detail',
        lookup_field='username',
        many=True,
        read_only=True
    )

    class Meta:
        model = Account
        fields = ['url', 'account_name', 'users', 'created']

Совет: Правильное сопоставление гиперссылочных представлений и вашего URL conf иногда может быть немного сложным. Печать repr экземпляра HyperlinkedModelSerializer - особенно полезный способ проверить, какие именно имена представлений и полей поиска должны сопоставлять отношения.


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

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


ListSerializer

Класс ListSerializer обеспечивает поведение для сериализации и валидации нескольких объектов одновременно. Обычно типично не требуется использовать ListSerializer напрямую, вместо этого следует просто передать many=True при инстанцировании сериализатора.

Когда сериализатор инстанцируется и передается many=True, будет создан экземпляр ListSerializer. Затем класс сериализатора становится дочерним для родительского ListSerializer.

Следующий аргумент также может быть передан полю ListSerializer или сериализатору, которому передается many=True :

По умолчанию это значение True, но может быть установлено в False, если вы хотите запретить пустые списки в качестве допустимого ввода.

Существует *несколько случаев, когда вы можете захотеть настроить поведение 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

Возможно, в релиз 3.1 будет включен пакет сторонних разработчиков, обеспечивающий автоматическую поддержку нескольких операций обновления, подобно поведению allow_add_remove, которое присутствовало в REST framework 2.

Когда инстанцируется сериализатор с many=True, нам нужно определить, какие аргументы и ключевые слова должны быть переданы в метод .__init__() как для дочернего Serializer класса, так и для родительского ListSerializer класса.

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

Иногда вам может понадобиться явно указать, как дочерний и родительский классы должны быть инстанцированы при передаче many=True. Вы можете сделать это с помощью метода класса many_init.

@classmethod
def many_init(cls, *args, **kwargs):
    # Instantiate the child serializer.
    kwargs['child'] = cls()
    # Instantiate the parent list serializer.
    return CustomListSerializer(*args, **kwargs)

BaseSerializer

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

Этот класс реализует тот же базовый API, что и класс Serializer:

  • .data - Возвращает исходящее примитивное представление.

  • .is_valid() - Десериализует и проверяет входящие данные.

  • .validated_data - Возвращает проверенные входящие данные.

  • .errors - Возвращает любые ошибки во время валидации.

  • .save() - Переносит проверенные данные в экземпляр объекта.

Есть четыре метода, которые могут быть переопределены, в зависимости от того, какую функциональность вы хотите, чтобы поддерживал класс сериализатора:

  • .to_representation() - Переопределите это для поддержки сериализации, для операций чтения.

  • .to_internal_value() - Переопределите это для поддержки десериализации, для операций записи.

  • .create() и .update() - переопределите один из них или оба для поддержки сохранения экземпляров.

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

Единственное различие, которое вы заметите при этом, - классы BaseSerializer не будут генерировать HTML-формы в просматриваемом API. Это происходит потому, что данные, которые они возвращают, не включают всю информацию о поле, которая позволила бы каждому полю быть преобразованным в подходящий HTML-ввод.

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

Для создания сериализатора чтения-записи нам сначала нужно реализовать метод .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()`:doc:` <#object-level-validation>`.

Аргумент data, передаваемый в этот метод, обычно будет значением request.data , поэтому тип данных, который он предоставляет, будет зависеть от классов парсера, которые вы настроили для своего API.

Наследование сериализатора

Подобно формам Django, вы можете расширять и повторно использовать сериализаторы с помощью наследования. Это позволяет вам объявить общий набор полей или методов в родительском классе, который затем может быть использован в нескольких сериализаторах. Например,

class MyBaseSerializer(Serializer):
    my_field = serializers.CharField()

    def validate_my_field(self, value):
        ...

class MySerializer(MyBaseSerializer):
    ...

Как и классы Django Model и ModelForm, внутренний класс 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
    
    
    However, you can only use this technique to opt out from a field defined declaratively by a parent class; it won’t prevent the ``ModelSerializer`` from generating a default field. To opt-out from default fields, see :doc:`Specifying which fields to include <#specifying-which-fields-to-include>`.
    

Динамическое изменение полей

После инициализации сериализатора к словарю полей, установленных на сериализаторе, можно получить доступ с помощью атрибута .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(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

        if fields is not None:
            # Drop any fields that are not specified in the `fields` argument.
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)

Это позволит вам сделать следующее:

>>> class UserSerializer(DynamicFieldsModelSerializer):
>>>     class Meta:
>>>         model = User
>>>         fields = ['id', 'username', 'email']
>>>
>>> print(UserSerializer(user))
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
>>>
>>> print(UserSerializer(user, fields=('id', 'email')))
{'id': 2, 'email': 'jon@example.com'}

Настройка полей по умолчанию

REST framework 2 предоставил API, позволяющий разработчикам переопределять, как класс ModelSerializer будет автоматически генерировать набор полей по умолчанию.

Этот API включал .get_field() , .get_pk_field() и другие методы.

Поскольку сериализаторы были кардинально переработаны в версии 3.0, этот API больше не существует. Вы все еще можете изменять создаваемые поля, но вам придется обратиться к исходному коду, и имейте в виду, что если изменения, которые вы делаете, направлены против частных частей API, то они могут быть изменены.


Пакеты сторонних производителей

Также доступны следующие пакеты сторонних производителей.

Django REST marshmallow

Пакет django-rest-marshmallow предоставляет альтернативную реализацию сериализаторов, используя библиотеку python marshmallow. Он предоставляет тот же API, что и сериализаторы фреймворка REST, и может быть использован в качестве замены в некоторых случаях.

Серпи

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

MongoengineModelSerializer

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

GeoFeatureModelSerializer

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

HStoreSerializer

Пакет django-rest-framework-hstore предоставляет HStoreSerializer для поддержки django-hstore DictionaryField модельного поля и его schema-mode возможности.

Динамический REST

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

Миксин динамических полей

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

DRF FlexFields

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

Расширения сериализатора

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

Формы HTML JSON

Пакет html-json-forms предоставляет алгоритм и сериализатор для обработки представлений <form> в соответствии с (неактивным) HTML JSON Form specification. Сериализатор облегчает обработку произвольно вложенных структур JSON в HTML. Например, <input name="items[0][id]" value="5"> будет интерпретировано как {"items": [{"id": "5"}]}.

DRF-Base64

DRF-Base64 предоставляет набор сериализаторов полей и моделей, который обрабатывает загрузку файлов в base64-кодировке.

QueryFields

djangorestframework-queryfields позволяет клиентам API указать, какие поля будут отправлены в ответе с помощью параметров запроса включения/исключения.

DRF Записываемый вложенный

Пакет drf-writable-nested предоставляет записываемый сериализатор вложенных моделей, который позволяет создавать/обновлять модели с вложенными связанными данными.

DRF Шифровать содержимое

Пакет drf-encrypt-content помогает вам шифровать данные, сериализованные с помощью ModelSerializer. Он также содержит некоторые вспомогательные функции. Которые помогут вам зашифровать ваши данные.

Вернуться на верх