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
# }

Похоже, что у вас две проблемы:

  1. Неправильное возвращаемое значение для метода get_queryset
  2. Передача некоторых дополнительных данных в сериализатор

Что касается первого, то вам просто не следует переопределять этот метод или возвращать кверисет. В зависимости от того, как вычисляются дополнительные данные, могут быть компромиссы, такие как аннотирование значения на queryset и указание его в сериализаторе списка полей.

Для второго варианта - если аннотация не подходит, а вы хотите вычислять значение единицы, возможным решением является добавление дополнительных данных в сериализатор context и использование их в качестве возвращаемого значения поля метода serializer field. В этом случае дополнительные данные будут размещены на одном уровне с данными записей модели (дополнительное поле для каждой записи модели):

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

Или вы можете продолжить использовать ваш подход с вложенными отношениями, переопределив listcreate, если необходимо) - просто добавьте дополнительные данные к проверенным сериализатором данным, так что результат будет выглядеть следующим образом:

{
    '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 на вашем представлении. Но у вас есть несколько проблем в вашем коде:

  1. get_queryset используется только для возврата экземпляра models.QuerySet, который используется для определения того, над какими объектами модели разрешено производить какие-либо действия.
  2. .
  3. Это продолжение первого пункта, get_queryset не используется для передачи данных сериализатору, это делается во время инициализации Serializer.
  4. Вы используете ModelSerializer, но не указываете атрибут fields на классе Meta, что больше не разрешено с drf version 3.3, вы должны определить либо атрибут fields, либо атрибут exclude.

Что касается того, как передать эти дополнительные данные сериализатору, вы можете сделать это несколькими способами:

  1. определите 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

Обратите внимание, что это поле предназначено только для чтения.

  1. Вы можете ввести вычисленное значение в конечный результат во время валидации, в результате чего ваш сериализатор будет выглядеть следующим образом:
    class ParentSerializer(serializers.Serializer):
        main_data = MainDataSerializer(many=True)

        def validate(self, attrs):
            attrs["extra_data"] = some_calculated_value()
            return attrs
  1. Вы также можете сделать это, переопределив функцию list на View и изменив данные, передаваемые сериализатору.

Я лично предпочитаю вариант номер 1, так как SerializerMethodField соответствует цели, а функция validate должна использоваться только для проверки, чтобы соблюсти концепцию единой ответственности.

Вернуться на верх