简体   繁体   中英

django-rest-framework override get_queryset and pass additional data to serializer along queryset

This is a toy example of the problem that I am facing. For some reason, I am unable to pass the expected data to my serializer and that is raising the following error.

AttributeError at /my-end-point/

Got AttributeError when attempting to get a value for field main_data on serializer ParentSerializer . The serializer field might be named incorrectly and not match any attribute or key on the str instance. Original exception text was: 'str' object has no attribute 'main_data'.

class MainModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MainModel


class ParentSerializer(serializers.Serializer):
    
    main_data = MainModelSerializer(many=True)
    extra_data = serializers.FloatField()
    

class View(ListCreateAPIView):
    
    serializer = ParentSerializer
    
    def get_queryset(self):
        # extra_data would have some calculated value which would be a float
        extra_data = some_calculated_value()
        queryset = MainModel.objects.filter(some filters)
        
        return {
            'main_data': queryset,
            'extra_data': extra_data
        }
        
        
# expected data that is passed to the ParentSerializer

# {
#     'main_data': queryset,
#     'extra_data': extra_data
# }

Look's like you have two problems:

  1. Incorrect return value for get_queryset method
  2. Passing some extra data to serializer

As for the first one you simply should not override that method or return a queryset. Depending on how extra data is being computing there can be the tradeoffs like annotating value on queryset and specifying it in serializer fields list .

For the second one - if annotation is not an option and you'd like to calculate value one's, the possible solution is to add extra data to serializer context and use it as return value of serializer method field . In this case extra data will be placed on the same level with model records data (extra field for each model record):

{
    'model_field_1': 'value',
    ...
    'extra_field': 'value',
}

Or you can continue using your approach with nested relationships by override list (and create , if needed) - just add extra data to serializer validated data, so that the result will look like this:

{
    'extra_data': 'value',
    'main_data': [
        {'id': 1, 'field1': 'value', ...}
        ...
    ]
}
class View(ListCreateAPIView):
    
    serializer = ParentSerializer
    
    def list(self, request, *args, **kwargs):
        # extra_data would have some calculated value which would be a float
        extra_data = some_calculated_value()
        qs = self.get_queryset()
        data = self.get_serializer(qs, many=True).data
        data['extra_data'] = extra_data
        return Response({'data': data}, status=status.HTTP_200_OK, content_type = 'application/json' ) 

I think what you want to achieve is to pass some extra data to the ParentSerializer to be returned by the list action on your view.But you have multiple problems in your code:

  1. get_queryset is only used to return an instance of models.QuerySet , which is used to determine which model objects the view is allowed to perform any kind of actions on.
  2. This is a follow up on the first point, get_queryset is not used to pass data to the serializer, this is done during the Serializer initialization.
  3. You are using ModelSerializer but you're not specifying the fields attribute on the Meta class which is not allowed anymore since drf version 3.3 , you have to define either a fields attribute or an exclude attribute.

As for actually how to pass that extra data to the Serializer, you can do that in multiple ways:

  1. define a SerializerMethodField as proposed by Charnel in his answer which is described by the drf docs here , your serializer will look like this:
    class ParentSerializer(serializers.Serializer):
        main_data = MainDataSerializer(many=True)
        extra_data = serializers.SerializerMethodField(method_name="some_calculated_value")

        def some_calculated_value(self, obj):
            # calculate value here
            return value

Note that this field is a read only field.

  1. You can inject the calculated value to the end result during validation, this will make your serializer look like this:
    class ParentSerializer(serializers.Serializer):
        main_data = MainDataSerializer(many=True)

        def validate(self, attrs):
            attrs["extra_data"] = some_calculated_value()
            return attrs
  1. You can also do it by overriding list function on the View and change the data passed to the serializer.

I personally prefer option number 1 since SerializerMethodField fits the purpose, while the validate function should be used only for validation to abide by the single responsibility concept.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM