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
# }
CodePudding user response:
Look's like you have two problems:
- Incorrect return value for
get_querysetmethod - 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' )
CodePudding user response:
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:
get_querysetis only used to return an instance ofmodels.QuerySet, which is used to determine which model objects the view is allowed to perform any kind of actions on.- This is a follow up on the first point,
get_querysetis not used to pass data to the serializer, this is done during theSerializerinitialization. - You are using
ModelSerializerbut you're not specifying thefieldsattribute on theMetaclass which is not allowed anymore sincedrf version 3.3, you have to define either afieldsattribute or anexcludeattribute.
As for actually how to pass that extra data to the Serializer, you can do that in multiple ways:
- 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.
- 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
- You can also do it by overriding
listfunction 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.
