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

DRF动态获取模型字段API报错,求修复方案

问题描述

需要在DRF中实现一个可动态接收模型字段参数并返回对应字段值的API接口,编写的视图类如下:

class RadioStationDynamicFields(APIView):
    def post(self, request):
        field_names = request.data.get('fields')
        if not field_names:
            return Response({"error": "fields are not specified."}, status=status.HTTP_400_BAD_REQUEST)
    try:
        radiostations = RadioStation.objects.all()
        serializer = RadioStationSerializer(radiostations, many=True, fields=field_names)
        return Response(serializer.data, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

当传入{"fields":"some_field"}格式的请求数据时,出现错误:

{ "error": "Field.__init__() got an unexpected keyword argument 'fields'" }

对应的序列化器代码:

class AddPointsSerializer(serializers.ModelSerializer):
    class Meta:
        model = AddPoints
        fields = ('locality')

class RadioStationSerializer(serializers.ModelSerializer):
    add_points = serializers.PrimaryKeyRelatedField(
        queryset=Locality.objects.all(), many=True, required=False
    )

    class Meta:
        model = RadioStation
        fields = '__all__'


    def to_internal_value(self, data):
        if 'locality' in data:
            try:
                data['locality'] = Locality.objects.get(name=data['locality']).pk
        except Locality.DoesNotExist:
            raise serializers.ValidationError({'locality': 'Locality not found'})

        if 'radio_channel' in data:
            try:
                data['radio_channel'] = RadioChannel.objects.get(name=data['radio_channel']).pk
                except RadioChannel.DoesNotExist:
                    raise serializers.ValidationError({'radio_channel': 'RadioChannel not found'})

        if 'add_points' in data:
            add_points = []
            for point in data['add_points']:
                try:
                    add_points.append(Locality.objects.get(name=point).pk)
                except Locality.DoesNotExist:
                    raise serializers.ValidationError({'add_points': f'Locality {point} not found'})
            data['add_points'] = add_points

        return super().to_internal_value(data)

    def update(self, instance, validated_data):
        if 'add_points' in validated_data:
            add_points = validated_data.pop('add_points')
            instance.add_points.set(add_points)

        return super().update(instance, validated_data)

    def __init__(self, *args, **kwargs):
    
        super(RadioStationSerializer, self).__init__(*args, **kwargs)
        if self.context.get('request').method in ['GET']:
            self.fields['radio_channel'] = serializers.StringRelatedField()
            self.fields['locality'] = serializers.StringRelatedField()
            self.fields['add_points'] = serializers.StringRelatedField(many=True, read_only=True)
修复方案

错误原因是DRF的ModelSerializer默认不支持初始化时传入fields参数,需要在序列化器的__init__方法中手动处理动态字段逻辑,同时调整视图层的参数处理逻辑。

1. 修改视图类

  • 将传入的fields参数统一处理为列表格式(支持单个字段字符串或逗号分隔的多个字段)
  • 增加字段合法性验证,确保传入的字段是RadioStation模型实际存在的字段

修改后的视图代码:

class RadioStationDynamicFields(APIView):
    def post(self, request):
        field_names = request.data.get('fields')
        if not field_names:
            return Response({"error": "fields are not specified."}, status=status.HTTP_400_BAD_REQUEST)
        
        # 处理字段格式:字符串转列表,去除空格
        if isinstance(field_names, str):
            field_names = [field.strip() for field in field_names.split(',')]
        
        try:
            # 验证字段是否属于模型
            valid_model_fields = [field.name for field in RadioStation._meta.get_fields()]
            invalid_fields = set(field_names) - set(valid_model_fields)
            if invalid_fields:
                return Response(
                    {"error": f"Invalid fields: {', '.join(invalid_fields)}"}, 
                    status=status.HTTP_400_BAD_REQUEST
                )
            
            radiostations = RadioStation.objects.all()
            serializer = RadioStationSerializer(radiostations, many=True, fields=field_names)
            return Response(serializer.data, status=status.HTTP_200_OK)
        except Exception as e:
            return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

2. 修改序列化器

在RadioStationSerializer的__init__方法中,添加动态字段过滤逻辑,并保留原有GET请求的字段类型替换逻辑:

class RadioStationSerializer(serializers.ModelSerializer):
    add_points = serializers.PrimaryKeyRelatedField(
        queryset=Locality.objects.all(), many=True, required=False
    )

    class Meta:
        model = RadioStation
        fields = '__all__'

    # to_internal_value、update方法保持不变,此处省略

    def __init__(self, *args, **kwargs):
        # 弹出动态字段参数,避免传递给父类导致错误
        requested_fields = kwargs.pop('fields', None)
        super().__init__(*args, **kwargs)
        
        # 过滤保留指定字段
        if requested_fields is not None:
            allowed_fields = set(requested_fields)
            for field_name in list(self.fields.keys()):
                if field_name not in allowed_fields:
                    self.fields.pop(field_name)
        
        # 原有GET请求的字段类型替换逻辑,仅对保留的字段生效
        request = self.context.get('request')
        if request and request.method == 'GET':
            if 'radio_channel' in self.fields:
                self.fields['radio_channel'] = serializers.StringRelatedField()
            if 'locality' in self.fields:
                self.fields['locality'] = serializers.StringRelatedField()
            if 'add_points' in self.fields:
                self.fields['add_points'] = serializers.StringRelatedField(many=True, read_only=True)

关键说明

  • 序列化器初始化时先弹出fields参数,避免传递给父类的__init__方法引发参数错误
  • 动态过滤字段时,遍历并移除不在指定列表中的字段
  • GET请求的字段类型替换逻辑增加了字段存在性检查,避免因动态过滤后字段被移除而触发KeyError

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 21:51:01