Django-rest-framework переопределяет get_queryset и передает дополнительные данные в сериализатор вместе с queryset
Это игрушечный пример проблемы, с которой я столкнулся. По какой-то причине я не могу передать ожидаемые данные моему сериализатору, что приводит к возникновению следующей ошибки.
AttributeError at /my-end-point/
Получена ошибка AttributeError при попытке получить значение для поля main_data на сериализаторе ParentSerializer.
Поле сериализатора может быть названо неверно и не совпадать с атрибутом или ключом экземпляра str.
Оригинальный текст исключения был: '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
# }
Похоже, что у вас две проблемы:
- Неправильное возвращаемое значение для метода
get_queryset - Передача некоторых дополнительных данных в сериализатор
Что касается первого, то вам просто не следует переопределять этот метод или возвращать кверисет. В зависимости от того, как вычисляются дополнительные данные, могут быть компромиссы, такие как аннотирование значения на queryset и указание его в сериализаторе списка полей.
Для второго варианта - если аннотация не подходит, а вы хотите вычислять значение единицы, возможным решением является добавление дополнительных данных в сериализатор context и использование их в качестве возвращаемого значения поля метода serializer field. В этом случае дополнительные данные будут размещены на одном уровне с данными записей модели (дополнительное поле для каждой записи модели):
{
'model_field_1': 'value',
...
'extra_field': 'value',
}
Или вы можете продолжить использовать ваш подход с вложенными отношениями, переопределив list (и create, если необходимо) - просто добавьте дополнительные данные к проверенным сериализатором данным, так что результат будет выглядеть следующим образом:
{
'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' )
Я думаю, что то, чего вы хотите достичь, это передать некоторые дополнительные данные в ParentSerializer, которые будут возвращены list action на вашем представлении. Но у вас есть несколько проблем в вашем коде:
get_querysetиспользуется только для возврата экземпляраmodels.QuerySet, который используется для определения того, над какими объектами модели разрешено производить какие-либо действия. .
- Это продолжение первого пункта,
get_querysetне используется для передачи данных сериализатору, это делается во время инициализацииSerializer. - Вы используете
ModelSerializer, но не указываете атрибутfieldsна классеMeta, что больше не разрешено сdrf version 3.3, вы должны определить либо атрибутfields, либо атрибутexclude.
Что касается того, как передать эти дополнительные данные сериализатору, вы можете сделать это несколькими способами:
- определите SerializerMethodField, как предложил Charnel в своем ответе, который описан в drf docs здесь, ваш сериализатор будет выглядеть так:
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
Обратите внимание, что это поле предназначено только для чтения.
- Вы можете ввести вычисленное значение в конечный результат во время валидации, в результате чего ваш сериализатор будет выглядеть следующим образом:
class ParentSerializer(serializers.Serializer):
main_data = MainDataSerializer(many=True)
def validate(self, attrs):
attrs["extra_data"] = some_calculated_value()
return attrs
- Вы также можете сделать это, переопределив функцию
listна View и изменив данные, передаваемые сериализатору.
Я лично предпочитаю вариант номер 1, так как SerializerMethodField соответствует цели, а функция validate должна использоваться только для проверки, чтобы соблюсти концепцию единой ответственности.