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 мог правильно отобразить схему. При этом аннотация не соответствует реальному типу возвращаемых данных.

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs