REST-фреймворк Django 3.0

Релиз 3.0 фреймворка Django REST является результатом почти четырех лет итераций и доработок. В нем всесторонне решены некоторые из ранее остававшихся проблем проектирования в сериализаторах, полях и общих представлениях.

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

Разница в качестве API и реализации фреймворка REST должна значительно облегчить написание, поддержку и отладку вашего приложения.

3.0 - это первый из трех релизов, которые были профинансированы нашим недавним проектом Kickstarter campaign.

Как всегда, огромное спасибо нашим многочисленным wonderful sponsors. Если вы ищете работу на Django и хотите работать с умными людьми, настроенными на сообщество, вам стоит заглянуть в этот список и посмотреть, кто нанимается.


Новые возможности

Примечательные особенности нового выпуска включают:

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

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

  • Новый класс BaseSerializer, облегчающий написание сериализаторов для альтернативных бэкендов хранения или полную настройку логики сериализации и валидации.

  • Более чистый API полей, включая новые классы, такие как ListField и MultipleChoiceField.

  • Super simple default implementations для общих представлений.

  • Поддержка переопределения того, как ошибки валидации обрабатываются вашим API.

  • API метаданных, который позволяет вам настраивать, как OPTIONS запросы обрабатываются вашим API.

  • Более компактный вывод JSON с включенным по умолчанию кодированием в стиле unicode.

  • Рендеринг HTML-формы на основе шаблонов для сериализаторов. Это будет доработано как общедоступный API в предстоящем выпуске 3.1.

В релизах 3.1 и 3.2 по-прежнему планируется значительная новая функциональность. Эти релизы будут соответствовать двум Kickstarter stretch goals - «Улучшение функций» и «Интерфейс администратора». Дальнейшие релизы 3.x будут представлять собой простые обновления, без того уровня фундаментальных изменений API, который необходим для релиза 3.0.


REST-фреймворк: Под капотом.

Этот доклад с мероприятия Django: Under the Hood в Амстердаме, ноябрь 2014 года, дает хороший фоновый контекст о проектных решениях, лежащих в основе 3.0.


Below is an in-depth guide to the API changes and migration notes for 3.0.

Объекты запроса

Свойства .data и .query_params.

Использование request.DATA и request.FILES в настоящее время ожидает отмены в пользу одного атрибута request.data, который содержит все разобранные данные.

Наличие отдельных атрибутов целесообразно для веб-приложений, которые анализируют только url-кодированные или многокомпонентные запросы, но не имеет смысла для анализа запросов общего назначения, который поддерживает REST framework.

Теперь вы можете передавать все данные запроса классу сериализатора в одном аргументе:

# Do this...
ExampleSerializer(data=request.data)

Вместо того чтобы передавать аргумент files отдельно:

# Don't do this...
ExampleSerializer(data=request.DATA, files=request.FILES)

Использование request.QUERY_PARAMS в настоящее время ожидает депривации в пользу request.query_params со строчными буквами.


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

Одноэтапное создание объекта.

Ранее сериализаторы использовали двухэтапное создание объекта следующим образом:

  1. При проверке данных создается экземпляр объекта. Этот экземпляр будет доступен как serializer.object.

  2. Вызов serializer.save() сохранит экземпляр объекта в базе данных.

Этот стиль соответствует тому, как работает класс ModelForm в Django, но является проблематичным по ряду причин:

  • Некоторые данные, такие как отношения «многие ко многим», не могут быть добавлены к экземпляру объекта до тех пор, пока он не будет сохранен. Этот тип данных необходимо было спрятать в каком-то недокументированном состоянии экземпляра объекта или сохранить как состояние экземпляра сериализатора, чтобы его можно было использовать при вызове .save().

  • Инстанцирование экземпляров модели напрямую означает, что вы не можете использовать классы менеджеров модели для создания экземпляров, например, ExampleModel.objects.create(...). Классы менеджеров - это отличный уровень, на котором можно реализовать бизнес-логику и ограничения данных на уровне приложения.

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

Теперь мы используем одношаговое создание объекта, например, так:

  1. Валидация данных делает очищенные данные доступными как serializer.validated_data.

  2. Вызов serializer.save() затем сохраняет и возвращает новый экземпляр объекта.

Ниже подробно описаны изменения, внесенные в API.

Методы .create() и .update().

Метод .restore_object() теперь удален, и вместо него мы имеем два отдельных метода .create() и .update(). Эти методы работают немного иначе, чем предыдущий .restore_object().

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

Эти методы также заменяют необязательный метод .save_object(), который больше не существует.

Следующий пример из учебника ранее использовал restore_object() для обработки создания и обновления экземпляров объектов.

def restore_object(self, attrs, instance=None):
    if instance:
        # Update existing instance
        instance.title = attrs.get('title', instance.title)
        instance.code = attrs.get('code', instance.code)
        instance.linenos = attrs.get('linenos', instance.linenos)
        instance.language = attrs.get('language', instance.language)
        instance.style = attrs.get('style', instance.style)
        return instance

    # Create new instance
    return Snippet(**attrs)

Теперь это будет разделено на два отдельных метода.

def update(self, instance, validated_data):
    instance.title = validated_data.get('title', instance.title)
    instance.code = validated_data.get('code', instance.code)
    instance.linenos = validated_data.get('linenos', instance.linenos)
    instance.language = validated_data.get('language', instance.language)
    instance.style = validated_data.get('style', instance.style)
    instance.save()
    return instance

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

Обратите внимание, что эти методы должны возвращать только что созданный экземпляр объекта.

Используйте .validated_data вместо .object.

Теперь вы должны использовать атрибут .validated_data, если вам нужно проверить данные перед сохранением, а не использовать атрибут .object, который больше не существует.

Например, следующий код уже недействителен* :

if serializer.is_valid():
    name = serializer.object.name  # Inspect validated field data.
    logging.info('Creating ticket "%s"' % name)
    serializer.object.user = request.user  # Include the user when saving.
    serializer.save()

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

Соответствующий код теперь будет выглядеть следующим образом:

if serializer.is_valid():
    name = serializer.validated_data['name']  # Inspect validated field data.
    logging.info('Creating ticket "%s"' % name)
    serializer.save(user=request.user)  # Include the user when saving.

Использование .is_valid(raise_exception=True)

Метод .is_valid() теперь принимает необязательный булев флаг, raise_exception.

Вызов .is_valid(raise_exception=True) приведет к возникновению ошибки ValidationError, если данные сериализатора содержат ошибки валидации. Эта ошибка будет обработана стандартным обработчиком исключений фреймворка REST, что позволит вам убрать обработку ответа на ошибку из кода представления.

Обработка и форматирование ответов на ошибки могут быть изменены глобально с помощью клавиши настроек EXCEPTION_HANDLER.

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

Использование serializers.ValidationError.

Ранее ошибка serializers.ValidationError была просто синонимом для django.core.exceptions.ValidationError. Теперь он изменен таким образом, что наследуется от стандартного базового класса APIException.

Причина этого в том, что класс Django ValidationError предназначен для использования с HTML формами, и его API делает его использование немного неудобным при вложенных ошибках валидации, которые могут возникать в сериализаторах.

Для большинства пользователей это изменение не потребует каких-либо обновлений в вашей кодовой базе, но стоит убедиться, что при возникновении ошибок валидации вы должны использовать класс исключения serializers.ValidationError, а не встроенное исключение Django.

Мы настоятельно рекомендуем использовать стиль импорта с разнесением имен import serializers, а не from serializers import ValidationError, чтобы избежать возможной путаницы.

Изменить на validate_<field_name>.

Крючки метода validate_<field_name>, которые могут быть присоединены к классам сериализатора, немного изменили свою сигнатуру и тип возврата. Ранее они принимали словарь всех входящих данных и ключ, представляющий имя поля, и возвращали словарь, включающий проверенные данные для этого поля:

def validate_score(self, attrs, source):
    if attrs['score'] % 10 != 0:
        raise serializers.ValidationError('This field should be a multiple of ten.')
    return attrs

Теперь это немного упрощено, и метод hooks просто принимает значение для проверки и возвращает проверенное значение.

def validate_score(self, value):
    if value % 10 != 0:
        raise serializers.ValidationError('This field should be a multiple of ten.')
    return value

Любая специальная валидация, применяемая к более чем одному полю, должна проходить в методе .validate(self, attrs), как обычно.

Поскольку .validate_<field_name> ранее принимал полный словарь атрибутов, его можно было использовать для проверки поля в зависимости от ввода в другом поле. Теперь, если вам нужно сделать это, вместо него следует использовать .validate().

Вы можете либо вернуть non_field_errors из метода validate, вызвав простое ValidationError

def validate(self, attrs):
    # serializer.errors == {'non_field_errors': ['A non field error']}
    raise serializers.ValidationError('A non field error')

В качестве альтернативы, если вы хотите, чтобы ошибки относились к определенному полю, используйте словарь при инстанцировании ValidationError , например, так:

def validate(self, attrs):
    # serializer.errors == {'my_field': ['A field error']}
    raise serializers.ValidationError({'my_field': 'A field error'})

Таким образом, вы можете написать проверку, которая сравнивает все поля ввода, но отмечает ошибку в конкретном поле.

Удаление transform_<field_name>.

Недостаточно используемый transform_<field_name> на классах сериализаторов больше не предоставляется. Вместо этого вы должны просто переопределить to_representation(), если вам нужно применить какие-либо изменения к стилю представления.

Например:

def to_representation(self, instance):
    ret = super(UserSerializer, self).to_representation(instance)
    ret['username'] = ret['username'].lower()
    return ret

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

Если вам абсолютно необходимо сохранить поведение transform_<field_name>, например, чтобы обеспечить более простое обновление с 2.x на 3.0, вы можете использовать миксин или базовый класс сериализатора, которые добавят это поведение обратно. Например:

class BaseModelSerializer(ModelSerializer):
    """
    A custom ModelSerializer class that preserves 2.x style `transform_<field_name>` behavior.
    """
    def to_representation(self, instance):
        ret = super(BaseModelSerializer, self).to_representation(instance)
        for key, value in ret.items():
            method = getattr(self, 'transform_' + key, None)
            if method is not None:
                ret[key] = method(value)
        return ret

Различия между валидацией ModelSerializer и ModelForm.

Это изменение также означает, что мы больше не используем метод .full_clean() на экземплярах модели, а вместо этого выполняем всю валидацию явно в сериализаторе. Это дает более чистое разделение и гарантирует, что на классах ModelSerializer не будет автоматического поведения валидации, которое не может быть легко воспроизведено на обычных классах Serializer.

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

Единственное отличие, которое необходимо отметить, заключается в том, что метод .clean() не будет вызываться как часть проверки сериализатора, как это было бы при использовании ModelForm. Используйте метод сериализатора .validate() для выполнения последнего шага проверки входящих данных, если это необходимо.

Бывают случаи, когда вам действительно необходимо сохранить логику валидации в методе модели .clean(), и вы не можете отделить ее от сериализатора .validate(). Вы можете сделать это, явно инстанцировав экземпляр модели в методе .validate().

def validate(self, attrs):
    instance = ExampleModel(**attrs)
    instance.clean()
    return attrs

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

Записываемая вложенная сериализация.

REST framework 2.x пытался автоматически поддержать записываемую вложенную сериализацию, но поведение было сложным и неочевидным. Попытка автоматической обработки этих случаев проблематична:

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

  • Неясно, какого поведения следует ожидать пользователю, когда связанным моделям передаются данные None.

  • Неясно, как пользователь должен ожидать, что отношения «to-many» будут обрабатывать обновления, создание и удаление нескольких записей.

Использование опции depth на ModelSerializer теперь по умолчанию будет создавать вложенные сериализаторы только для чтения.

Если вы попытаетесь использовать записываемый вложенный сериализатор без написания пользовательского метода create() и/или update(), вы увидите ошибку утверждения при попытке сохранить сериализатор. Например:

>>> class ProfileSerializer(serializers.ModelSerializer):
>>>     class Meta:
>>>         model = Profile
>>>         fields = ['address', 'phone']
>>>
>>> class UserSerializer(serializers.ModelSerializer):
>>>     profile = ProfileSerializer()
>>>     class Meta:
>>>         model = User
>>>         fields = ['username', 'email', 'profile']
>>>
>>> data = {
>>>     'username': 'lizzy',
>>>     'email': 'lizzy@example.com',
>>>     'profile': {'address': '123 Acacia Avenue', 'phone': '01273 100200'}
>>> }
>>>
>>> serializer = UserSerializer(data=data)
>>> serializer.save()
AssertionError: The `.create()` method does not support nested writable fields by default. Write an explicit `.create()` method for serializer `UserSerializer`, or set `read_only=True` on nested serializer fields.

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

Одноэтапное создание объекта делает это намного проще и очевиднее, чем предыдущее поведение .restore_object().

Печатные представления сериализатора.

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

Например, если взять следующий пример модели:

class LocationRating(models.Model):
    location = models.CharField(max_length=100)
    rating = models.IntegerField()
    created_by = models.ForeignKey(User)

Давайте создадим простой класс ModelSerializer, соответствующий модели LocationRating.

class LocationRatingSerializer(serializer.ModelSerializer):
    class Meta:
        model = LocationRating

Теперь мы можем проверить представление сериализатора в оболочке Django, используя python manage.py shell

>>> serializer = LocationRatingSerializer()
>>> print(serializer)  # Or use `print serializer` in Python 2.x
LocationRatingSerializer():
    id = IntegerField(label='ID', read_only=True)
    location = CharField(max_length=100)
    rating = IntegerField()
    created_by = PrimaryKeyRelatedField(queryset=User.objects.all())

Опция extra_kwargs.

Опция write_only_fields в ModelSerializer была перенесена в PendingDeprecation и заменена на более общую extra_kwargs.

class MySerializer(serializer.ModelSerializer):
    class Meta:
        model = MyModel
        fields = ['id', 'email', 'notes', 'is_admin']
        extra_kwargs = {
                'is_admin': {'write_only': True}
        }

В качестве альтернативы, укажите поле явно в классе сериализатора:

class MySerializer(serializer.ModelSerializer):
    is_admin = serializers.BooleanField(write_only=True)

    class Meta:
        model = MyModel
        fields = ['id', 'email', 'notes', 'is_admin']

Опция read_only_fields остается как удобное сокращение для более распространенного случая.

Изменяется на HyperlinkedModelSerializer.

Опции view_name и lookup_field были перенесены в PendingDeprecation. Они больше не требуются, так как вместо них можно использовать аргумент extra_kwargs:

class MySerializer(serializer.HyperlinkedModelSerializer):
    class Meta:
        model = MyModel
        fields = ['url', 'email', 'notes', 'is_admin']
        extra_kwargs = {
            'url': {'lookup_field': 'uuid'}
        }

В качестве альтернативы, укажите поле явно в классе сериализатора:

class MySerializer(serializer.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name='mymodel-detail',
        lookup_field='uuid'
    )

    class Meta:
        model = MyModel
        fields = ['url', 'email', 'notes', 'is_admin']

Поля для методов и свойств модели.

С помощью опции ModelSerializer теперь можно указать имена полей в опции fields, которые ссылаются на методы или свойства модели. Например, предположим, что у вас есть следующая модель:

class Invitation(models.Model):
    created = models.DateTimeField()
    to_email = models.EmailField()
    message = models.CharField(max_length=1000)

    def expiry_date(self):
        return self.created + datetime.timedelta(days=30)

Вы можете включить expiry_date в качестве опции поля в класс ModelSerializer.

class InvitationSerializer(serializers.ModelSerializer):
    class Meta:
        model = Invitation
        fields = ['to_email', 'message', 'expiry_date']

Эти поля будут сопоставлены с экземплярами serializers.ReadOnlyField().

>>> serializer = InvitationSerializer()
>>> print(repr(serializer))
InvitationSerializer():
    to_email = EmailField(max_length=75)
    message = CharField(max_length=1000)
    expiry_date = ReadOnlyField()

Класс ListSerializer.

Теперь добавлен класс ListSerializer, который позволяет создавать базовые классы сериализаторов только для приема нескольких входов.

class MultipleUserSerializer(ListSerializer):
    child = UserSerializer()

Вы также можете по-прежнему использовать аргумент many=True в классах сериализаторов. Стоит отметить, что аргумент many=True прозрачно создает экземпляр ListSerializer, позволяя чисто разделить логику валидации для списочных и несписочных данных в кодовой базе REST-фреймворка.

Обычно лучше продолжать использовать существующий флаг ``many=True``, чем объявлять классы ListSerializer явно, но объявление классов явно может быть полезным, если вам нужно написать пользовательские create или update методы для массовых обновлений, или обеспечить другое пользовательское поведение.

Смотрите также новый класс ListField, который проверяет ввод таким же образом, но не включает интерфейсы сериализатора .is_valid() , .data , .save() и так далее.

Класс BaseSerializer.

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

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

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

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

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

  • .errors - Возвращает ошибку при проверке.

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

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

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

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

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

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

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

Только для чтения BaseSerializer классы.

Чтобы реализовать сериализатор только для чтения, используя класс BaseSerializer, нам просто нужно переопределить метод .to_representation(). Давайте рассмотрим пример на примере простой модели Django:

class HighScore(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    player_name = models.CharField(max_length=10)
    score = models.IntegerField()

Очень просто создать сериализатор только для чтения для преобразования экземпляров HighScore в примитивные типы данных.

class HighScoreSerializer(serializers.BaseSerializer):
    def to_representation(self, obj):
        return {
            'score': obj.score,
            'player_name': obj.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(). Этот метод возвращает проверенные значения, которые будут использованы для построения экземпляра объекта, и может выдать ошибку 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 ValidationError({
                'score': 'This field is required.'
            })
        if not player_name:
            raise ValidationError({
                'player_name': 'This field is required.'
            })
        if len(player_name) > 10:
            raise 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, obj):
        return {
            'score': obj.score,
            'player_name': obj.player_name
        }

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

Создание новых общих сериализаторов с BaseSerializer.

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

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

class ObjectSerializer(serializers.BaseSerializer):
    """
    A read-only serializer that coerces arbitrary complex objects
    into primitive representations.
    """
    def to_representation(self, obj):
        for attribute_name in dir(obj):
            attribute = getattr(obj, 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)

Поля сериализатора

Классы полей Field и ReadOnly.

Есть несколько незначительных изменений в базовых классах полей.

Ранее у нас было два базовых класса:

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

  • WritableField как базовый класс для полей чтения-записи.

Теперь мы используем следующее:

  • Field - это базовый класс для всех полей. Он не включает никакой реализации по умолчанию для сериализации или десериализации данных.

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

Аргументы required , allow_null , allow_blank и default.

Фреймворк REST теперь имеет более явный и четкий контроль над проверкой пустых значений для полей.

Ранее значение ключевого слова-аргумента required=False было неопределенным. На практике его использование означало, что поле может быть либо не включено во входные данные, либо включено, но иметь значение None или пустую строку.

Теперь мы имеем лучшее разделение, с отдельными аргументами required , allow_null и allow_blank.

Следующий набор аргументов используется для управления проверкой пустых значений:

  • required=False : Значение не обязательно должно присутствовать на входе, и не будет передано в .create() или .update(), если его не видно.

  • default=<value> : Значение не обязательно должно присутствовать на входе, и значение по умолчанию будет передано в .create() или .update(), если его не видно.

  • allow_null=True : None является допустимым входом.

  • allow_blank=True : '' является допустимым вводом. Только для CharField и подклассов.

Обычно вы хотите использовать required=False, если соответствующее поле модели имеет значение по умолчанию, и дополнительно установить allow_null=True или allow_blank=True, если это необходимо.

Аргумент default также доступен и всегда подразумевает, что поле не обязательно должно быть во входных данных. Нет необходимости использовать аргумент required, если указано значение по умолчанию, это приведет к ошибке.

Принудительные типы вывода.

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

Удаление .validate().

Метод .validate() теперь удален из классов полей. Этот метод в любом случае был недокументирован и не являлся публичным API. Вместо этого вы должны просто переопределить to_internal_value().

class UppercaseCharField(serializers.CharField):
    def to_internal_value(self, data):
        value = super(UppercaseCharField, self).to_internal_value(data)
        if value != value.upper():
            raise serializers.ValidationError('The input should be uppercase only.')
        return value

Ранее ошибки валидации могли возникать либо в .to_native(), либо в .validate() , что делало неочевидным, что именно следует использовать. Предоставление только одной точки API обеспечивает большее повторение и укрепление основного API.

Класс ListField.

Теперь добавлен класс ListField. Это поле проверяет правильность ввода списка. Оно принимает аргумент с ключевым словом child, который используется для указания поля, используемого для проверки каждого элемента списка. Например:

scores = ListField(child=IntegerField(min_value=0, max_value=100))

Вы также можете использовать декларативный стиль для создания новых подклассов ListField , например, так:

class ScoresField(ListField):
    child = IntegerField(min_value=0, max_value=100)

Теперь мы можем использовать класс ScoresField внутри другого сериализатора:

scores = ScoresField()

Смотрите также новый класс ListSerializer, который проверяет ввод таким же образом, но также включает интерфейсы сериализатора .is_valid() , .data , .save() и так далее.

Класс ChoiceField теперь может принимать плоский список.

Класс ChoiceField теперь может принимать список вариантов в дополнение к существующему стилю использования списка пар (name, display_value). Теперь допустимо следующее:

color = ChoiceField(choices=['red', 'green', 'blue'])

Класс MultipleChoiceField.

Добавлен класс MultipleChoiceField. Это поле действует как ChoiceField , но возвращает набор, который может включать ни один, один или много допустимых вариантов.

Изменения в API пользовательских полей.

Имена методов from_native(self, value) и to_native(self, data) были заменены на более очевидные to_internal_value(self, data) и to_representation(self, value).

Методы field_from_native() и field_to_native() удалены. Ранее вы могли использовать эти методы, если хотели настроить поведение таким образом, чтобы не просто искать значение поля в объекте. Например.

def field_to_native(self, obj, field_name):
    """A custom read-only field that returns the class name."""
    return obj.__class__.__name__

Теперь, если вам нужен доступ ко всему объекту, вам придется переопределить один или оба из следующих параметров:

  • Используйте get_attribute для изменения значения атрибута, переданного в to_representation().

  • Используйте get_value для изменения значения данных, переданных to_internal_value().

Например:

def get_attribute(self, obj):
    # Pass the entire object through to `to_representation()`,
    # instead of the standard attribute lookup.
    return obj

def to_representation(self, value):
    return value.__class__.__name__

Явное queryset требуется для реляционных полей.

Ранее реляционные поля, которые были явно объявлены в классе сериализатора, могли опускать аргумент queryset, если (и только если) они были объявлены в ModelSerializer.

Этот код будет действителен в 2.4.3 :

class AccountSerializer(serializers.ModelSerializer):
    organizations = serializers.SlugRelatedField(slug_field='name')

    class Meta:
        model = Account

Однако этот код не будет действителен в 3.0 :

# Missing `queryset`
class AccountSerializer(serializers.Serializer):
    organizations = serializers.SlugRelatedField(slug_field='name')

    def restore_object(self, attrs, instance=None):
        # ...

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

class AccountSerializer(serializers.ModelSerializer):
    organizations = serializers.SlugRelatedField(
        slug_field='name',
        queryset=Organization.objects.all()
    )

    class Meta:
        model = Account

Аргумент queryset требуется только для полей с возможностью записи, и не требуется и не действителен для полей с read_only=True.

Необязательный аргумент для SerializerMethodField.

Аргумент SerializerMethodField теперь необязателен и по умолчанию равен get_<field_name>. В качестве примера можно привести следующее:

class AccountSerializer(serializers.Serializer):
    # `method_name='get_billing_details'` by default.
    billing_details = serializers.SerializerMethodField()

    def get_billing_details(self, account):
        return calculate_billing(account)

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

billing_details = serializers.SerializerMethodField('get_billing_details')

Обеспечение последовательного использования source.

Я видел несколько кодовых баз, которые без необходимости включают аргумент source, устанавливая его в то же значение, что и имя поля. Такое использование является избыточным и запутывающим, делая менее очевидным тот факт, что source обычно не требуется.

Следующее использование теперь вызовет ошибку* :

email = serializers.EmailField(source='email')

Классы UniqueValidator и UniqueTogetherValidator.

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

UniqueValidator применяется к полю сериализатора и принимает единственный аргумент queryset.

from rest_framework import serializers
from rest_framework.validators import UniqueValidator

class OrganizationSerializer(serializers.Serializer):
    url = serializers.HyperlinkedIdentityField(view_name='organization_detail')
    created = serializers.DateTimeField(read_only=True)
    name = serializers.CharField(
        max_length=100,
        validators=UniqueValidator(queryset=Organization.objects.all())
    )

UniqueTogetherValidator должен применяться к сериализатору и принимает аргумент queryset и аргумент fields, который должен представлять собой список или кортеж имен полей.

class RaceResultSerializer(serializers.Serializer):
    category = serializers.ChoiceField(['5k', '10k'])
    position = serializers.IntegerField()
    name = serializers.CharField(max_length=100)

    class Meta:
        validators = [UniqueTogetherValidator(
            queryset=RaceResult.objects.all(),
            fields=['category', 'position']
        )]

Классы UniqueForDateValidator.

REST framework также теперь включает явные классы валидаторов для проверки ограничений полей модели unique_for_date , unique_for_month , и unique_for_year. Они используются внутри системы вместо вызова Model.full_clean().

Эти классы документированы в разделе документации Validators.


Общие представления

Упрощение логики представления.

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

Изменения в хуках до/после сохранения.

Крючки pre_save и post_save больше не существуют, а заменены на perform_create(self, serializer) и perform_update(self, serializer).

Эти методы должны сохранять экземпляр объекта, вызывая serializer.save() , добавляя при необходимости дополнительные аргументы. Они также могут выполнять любое пользовательское поведение до или после сохранения.

Например:

def perform_create(self, serializer):
    # Include the owner attribute directly, rather than from request data.
    instance = serializer.save(owner=self.request.user)
    # Perform a custom post-save action.
    send_email(instance.to_email, instance.message)

Крючки pre_delete и post_delete больше не существуют и заменяются на .perform_destroy(self, instance) , который должен удалить экземпляр и выполнить любые пользовательские действия.

def perform_destroy(self, instance):
    # Perform a custom pre-delete action.
    send_deletion_alert(user=instance.created_by, deleted=instance)
    # Delete the object instance.
    instance.delete()

Удаление атрибутов вида.

Атрибуты .object и .object_list больше не устанавливаются для экземпляра представления. Обращение с представлениями как с изменяемыми экземплярами объектов, которые хранят состояние во время обработки представления, как правило, является плохим дизайном и может привести к неясной логике потока.

Я бы лично рекомендовал разработчикам рассматривать экземпляры представлений как неизменяемые объекты в коде приложения.

PUT как создать.

Разрешение PUT в качестве операций создания является проблематичным, поскольку оно обязательно раскрывает информацию о существовании или несуществовании объектов. Также не очевидно, что прозрачное разрешение повторного создания ранее удаленных экземпляров обязательно является лучшим поведением по умолчанию, чем простое возвращение ответов 404.

Оба стиля «** PUT as 404» и «** PUT as create» могут быть действительны в различных обстоятельствах, но сейчас мы выбрали поведение 404 по умолчанию, так как оно проще и очевиднее.

Если вам нужно восстановить прежнее поведение, вы можете включить this ``AllowPUTAsCreateMixin:doc:` class <https://gist.github.com/tomchristie/a2ace4577eff2c603b1b>` в качестве миксина в ваши представления.

Настройка ответов на ошибки.

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

Это изменение означает, что теперь вы можете легко настроить стиль ответов на ошибки для всего вашего API, без необходимости изменять какие-либо общие представления.


API метаданных

Поведение для работы с запросами OPTIONS ранее было встроено непосредственно в представления на основе классов. Теперь это было должным образом отделено в API метаданных, который позволяет использовать тот же подключаемый стиль, что и другие политики API в REST-фреймворке.

Это значительно упрощает использование различных стилей для ответов OPTIONS во всем вашем API, а также делает возможным создание сторонних политик метаданных.


Сериализаторы как HTML-формы

REST framework 3.0 включает шаблонизированный рендеринг HTML-форм для сериализаторов.

Этот API еще не следует считать окончательно доработанным, он будет переведен в разряд публичных API только в релизе 3.1.

К существенным изменениям, о которых вам необходимо знать, относятся:

  • Вложенные HTML-формы теперь поддерживаются, например, UserSerializer с вложенным ProfileSerializer теперь будет отображать вложенный fieldset при использовании в просматриваемом API.

  • Вложенные списки HTML-форм пока не поддерживаются, но планируются в версии 3.1.

  • Поскольку теперь мы используем шаблонизированную генерацию HTML-форм, опция ``widget`` больше не доступна для полей сериализатора. Вместо этого вы можете управлять шаблоном, который используется для данного поля, с помощью словаря style.

Аргумент ключевого слова style для полей сериализатора.

Ключевой аргумент style может быть использован для передачи дополнительной информации от поля сериализатора классу рендеринга. В частности, HTMLFormRenderer использует ключ base_template, чтобы определить, с каким шаблоном рендерить поле.

Например, чтобы использовать элемент управления textarea вместо элемента управления по умолчанию input, вы должны использовать следующее…

additional_notes = serializers.CharField(
    style={'base_template': 'textarea.html'}
)

Аналогично, чтобы использовать элемент управления радиокнопка вместо элемента управления по умолчанию select, вы должны использовать следующее…

color_channel = serializers.ChoiceField(
    choices=['red', 'blue', 'green'],
    style={'base_template': 'radio.html'}
)

Этот API следует считать предварительным, и в ближайшем релизе 3.1 могут быть внесены незначительные изменения.


Стиль API

Есть некоторые улучшения в стиле по умолчанию, который мы используем в наших ответах API.

Юникод JSON по умолчанию.

Юникодовый JSON теперь используется по умолчанию. Класс UnicodeJSONRenderer больше не существует, и была добавлена настройка UNICODE_JSON. Чтобы изменить это поведение, используйте новую настройку:

REST_FRAMEWORK = {
    'UNICODE_JSON': False
}

Компактный JSON по умолчанию.

Теперь мы по умолчанию выводим компактный JSON в ответах. Например, мы возвращаем:

{"email":"amy@example.com","is_admin":true}

Вместо следующего:

{"email": "amy@example.com", "is_admin": true}

Была добавлена настройка COMPACT_JSON, которая может быть использована для изменения этого поведения при необходимости:

REST_FRAMEWORK = {
    'COMPACT_JSON': False
}

Поля файлов в виде URL-адресов

Классы FileField и ImageField теперь по умолчанию представляются как URL. Вы должны убедиться, что вы установили standard ``MEDIA_URL:doc:` setting <https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-MEDIA_URL>` Django соответствующим образом, и убедиться, что ваше приложение serves the uploaded files.

Вы можете изменить это поведение и отобразить имена файлов в представлении, используя клавишу настройки UPLOADED_FILES_USE_URL:

REST_FRAMEWORK = {
    'UPLOADED_FILES_USE_URL': False
}

Вы также можете изменять поля сериализатора по отдельности, используя аргумент use_url:

uploaded_file = serializers.FileField(use_url=False)

Также обратите внимание, что при инстанцировании сериализатора в качестве контекста ему следует передать объект request, чтобы можно было вернуть полностью определенный URL. Возвращаемые URL будут иметь вид https://example.com/url_path/filename.txt. Например:

context = {'request': request}
serializer = ExampleSerializer(instance, context=context)
return Response(serializer.data)

Если запрос опущен в контексте, возвращаемые URL будут иметь вид /url_path/filename.txt.

Дросселируйте заголовки с помощью Retry-After.

Пользовательский заголовок X-Throttle-Wait-Second теперь отменен в пользу стандартного заголовка Retry-After. При необходимости вы можете изменить это поведение, написав собственный обработчик исключений для вашего приложения.

Объекты даты и времени в виде строк ISO-8601 в данных сериализатора.

Объекты даты и времени теперь по умолчанию принудительно приводятся к строкам в выводе сериализатора. Ранее они возвращались как объекты Date , Time и DateTime, а затем приводились к строкам рендерером.

Вы можете изменить это поведение глобально, настроив существующие ключи настроек DATE_FORMAT , DATETIME_FORMAT и TIME_FORMAT. Установка этих значений в None вместо значения по умолчанию 'iso-8601' приведет к тому, что нативные объекты будут возвращаться в данных сериализатора.

REST_FRAMEWORK = {
    # Return native `Date` and `Time` objects in `serializer.data`
    'DATETIME_FORMAT': None
    'DATE_FORMAT': None
    'TIME_FORMAT': None
}

Вы также можете изменять поля сериализатора по отдельности, используя аргументы date_format , time_format и datetime_format:

# Return `DateTime` instances in `serializer.data`, not strings.
created = serializers.DateTimeField(format=None)

Десятичные числа как строки в данных сериализатора.

Десятичные числа теперь по умолчанию преобразуются в строки в выводе сериализатора. Ранее они возвращались как объекты Decimal, а затем приводились к строкам рендерером.

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

REST_FRAMEWORK = {
    'COERCE_DECIMAL_TO_STRING': False
}

Или измените его для отдельного поля сериализатора, используя аргумент ключевого слова coerce_to_string.

# Return `Decimal` instances in `serializer.data`, not strings.
amount = serializers.DecimalField(
    max_digits=10,
    decimal_places=2,
    coerce_to_string=False
)

Рендерер JSON по умолчанию будет возвращать объекты float для некоррелированных экземпляров Decimal. Это позволяет вам легко переключаться между строковым и плавающим представлением десятичных дробей в зависимости от потребностей вашего API.


Разные заметки

  • Сериализатор ChoiceField в настоящее время не отображает вложенные варианты, как это было в версии 2.4. Эта проблема будет решена в версии 3.1.

  • В связи с новым шаблонизированным рендерингом формы опция «виджет» больше не действует. Это означает, что теперь нет возможности использовать сторонние виджеты «автозаполнения» для отображения входов select, содержащих большое количество вариантов. Вам придется использовать либо обычный select, либо обычный текстовый ввод. Мы можем рассмотреть возможность решения этой проблемы в версии 3.1 или 3.2, если будет достаточно желающих.

  • Некоторые сообщения об ошибках валидации по умолчанию были переписаны и больше не могут быть предварительно переведены. Вы все еще можете create language files with Django, если хотите их локализовать.

  • Ранее подклассы APIException могли принимать любой произвольный тип в аргументе detail. Теперь эти исключения используют переводимые текстовые строки, и в результате вызывают force_text на аргументе detail, который должен быть строкой. Если вам нужны сложные аргументы для класса APIException, вам следует создать его подкласс и переопределить метод __init__(). Обычно вместо этого вы хотите использовать пользовательский обработчик исключений для обеспечения нестандартных ответов на ошибки.


Что будет дальше

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

В выпуске 3.1 планируется внести улучшения в следующие компоненты:

  • Публичный API для использования сериализаторов в качестве HTML-форм.

  • Разбор запросов, медиатипы и реализация просматриваемого API.

  • Внедрение нового API пагинации.

  • Улучшенная поддержка версионности API.

В релизе 3.2 планируется представить альтернативный интерфейс в стиле администратора для просматриваемого API.

Вы можете следить за разработкой на сайте GitHub, где мы используем milestones to indicate planning timescales.

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