Django Rest Framework - 读取嵌套数据,写入整数

时间:2014-10-25 10:58:36

标签: python django rest 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 model serializers - read nested, write flat

4 个答案:

答案 0 :(得分:14)

Django允许您使用item属性访问Pin上的项目,但实际上将关系存储为item_id。您可以在序列化程序中使用此策略来解决Python对象不能具有两个具有相同名称的属性(您在代码中遇到的问题)的事实。

执行此操作的最佳方法是使用带有PrimaryKeyRelatedField参数的source。这将确保完成正确的验证,在字段验证期间(紧接在序列化程序的"item_id": <id>调用之前)将"item": <instance>转换为validate。这允许您在validatecreateupdate方法期间操纵完整对象。你的最终代码是:

class PinSerializer(serializers.ModelSerializer):
    item = ItemSerializer(read_only=True)
    item_id = serializers.PrimaryKeyRelatedField(write_only=True,
                                                 source='item',
                                                 queryset=Item.objects.all())

    class Meta:
        model = Pin
        fields = ('id', 'item', 'item_id',)

注1:我还删除了读取字段上的source='item',因为这是多余的。

注2:我实际上发现Django Rest的设置非常不直观,没有指定Item序列化器的Pin序列化器将item_id返回为"item": <id>而不是"item_id": <id>,但是点。

此方法甚至可以与正向和反向“多”关系一起使用。例如,您可以使用item_ids数组来设置项目中的所有引脚,并使用以下代码:

class ItemSerializer(serializers.ModelSerializer):
    pins = PinSerializer(many=True, read_only=True)
    pin_ids = serializers.PrimaryKeyRelatedField(many=True,
                                                 write_only=True,
                                                 source='pins',
                                                 queryset=Pin.objects.all())

    class Meta:
        model = Item
        fields = ('id', 'pins', 'pin_ids',)

我之前建议的另一个策略是使用IntegerField直接设置item_id。假设您使用OneToOneField或ForeignKey将Pin与项目相关联,则可以将item_id设置为整数,而不使用item字段。这会削弱验证并导致违反约束的DB级错误。如果你想跳过验证DB调用,特别需要ID而不是验证/创建/更新代码中的对象,或者需要同时具有相同源的可写字段,这可能会更好,但我不会再推荐一下。完整的一行是:

item_id = serializers.IntegerField(write_only=True)

答案 1 :(得分:11)

如果您使用的是DRF 3.0,则可以实现新的to_internal_value方法来覆盖item字段,将其更改为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

需要注意的两件事:可浏览的web api仍然会认为写入是嵌套的。我不知道如何解决这个问题,但我只使用网络界面进行调试,这不是什么大问题。此外,在您编写返回的项目后,将使用平面项而不是嵌套项。要解决此问题,您可以添加此代码以强制读取始终使用Item序列化程序。

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

我从Anton Dmitrievsky的回答中得到了这个想法:DRF: Simple foreign key assignment with nested serializers?

答案 2 :(得分:2)

您可以创建自定义序列化器字段(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)

然后在序列化程序类中使用此字段。

答案 3 :(得分:0)

我创建了一个Field类型,该类型试图解决带有Integer中的ForeignKey的数据保存请求以及读取具有嵌套数据的数据的请求

这是课程:

class NestedRelatedField(serializers.PrimaryKeyRelatedField):
"""
    Model identical to PrimaryKeyRelatedField but its
    representation will be nested and its input will
    be a primary key.
"""

def __init__(self, **kwargs):
    self.pk_field = kwargs.pop('pk_field', None)
    self.model = kwargs.pop('model', None)
    self.serializer_class = kwargs.pop('serializer_class', None)
    super().__init__(**kwargs)

def to_representation(self, data):
    pk = super(NestedRelatedField, self).to_representation(data)
    try:
        return self.serializer_class(self.model.objects.get(pk=pk)).data
    except self.model.DoesNotExist:
        return None

def to_internal_value(self, data):
    return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)

因此将被使用:

class PostModelSerializer(serializers.ModelSerializer):

    message = NestedRelatedField(
         queryset=MessagePrefix.objects.all(),
         model=MessagePrefix,
         serializer_class=MessagePrefixModelSerializer
   )

希望对您有帮助。