You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django Rest Framework如何实现仅读取嵌套、写入不嵌套的序列化?

在DRF中实现读写分离的序列化器(读取返回嵌套对象,写入仅传ID)

问题场景

假设有Foo模型与Bar模型通过外键关联:

  • 读取数据时,需要返回完整的Bar嵌套对象用于UI渲染,序列化器写法如下:
class ReadFooSerializer(serializers.ModelSerializer):
    bar = BarSerializer()

    class Meta:
        fields = (..., 'bar', ...)
        ...
  • 写入数据(AJAX提交)时,前端仅传Bar的ID,此时用上述序列化器会触发错误:

"Invalid data. Expected a dictionary, but got int."

如果为读写分别创建独立序列化器,在复杂场景下维护成本极高,有没有更简洁的方式实现读取时嵌套序列化,写入时仅接收ID?

此前尝试过继承基序列化器的方式,但维护Meta.fields十分繁琐,最终代码如下:

class BaseFooSerializer(serializers.ModelSerializer):
    ...

class ReadFooSerializer(BaseFooSerializer):
    bar = BarSerializer

    class Meta(BaseFooSerializer.Meta):
        fields = BaseFooSerializer.Meta.fields + ['bar']


class WriteFooSerializer(BaseFooSerializer):
    bar_id = serializers.IntegerField()

    class Meta(BaseFooSerializer.Meta):
        fields = BaseFooSerializer.Meta.fields + ['bar_id']

    def to_representation(self, instance):
        return ReadFooSerializer(instance).data

解决方案

方案1:单序列化器重写to_representation

无需拆分两个序列化器,直接在一个类中处理读写逻辑:

class FooSerializer(serializers.ModelSerializer):
    bar = serializers.PrimaryKeyRelatedField(queryset=Bar.objects.all())

    class Meta:
        model = Foo
        fields = (..., 'bar', ...)

    def to_representation(self, instance):
        # 读取时将ID替换为完整的Bar嵌套对象
        representation = super().to_representation(instance)
        representation['bar'] = BarSerializer(instance.bar).data
        return representation
  • 写入阶段:bar字段接收ID,PrimaryKeyRelatedField自动验证ID合法性
  • 读取阶段:通过to_representation方法将ID转换为完整的Bar序列化数据

方案2:拆分读写字段+自定义处理逻辑

如果需要更灵活的权限控制,可拆分只读/只写字段:

class FooSerializer(serializers.ModelSerializer):
    bar = serializers.SerializerMethodField(read_only=True)
    bar_id = serializers.IntegerField(write_only=True)

    class Meta:
        model = Foo
        fields = (..., 'bar', 'bar_id', ...)

    def get_bar(self, instance):
        return BarSerializer(instance.bar).data

    def create(self, validated_data):
        # 取出bar_id并关联Bar对象
        bar_id = validated_data.pop('bar_id')
        bar = Bar.objects.get(id=bar_id)
        return Foo.objects.create(bar=bar, **validated_data)

    def update(self, instance, validated_data):
        bar_id = validated_data.pop('bar_id', None)
        if bar_id:
            instance.bar = Bar.objects.get(id=bar_id)
        instance.save()
        return super().update(instance, validated_data)
  • bar为只读字段,通过SerializerMethodField返回嵌套对象
  • bar_id为只写字段,接收前端传入的ID,在create/update方法中处理关联逻辑

方案3:改进继承方式,动态维护字段

如果偏好继承模式,可通过__init__动态添加字段,避免手动维护fields:

class BaseFooSerializer(serializers.ModelSerializer):
    class Meta:
        model = Foo
        fields = ('id', 'name', ...)  # 定义所有公共字段

class ReadFooSerializer(BaseFooSerializer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['bar'] = BarSerializer()

class WriteFooSerializer(BaseFooSerializer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['bar'] = serializers.PrimaryKeyRelatedField(queryset=Bar.objects.all())

    def to_representation(self, instance):
        # 复用读取序列化器的返回格式
        return ReadFooSerializer(instance).data
  • 基类定义公共字段,读写序列化器在初始化时动态添加bar字段的不同实现
  • 写入序列化器复用读取逻辑,保证返回格式统一

内容的提问来源于stack exchange,提问作者run_the_race

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 02:00:34