DEV Community

Гимаев Наиль
Гимаев Наиль

Posted on • Edited on

DRF: SerializerMethodField vs DataSerializerField

Оставлю это здесь, как черновик.
drf-spectacular плохо дружит с SerializerMethodField. Поэтому я написал свой класс поля.

from typing import Type, cast, TypeVar
from uuid import uuid4

from rest_framework.fields import Field
from rest_framework.serializers import ListSerializer, Serializer

SerializerType = TypeVar("SerializerType", bound=Serializer)

class DataSerializerField(Field):
    """A serializer for calculated data"""

    def __new__(cls, serializer: Type['SerializerType'], *args, method_name=None, **kwargs) -> 'SerializerType':
        class GetAttributeMixin(Field if TYPE_CHECKING else object):
            def get_attribute(self, instance):
                if self.source_attrs:
                    value = super().get_attribute(instance)
                else:
                    default_method_name = f'get_{self.field_name}_data'
                    attr_name = method_name or default_method_name
                    method = getattr(self.parent, attr_name, None)
                    if not method:
                        raise AttributeError(f'Method {attr_name} not found in class {type(self.parent).__name__}')
                    value = method(instance)
                return value

        class_name = f'{serializer.__name__.replace("Serializer", "")}DataSerializer'
        list_serializer_class = type('DataListSerializer', (GetAttributeMixin, ListSerializer), {})
        parent_meta_class = getattr(serializer, 'Meta', object)
        ref_name = f'{class_name}_{uuid4().hex}'
        meta_class = type(
            'Meta',
            (parent_meta_class,),
            {'list_serializer_class': list_serializer_class, 'ref_name': ref_name},
        )
        data_serializer_class = type(class_name, (GetAttributeMixin, serializer), {'Meta': meta_class})
        kwargs.setdefault('source', '*')
        kwargs['read_only'] = True
        kwargs['required'] = False
        return data_serializer_class(**kwargs)
Enter fullscreen mode Exit fullscreen mode

Пример использования:

class ParentSerializer(Serializer):
  serializer_field_object = DataSerializerField(ChildSerializer)
  serializer_field_list = DataSerializerField(ChildSerializer, many=True)

  def get_serializer_field_object_data(parent_obj) -> ChildObject:
    return ChildObject()

  def get_serializer_field_list_data(parent_obj) -> list[ChildObject]:
    return [ChildObject(), ChildObject()]
Enter fullscreen mode Exit fullscreen mode

Аннотацию результатов методов можно не писать, она влияет только на работу линтеров.

Этот же код с SerializerMethodField выглядел бы так

class ParentSerializer(Serializer):
  serializer_field_object = SerializerMethodField()
  serializer_field_list = SerializerMethodField()

  def get_serializer_field_object(parent_obj) -> ChildSerializer:
    return ChildSerializer(ChildObject()).data

  def get_serializer_field_list(parent_obj) -> list[ChildSerializer]:
    return ChildSerializer([ChildObject(), ChildObject()]).data
Enter fullscreen mode Exit fullscreen mode

Обратите внимание на аннотацию результатов функций. Она нужна для того, чтобы spectacular-swagger мог правильно отобразить схему. При этом аннотация не соответствует реальному типу возвращаемых данных.

Top comments (0)