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
相关产品推荐
相关产品推荐

