问题 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框架模型序列化器 - 读取嵌套,写入平面


9500
2017-10-25 10:58


起源



答案:


假设您使用OneToOneField或ForeignKey将Pin与您的Item相关联,Django将关系存储为 item_id,但经常将项目抽象为 item。您可以利用这一点来解决Python对象不能具有两个具有相同名称的属性(您在代码中遇到的问题)的事实。

只需添加 _id 对于write属性的名称,任何写入都将设置基础关系,而任何读取都将使用抽象对象。您的最终代码将是:

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的设置非常不直观,没有指定Item序列化器的Pin序列化器返回item_id为 "item": <id> 并不是 "item_id": <id>,但这不是重点。


9
2018-05-30 15:59



这可能是一个评论。您可以添加更多详细信息,以使其成为一个好的答案。 - Raju
不能发表评论,因为我没有50个声望。我将添加其工作原理,但除此之外,还没有更多细节需要说明。 - Jonathan Richards
听起来不错。否则,帖子可能会被标记为低质量答案,并且可能会被推送到审核队列。 - Raju
是不推荐使用IntegerSerializer?在文档中看不到它。 - Shawn Anderson
@ShawnAnderson我很糟糕,我没有注意到原问题中的拼写错误。它应该是IntegerField,我会更新我的答案。 - Jonathan Richards


答案:


假设您使用OneToOneField或ForeignKey将Pin与您的Item相关联,Django将关系存储为 item_id,但经常将项目抽象为 item。您可以利用这一点来解决Python对象不能具有两个具有相同名称的属性(您在代码中遇到的问题)的事实。

只需添加 _id 对于write属性的名称,任何写入都将设置基础关系,而任何读取都将使用抽象对象。您的最终代码将是:

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的设置非常不直观,没有指定Item序列化器的Pin序列化器返回item_id为 "item": <id> 并不是 "item_id": <id>,但这不是重点。


9
2018-05-30 15:59



这可能是一个评论。您可以添加更多详细信息,以使其成为一个好的答案。 - Raju
不能发表评论,因为我没有50个声望。我将添加其工作原理,但除此之外,还没有更多细节需要说明。 - Jonathan Richards
听起来不错。否则,帖子可能会被标记为低质量答案,并且可能会被推送到审核队列。 - Raju
是不推荐使用IntegerSerializer?在文档中看不到它。 - Shawn Anderson
@ShawnAnderson我很糟糕,我没有注意到原问题中的拼写错误。它应该是IntegerField,我会更新我的答案。 - Jonathan Richards


如果您使用的是DRF 3.0,则可以实施新的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仍然会认为写入是嵌套的。我不知道如何解决这个问题,但我只使用Web界面进行调试,这不是什么大问题。此外,在您编写返回的项目后将具有平面项目而不是嵌套项目。要解决此问题,您可以添加此代码以强制读取始终使用Item序列化程序。

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

我从Anton Dmitrievsky的答案中得到了这个想法: DRF:使用嵌套序列化程序进行简单的外键分配?


6
2017-09-07 05:51



这个评论解决了我花了几个小时试图解决的问题。谢谢! - Jasper


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

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


1
2017-10-25 11:54



如果有人稍后遇到此问题,请注意DRF 3.0中的Field API已更改。 from_native(self, ob) 就是现在 to_representation(self, obj) 和 to_native(self, data) 就是现在 to_internal_value(self, data) - MBrizzle