【问题标题】:Django Rest Framework - Read nested data, write integerDjango Rest Framework - 读取嵌套数据,写入整数
【发布时间】:2014-12-21 02:26:12
【问题描述】:

到目前为止,我对 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

【问题讨论】:

    标签: python django rest django-rest-framework


    【解决方案1】:

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

    最好的方法是使用PrimaryKeyRelatedFieldsource 参数。这将确保完成正确的验证,在字段验证期间将"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>,但这不是重点。

    此方法甚至可以用于正向和反向“多”关系。例如,您可以使用 pin_ids 的数组来设置 Item 上的所有 Pins,代码如下:

    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 级错误。如果您想跳过验证数据库调用,在验证/创建/更新代码中需要 ID 而不是对象,或者需要同时具有相同源的可写字段,这可能会更好,但我不会再推荐。整行是:

    item_id = serializers.IntegerField(write_only=True)
    

    【讨论】:

    • 这可能是一条评论。您可以添加更多详细信息以使其成为一个很好的答案。
    • 不能发表评论,因为我没有 50 声望。我会添加它为什么起作用,但除此之外,实际上没有更多需要说明的细节。
    • 听起来不错。否则,帖子可能会被标记为低质量答案,并且可能会被推送到审核队列。
    • IntegerSerializer 是否已弃用?在文档中看不到它。
    • 我同意注 2。这让我抓狂。
    【解决方案2】:

    我创建了一个 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
       )
    

    希望对你有帮助。

    【讨论】:

      【解决方案3】:

      如果您使用的是 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
      

      有两点需要注意:可浏览的 web api 仍然会认为 writes 是嵌套的。我不知道如何解决这个问题,但我只使用 Web 界面进行调试,所以没什么大不了的。此外,在您编写后返回的项目将具有平面项目而不是嵌套项目。要解决此问题,您可以添加此代码以强制读取始终使用 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?

      【讨论】:

      • 这条评论解决了一个我花了几个小时试图解决的问题。谢谢!
      • 这是我们不应该盲目遵循“不要重复自己”理念的少数情况之一。最好重复已知具有不同关注点并将单独发展的代码行(另一个序列化程序类)。通过这样做,我们避免了将代码保存在一个地方的混乱解决方法。
      【解决方案4】:

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

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

      【讨论】:

      • 如果以后有人遇到这个问题,请注意 DRF 3.0 中的 Field API 发生了变化。 from_native(self, ob) 现在是 to_representation(self, obj)to_native(self, data) 现在是 to_internal_value(self, data)
      • 另外,serializers.WritableField 已被删除以支持 serializers.Field
      猜你喜欢
      • 2018-06-04
      • 2020-06-20
      • 1970-01-01
      • 2014-10-03
      • 1970-01-01
      • 1970-01-01
      • 2017-12-03
      • 1970-01-01
      • 2015-03-20
      相关资源
      最近更新 更多