Подтвердить что ты не робот

Django Rest Framework - чтение вложенных данных, запись целых чисел

До сих пор я очень доволен Django Rest Framework, поэтому я почти не могу поверить в такое большое упущение в кодовой базе. Надеюсь, кто-то знает, как это поддерживать:

class PinSerializer(serializers.ModelSerializer):
    item = ItemSerializer(read_only=True, source='item')
    item = serializers.IntegerSerializer(write_only=True)

    class Meta:
        model = Pin

с целью

The goal here is to read:
{pin: item: {name: 'a', url: 'b'}}
but to write using an id
{pin: item: 10}

Альтернативой может быть использование двух сериализаторов, но это похоже на действительно уродливое решение: django rest framework модельные сериализаторы - читать вложенные, писать плоские

4b9b3361

Ответ 1

Предполагая, что вы используете OneToOneField или ForeignKey для связи вашего Pin с вашим Item, Django сохраняет отношение как item_id, но часто абстрагирует Item как item. Вы можете воспользоваться этим, чтобы обойти тот факт, что объект Python не может иметь два атрибута с тем же именем (проблема, с которой вы столкнетесь в своем коде).

Просто добавьте _id к имени вашего атрибута записи, и любые записи установят базовое отношение, в то время как любые чтения будут использовать абстрактный объект. Конечный код будет:

class PinSerializer(serializers.ModelSerializer):
    item = ItemSerializer(read_only=True)
    item_id = serializers.IntegerField(write_only=True)

    class Meta:
        model = Pin

Примечание 1: Я также удалил source='item', поскольку он был избыточным и изменил serializers.IntegerSerializer на serializers.IntegerField, так как я думаю, что это была опечатка.

Примечание 2: На самом деле я считаю неинтуитивным, что Django Rest настроен таким образом, что сериализатор Pin без заданного сериализатора элемента возвращает item_id как "item": <id>, а не "item_id": <id>, но это не относится к точке.

Ответ 2

Если вы используете DRF 3.0, вы можете реализовать новый метод to_internal_value для переопределения поля элемента, чтобы изменить его на PrimaryKeyRelatedField, чтобы разрешить плоскую запись. Значение to_internal_value принимает неутвержденные входящие данные как входные данные и должно возвращать проверенные данные, которые будут доступны как serializer.validated_data. См. Документы: http://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data

Поэтому в вашем случае это будет:

class ItemSerializer(ModelSerializer):
    class Meta:
        model = Item

class PinSerializer(ModelSerializer):
    item = ItemSerializer() 

    # override the nested item field to PrimareKeyRelatedField on writes
    def to_internal_value(self, data):
         self.fields['item'] = serializers.PrimaryKeyRelatedField(queryset=Item.objects.all())
         return super(PinSerializer, self).to_internal_value(data)

    class Meta:
        model = Pin

Следует отметить две вещи: просматриваемый веб-апи все равно будет думать, что записи будут вложенными. Я не уверен, как это исправить, но я использую только веб-интерфейс для отладки, поэтому не большое дело. Кроме того, после того, как вы напишите, возвращаемый элемент будет иметь плоский элемент вместо вложенного. Чтобы исправить это, вы можете добавить этот код, чтобы заставить чтение использовать сериализатор элементов всегда.

def to_representation(self, obj):
    self.fields['item'] = ItemSerializer()
    return super(PinSerializer, self).to_representation(obj)

Я получил эту идею от Антона Дмитриевского здесь: DRF: Простое назначение внешнего ключа с вложенными сериализаторами?

Ответ 3

Вы можете создать настраиваемое поле Serializer (http://www.django-rest-framework.org/api-guide/fields)

Пример взяты из ссылки:

class ColourField(serializers.WritableField):
    """
    Color objects are serialized into "rgb(#, #, #)" notation.
    """
    def to_native(self, obj):
        return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue)

    def from_native(self, data):
        data = data.strip('rgb(').rstrip(')')
        red, green, blue = [int(col) for col in data.split(',')]
        return Color(red, green, blue)

Затем используйте это поле в вашем классе сериализатора.