Django rest framework recursive support (#110)
* add get_serializer_ref_name utility function * implement RecursiveFieldInspector * add option to allow non-existing reference in SchemaRef * add examples and README * Update changelog and docs
This commit is contained in:
committed by
Cristi Vîjdea
parent
d2dc09cb3c
commit
979ec84630
@@ -6,12 +6,13 @@ SWAGGER_DEFAULTS = {
|
||||
|
||||
'DEFAULT_FIELD_INSPECTORS': [
|
||||
'drf_yasg.inspectors.CamelCaseJSONFilter',
|
||||
'drf_yasg.inspectors.RecursiveFieldInspector',
|
||||
'drf_yasg.inspectors.ReferencingSerializerInspector',
|
||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||
'drf_yasg.inspectors.ChoiceFieldInspector',
|
||||
'drf_yasg.inspectors.FileFieldInspector',
|
||||
'drf_yasg.inspectors.DictFieldInspector',
|
||||
'drf_yasg.inspectors.HiddenFieldInspector',
|
||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||
'drf_yasg.inspectors.SimpleFieldInspector',
|
||||
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
||||
],
|
||||
|
||||
@@ -4,8 +4,8 @@ from .base import (
|
||||
)
|
||||
from .field import (
|
||||
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
||||
InlineSerializerInspector, ReferencingSerializerInspector, RelatedFieldInspector, SimpleFieldInspector,
|
||||
StringDefaultFieldInspector
|
||||
InlineSerializerInspector, RecursiveFieldInspector, ReferencingSerializerInspector, RelatedFieldInspector,
|
||||
SimpleFieldInspector, StringDefaultFieldInspector
|
||||
)
|
||||
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
||||
from .view import SwaggerAutoSchema
|
||||
@@ -23,9 +23,9 @@ __all__ = [
|
||||
'CoreAPICompatInspector', 'DjangoRestResponsePagination',
|
||||
|
||||
# field inspectors
|
||||
'InlineSerializerInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector', 'SimpleFieldInspector',
|
||||
'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector', 'StringDefaultFieldInspector',
|
||||
'CamelCaseJSONFilter', 'HiddenFieldInspector',
|
||||
'InlineSerializerInspector', 'RecursiveFieldInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector',
|
||||
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector',
|
||||
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector',
|
||||
|
||||
# view inspectors
|
||||
'SwaggerAutoSchema',
|
||||
|
||||
@@ -10,7 +10,7 @@ from rest_framework.settings import api_settings as rest_framework_settings
|
||||
|
||||
from .. import openapi
|
||||
from ..errors import SwaggerGenerationError
|
||||
from ..utils import decimal_as_float, filter_none
|
||||
from ..utils import decimal_as_float, filter_none, get_serializer_ref_name
|
||||
from .base import FieldInspector, NotHandled, SerializerInspector
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -55,23 +55,12 @@ class InlineSerializerInspector(SerializerInspector):
|
||||
if swagger_object_type != openapi.Schema:
|
||||
raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)
|
||||
|
||||
serializer = field
|
||||
serializer_meta = getattr(serializer, 'Meta', None)
|
||||
serializer_name = type(serializer).__name__
|
||||
if hasattr(serializer_meta, 'ref_name'):
|
||||
ref_name = serializer_meta.ref_name
|
||||
elif serializer_name == 'NestedSerializer' and isinstance(serializer, serializers.ModelSerializer):
|
||||
logger.debug("Forcing inline output for ModelSerializer named 'NestedSerializer': " + str(serializer))
|
||||
ref_name = None
|
||||
else:
|
||||
ref_name = serializer_name
|
||||
if ref_name.endswith('Serializer'):
|
||||
ref_name = ref_name[:-len('Serializer')]
|
||||
ref_name = get_serializer_ref_name(field)
|
||||
|
||||
def make_schema_definition():
|
||||
properties = OrderedDict()
|
||||
required = []
|
||||
for property_name, child in serializer.fields.items():
|
||||
for property_name, child in field.fields.items():
|
||||
property_name = self.get_property_name(property_name)
|
||||
prop_kwargs = {
|
||||
'read_only': child.read_only or None
|
||||
@@ -531,3 +520,24 @@ else:
|
||||
return camelize_schema(result, self.components)
|
||||
|
||||
return result
|
||||
|
||||
try:
|
||||
from rest_framework_recursive.fields import RecursiveField
|
||||
except ImportError:
|
||||
class RecursiveFieldInspector(FieldInspector):
|
||||
"""Provides conversion for RecursiveField (https://github.com/heywbj/django-rest-framework-recursive)"""
|
||||
pass
|
||||
else:
|
||||
class RecursiveFieldInspector(FieldInspector):
|
||||
"""Provides conversion for RecursiveField (https://github.com/heywbj/django-rest-framework-recursive)"""
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||
if isinstance(field, RecursiveField) and swagger_object_type == openapi.Schema:
|
||||
assert use_references is True, "Can not create schema for RecursiveField when use_references is False"
|
||||
|
||||
ref_name = get_serializer_ref_name(field.proxied)
|
||||
assert ref_name is not None, "Can not create RecursiveField schema for inline ModelSerializer"
|
||||
|
||||
return openapi.SchemaRef(self.components.with_scope(openapi.SCHEMA_DEFINITIONS), ref_name,
|
||||
ignore_unresolved=True)
|
||||
|
||||
return NotHandled
|
||||
|
||||
@@ -466,7 +466,7 @@ class Schema(SwaggerDict):
|
||||
class _Ref(SwaggerDict):
|
||||
ref_name_re = re.compile(r"#/(?P<scope>.+)/(?P<name>[^/]+)$")
|
||||
|
||||
def __init__(self, resolver, name, scope, expected_type):
|
||||
def __init__(self, resolver, name, scope, expected_type, ignore_unresolved=False):
|
||||
"""Base class for all reference types. A reference object has only one property, ``$ref``, which must be a JSON
|
||||
reference to a valid object in the specification, e.g. ``#/definitions/Article`` to refer to an article model.
|
||||
|
||||
@@ -474,13 +474,15 @@ class _Ref(SwaggerDict):
|
||||
:param str name: referenced object name, e.g. "Article"
|
||||
:param str scope: reference scope, e.g. "definitions"
|
||||
:param type[.SwaggerDict] expected_type: the expected type that will be asserted on the object found in resolver
|
||||
:param bool ignore_unresolved: allow the reference to be not defined in resolver
|
||||
"""
|
||||
super(_Ref, self).__init__()
|
||||
assert not type(self) == _Ref, "do not instantiate _Ref directly"
|
||||
ref_name = "#/{scope}/{name}".format(scope=scope, name=name)
|
||||
obj = resolver.get(name, scope)
|
||||
assert isinstance(obj, expected_type), ref_name + " is a {actual}, not a {expected}" \
|
||||
.format(actual=type(obj).__name__, expected=expected_type.__name__)
|
||||
if not ignore_unresolved:
|
||||
obj = resolver.get(name, scope)
|
||||
assert isinstance(obj, expected_type), ref_name + " is a {actual}, not a {expected}" \
|
||||
.format(actual=type(obj).__name__, expected=expected_type.__name__)
|
||||
self.ref = ref_name
|
||||
|
||||
def resolve(self, resolver):
|
||||
@@ -502,14 +504,15 @@ class _Ref(SwaggerDict):
|
||||
|
||||
|
||||
class SchemaRef(_Ref):
|
||||
def __init__(self, resolver, schema_name):
|
||||
def __init__(self, resolver, schema_name, ignore_unresolved=False):
|
||||
"""Adds a reference to a named Schema defined in the ``#/definitions/`` object.
|
||||
|
||||
:param .ReferenceResolver resolver: component resolver which must contain the definition
|
||||
:param str schema_name: schema name
|
||||
:param bool ignore_unresolved: allow the reference to be not defined in resolver
|
||||
"""
|
||||
assert SCHEMA_DEFINITIONS in resolver.scopes
|
||||
super(SchemaRef, self).__init__(resolver, schema_name, SCHEMA_DEFINITIONS, Schema)
|
||||
super(SchemaRef, self).__init__(resolver, schema_name, SCHEMA_DEFINITIONS, Schema, ignore_unresolved)
|
||||
|
||||
|
||||
Schema.OR_REF = (Schema, SchemaRef)
|
||||
|
||||
@@ -295,3 +295,25 @@ def decimal_as_float(field):
|
||||
if isinstance(field, serializers.DecimalField) or isinstance(field, models.DecimalField):
|
||||
return not getattr(field, 'coerce_to_string', rest_framework_settings.COERCE_DECIMAL_TO_STRING)
|
||||
return False
|
||||
|
||||
|
||||
def get_serializer_ref_name(serializer):
|
||||
"""
|
||||
Get serializer's ref_name (or None for ModelSerializer if it is named 'NestedSerializer')
|
||||
|
||||
:param serializer: Serializer instance
|
||||
:return: Serializer's ref_name or None for inline serializer
|
||||
:rtype: str or None
|
||||
"""
|
||||
serializer_meta = getattr(serializer, 'Meta', None)
|
||||
serializer_name = type(serializer).__name__
|
||||
if hasattr(serializer_meta, 'ref_name'):
|
||||
ref_name = serializer_meta.ref_name
|
||||
elif serializer_name == 'NestedSerializer' and isinstance(serializer, serializers.ModelSerializer):
|
||||
logger.debug("Forcing inline output for ModelSerializer named 'NestedSerializer': " + str(serializer))
|
||||
ref_name = None
|
||||
else:
|
||||
ref_name = serializer_name
|
||||
if ref_name.endswith('Serializer'):
|
||||
ref_name = ref_name[:-len('Serializer')]
|
||||
return ref_name
|
||||
|
||||
Reference in New Issue
Block a user