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: def get_attribute(self, instance): self_ = cast(Field, self) if self_.source_attrs: value = super().get_attribute(instance) # type: ignore  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)