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
должна использоваться только для проверки, чтобы соблюсти концепцию единой ответственности.