用Django Rest框架在swagger生成中重复使用选择定义

0 人关注

我有一个Django(Django Rest Framework)网络服务,使用 drf-yasg 来生成一个 swagger.json 的文件。在模型中,我有几个枚举/选择字段,在多个地方使用。默认情况下, drf-yasg 为每次出现的字段定义了内联。

Choices = serializers.ChoiceField(choices=['a', 'b', 'c'])
class SomeObject(serializers.Serializer):
    field_1 = Choices
    field_2 = Choices

在swagger文件中产生以下定义。

"definitions": { "SomeObject": { "required": [ "field_1", "field_2" ], "type": "object", "properties": { "field_1": { "title": "Field 1", "type": "string", "enum": [ "a", "b", "c" ] "field_2": { "title": "Field 1", "type": "string", "enum": [ "a", "b", "c" ]

这是个小问题,因为它使客户端编码工具将每个枚举生成为自己的类型,而不是重复使用定义。所以我想创建一个这样的swagger文件。

"definitions": { "Choices": { "title": "Field 1", "type": "string", "enum": [ "a", "b", "c" ] "SomeObject": { "required": [ "field_1", "field_2" ], "type": "object", "properties": { "field_1": { "$ref": "#/definitions/Choices" "field_2": { "$ref": "#/definitions/Choices"

是否有可能在Django Rest框架内启用这种行为?

python
python-3.x
django-rest-framework
drf-yasg
JAD
JAD
发布于 2019-06-20
1 个回答
JAD
JAD
发布于 2019-11-25
已采纳
0 人赞同

如果有人看到这个,需要一些指点的话。我最终实现了这一点,具体如下。

创建一个 ChoiceField 的子类,用来提示枚举应该被实现为引用,以及其他一些检查。对 str 的检查是为了确保序列化知道如何处理这些值。

from rest_framework import serializers
from enum import Enum
class ReferenceEnumField(serializers.ChoiceField):
  def __init__(self, enum_type, **kwargs):
    if not issubclass(enum_type, str):
      raise TypeError("enum_type should inherit from str in order to be json-serializable.")
    if not issubclass(enum_type, Enum):
      raise TypeError("enum_type should be an Enum")
    self.enum_name = enum_type.__name__
    super().__init__(choices=[enum.name for enum in enum_type], **kwargs)

然后是检查器,它可以被添加到装饰器中,如下所示。

from drf_yasg.inspectors.base import NotHandled
from drf_yasg.inspectors.field import ReferencingSerializerInspector
from drf_yasg import openapi
from drf_yasg.errors import SwaggerGenerationError
from .ReferenceEnumfield import ReferenceEnumField
class EnumAsReferenceInspector(ReferencingSerializerInspector):
  accepting_objects = True
  @classmethod
  def set_accepting_objects(cls, value):
    cls.accepting_objects = value
  def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
    SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
    if EnumAsReferenceInspector.accepting_objects and isinstance(field, ReferenceEnumField):
        # Avoid infinite recursion by setting the class to not accept objects to serialize.
        EnumAsReferenceInspector.set_accepting_objects(False)
        if swagger_object_type != openapi.Schema:
          raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)
        ref_name = field.enum_name
        def make_schema_definition(enum=field):
          return self.probe_field_inspectors(enum, ChildSwaggerType, use_references)
        if not ref_name or not use_references:
          return make_schema_definition()
        definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
        actual_schema = definitions.setdefault(ref_name, make_schema_definition)
        actual_schema._remove_read_only()
        return openapi.SchemaRef(definitions, ref_name)
      finally:
        EnumAsReferenceInspector.set_accepting_objects(True)