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_queryset
method - 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_queryset
is 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_queryset
is not used to pass data to the serializer, this is done during theSerializer
initialization. - You are using
ModelSerializer
but you're not specifying thefields
attribute on theMeta
class which is not allowed anymore sincedrf version 3.3
, you have to define either afields
attribute or anexclude
attribute.
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
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.