restdjango-modelsserializationdjango-rest-framework

DRF API method with the possibility of dynamically transfer/recieve model fields


I need to write an api method in the Django Rest Framework that has the ability to get model fields and use them to get model values

I have written the following code for api method:

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)

When transmitting data in the form of {"fields":"some_field"}, I get an error:{ "error": "Field.__init__() got an unexpected keyword argument 'fields'" }\

My serializer:

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)

` What should I do to make this method work ?


Solution

  • it's easy to organize. You are asking about a serializer with dynamic fields. In DRF you can find documentation about this here: https://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields

    But this is wrong solution, and I cover it in my talks, e.g. here: https://youtu.be/IycQNgzFKwE?si=VV1Avx2KvsHxELvC&t=163 (about 2:45sec after the start).

    For your case:

    class RadioStationSerializer(serializers.ModelSerializer):
        add_points = serializers.PrimaryKeyRelatedField(queryset=Locality.objects.all(), many=True, required=False)
    
        class Meta:
            model = RadioStation
            fields = '__all__'
    
        def __init__(self, *args, **kwargs):
            fields = kwargs.pop('fields')
            if fields:
                self.Meta = self.Meta()
                self.Meta.fields = fields 
            super().__init__(*args, **kwargs)
    

    in __init__ you override Meta for current serializer and change field attribute of serializer.Meta