DEV Community

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

Posted on

DRF: SerializerMethodField vs DataSerializerField

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

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

    def __new__(
        cls, serializer: Type["SerializerType"], *args, method_name=None, **kwargs
    ) -> "SerializerType":
        class GetAttributeMixin(mixin_for(Field)):
            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 = class_name + now_as_timestamp_str()
        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)