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 docsopenapi3
parent
d2dc09cb3c
commit
979ec84630
|
|
@ -353,6 +353,12 @@ Integration with `djangorestframework-camel-case <https://github.com/vbabiy/djan
|
||||||
provided out of the box - if you have ``djangorestframework-camel-case`` installed and your ``APIView`` uses
|
provided out of the box - if you have ``djangorestframework-camel-case`` installed and your ``APIView`` uses
|
||||||
``CamelCaseJSONParser`` or ``CamelCaseJSONRenderer``, all property names will be converted to *camelCase* by default.
|
``CamelCaseJSONParser`` or ``CamelCaseJSONRenderer``, all property names will be converted to *camelCase* by default.
|
||||||
|
|
||||||
|
djangorestframework-recursive
|
||||||
|
===============================
|
||||||
|
|
||||||
|
Integration with `djangorestframework-recursive <https://github.com/heywbj/django-rest-framework-recursive>`_ is
|
||||||
|
provided out of the box - if you have ``djangorestframework-recursive`` installed.
|
||||||
|
|
||||||
.. |travis| image:: https://img.shields.io/travis/axnsan12/drf-yasg/master.svg
|
.. |travis| image:: https://img.shields.io/travis/axnsan12/drf-yasg/master.svg
|
||||||
:target: https://travis-ci.org/axnsan12/drf-yasg
|
:target: https://travis-ci.org/axnsan12/drf-yasg
|
||||||
:alt: Travis CI
|
:alt: Travis CI
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,20 @@ Changelog
|
||||||
#########
|
#########
|
||||||
|
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.7.0**
|
||||||
|
*********
|
||||||
|
|
||||||
|
*Release date: Apr 27, 2018*
|
||||||
|
|
||||||
|
- **ADDED:** added integration with `djangorestframework-recursive <https://github.com/heywbj/django-rest-framework-recursive>`_
|
||||||
|
(:issue:`109`, :pr:`110`, thanks to :ghuser:`rsichny`)
|
||||||
|
|
||||||
|
*NOTE:* in order for this to work, you will have to add the new ``drf_yasg.inspectors.RecursiveFieldInspector`` to
|
||||||
|
your ``DEFAULT_FIELD_INSPECTORS`` array if you changed it from the default value
|
||||||
|
|
||||||
|
- **FIXED:** ``SchemaRef`` now supports cyclical references via the ``ignore_unresolved`` argument
|
||||||
|
|
||||||
*********
|
*********
|
||||||
**1.6.2**
|
**1.6.2**
|
||||||
*********
|
*********
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ to this list.
|
||||||
:class:`'drf_yasg.inspectors.FileFieldInspector' <.inspectors.FileFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.FileFieldInspector' <.inspectors.FileFieldInspector>`, |br| \
|
||||||
:class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \
|
||||||
:class:`'drf_yasg.inspectors.HiddenFieldInspector' <.inspectors.HiddenFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.HiddenFieldInspector' <.inspectors.HiddenFieldInspector>`, |br| \
|
||||||
|
:class:`'drf_yasg.inspectors.RecursiveFieldInspector' <.inspectors.RecursiveFieldInspector>`, |br| \
|
||||||
:class:`'drf_yasg.inspectors.SimpleFieldInspector' <.inspectors.SimpleFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.SimpleFieldInspector' <.inspectors.SimpleFieldInspector>`, |br| \
|
||||||
:class:`'drf_yasg.inspectors.StringDefaultFieldInspector' <.inspectors.StringDefaultFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.StringDefaultFieldInspector' <.inspectors.StringDefaultFieldInspector>`, |br| \
|
||||||
``]``
|
``]``
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,6 @@ django-cors-headers>=2.1.0
|
||||||
django-filter>=1.1.0,<2.0; python_version == "2.7"
|
django-filter>=1.1.0,<2.0; python_version == "2.7"
|
||||||
django-filter>=1.1.0; python_version >= "3.4"
|
django-filter>=1.1.0; python_version >= "3.4"
|
||||||
djangorestframework-camel-case>=0.2.0
|
djangorestframework-camel-case>=0.2.0
|
||||||
|
djangorestframework-recursive>=0.1.2
|
||||||
dj-database-url>=0.4.2
|
dj-database-url>=0.4.2
|
||||||
user_agents>=1.1.0
|
user_agents>=1.1.0
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ SWAGGER_DEFAULTS = {
|
||||||
|
|
||||||
'DEFAULT_FIELD_INSPECTORS': [
|
'DEFAULT_FIELD_INSPECTORS': [
|
||||||
'drf_yasg.inspectors.CamelCaseJSONFilter',
|
'drf_yasg.inspectors.CamelCaseJSONFilter',
|
||||||
|
'drf_yasg.inspectors.RecursiveFieldInspector',
|
||||||
'drf_yasg.inspectors.ReferencingSerializerInspector',
|
'drf_yasg.inspectors.ReferencingSerializerInspector',
|
||||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
|
||||||
'drf_yasg.inspectors.ChoiceFieldInspector',
|
'drf_yasg.inspectors.ChoiceFieldInspector',
|
||||||
'drf_yasg.inspectors.FileFieldInspector',
|
'drf_yasg.inspectors.FileFieldInspector',
|
||||||
'drf_yasg.inspectors.DictFieldInspector',
|
'drf_yasg.inspectors.DictFieldInspector',
|
||||||
'drf_yasg.inspectors.HiddenFieldInspector',
|
'drf_yasg.inspectors.HiddenFieldInspector',
|
||||||
|
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||||
'drf_yasg.inspectors.SimpleFieldInspector',
|
'drf_yasg.inspectors.SimpleFieldInspector',
|
||||||
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ from .base import (
|
||||||
)
|
)
|
||||||
from .field import (
|
from .field import (
|
||||||
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
||||||
InlineSerializerInspector, ReferencingSerializerInspector, RelatedFieldInspector, SimpleFieldInspector,
|
InlineSerializerInspector, RecursiveFieldInspector, ReferencingSerializerInspector, RelatedFieldInspector,
|
||||||
StringDefaultFieldInspector
|
SimpleFieldInspector, StringDefaultFieldInspector
|
||||||
)
|
)
|
||||||
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
||||||
from .view import SwaggerAutoSchema
|
from .view import SwaggerAutoSchema
|
||||||
|
|
@ -23,9 +23,9 @@ __all__ = [
|
||||||
'CoreAPICompatInspector', 'DjangoRestResponsePagination',
|
'CoreAPICompatInspector', 'DjangoRestResponsePagination',
|
||||||
|
|
||||||
# field inspectors
|
# field inspectors
|
||||||
'InlineSerializerInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector', 'SimpleFieldInspector',
|
'InlineSerializerInspector', 'RecursiveFieldInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector',
|
||||||
'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector', 'StringDefaultFieldInspector',
|
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector',
|
||||||
'CamelCaseJSONFilter', 'HiddenFieldInspector',
|
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector',
|
||||||
|
|
||||||
# view inspectors
|
# view inspectors
|
||||||
'SwaggerAutoSchema',
|
'SwaggerAutoSchema',
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from rest_framework.settings import api_settings as rest_framework_settings
|
||||||
|
|
||||||
from .. import openapi
|
from .. import openapi
|
||||||
from ..errors import SwaggerGenerationError
|
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
|
from .base import FieldInspector, NotHandled, SerializerInspector
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -55,23 +55,12 @@ class InlineSerializerInspector(SerializerInspector):
|
||||||
if swagger_object_type != openapi.Schema:
|
if swagger_object_type != openapi.Schema:
|
||||||
raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)
|
raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)
|
||||||
|
|
||||||
serializer = field
|
ref_name = get_serializer_ref_name(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')]
|
|
||||||
|
|
||||||
def make_schema_definition():
|
def make_schema_definition():
|
||||||
properties = OrderedDict()
|
properties = OrderedDict()
|
||||||
required = []
|
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)
|
property_name = self.get_property_name(property_name)
|
||||||
prop_kwargs = {
|
prop_kwargs = {
|
||||||
'read_only': child.read_only or None
|
'read_only': child.read_only or None
|
||||||
|
|
@ -531,3 +520,24 @@ else:
|
||||||
return camelize_schema(result, self.components)
|
return camelize_schema(result, self.components)
|
||||||
|
|
||||||
return result
|
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):
|
class _Ref(SwaggerDict):
|
||||||
ref_name_re = re.compile(r"#/(?P<scope>.+)/(?P<name>[^/]+)$")
|
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
|
"""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.
|
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 name: referenced object name, e.g. "Article"
|
||||||
:param str scope: reference scope, e.g. "definitions"
|
: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 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__()
|
super(_Ref, self).__init__()
|
||||||
assert not type(self) == _Ref, "do not instantiate _Ref directly"
|
assert not type(self) == _Ref, "do not instantiate _Ref directly"
|
||||||
ref_name = "#/{scope}/{name}".format(scope=scope, name=name)
|
ref_name = "#/{scope}/{name}".format(scope=scope, name=name)
|
||||||
obj = resolver.get(name, scope)
|
if not ignore_unresolved:
|
||||||
assert isinstance(obj, expected_type), ref_name + " is a {actual}, not a {expected}" \
|
obj = resolver.get(name, scope)
|
||||||
.format(actual=type(obj).__name__, expected=expected_type.__name__)
|
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
|
self.ref = ref_name
|
||||||
|
|
||||||
def resolve(self, resolver):
|
def resolve(self, resolver):
|
||||||
|
|
@ -502,14 +504,15 @@ class _Ref(SwaggerDict):
|
||||||
|
|
||||||
|
|
||||||
class SchemaRef(_Ref):
|
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.
|
"""Adds a reference to a named Schema defined in the ``#/definitions/`` object.
|
||||||
|
|
||||||
:param .ReferenceResolver resolver: component resolver which must contain the definition
|
:param .ReferenceResolver resolver: component resolver which must contain the definition
|
||||||
:param str schema_name: schema name
|
: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
|
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)
|
Schema.OR_REF = (Schema, SchemaRef)
|
||||||
|
|
|
||||||
|
|
@ -295,3 +295,25 @@ def decimal_as_float(field):
|
||||||
if isinstance(field, serializers.DecimalField) or isinstance(field, models.DecimalField):
|
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 not getattr(field, 'coerce_to_string', rest_framework_settings.COERCE_DECIMAL_TO_STRING)
|
||||||
return False
|
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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 2.0.4 on 2018-04-26 13:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('todo', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TodoTree',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=50)),
|
||||||
|
('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='children', to='todo.TodoTree')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -13,3 +13,8 @@ class TodoAnother(models.Model):
|
||||||
class TodoYetAnother(models.Model):
|
class TodoYetAnother(models.Model):
|
||||||
todo = models.ForeignKey(TodoAnother, on_delete=models.CASCADE)
|
todo = models.ForeignKey(TodoAnother, on_delete=models.CASCADE)
|
||||||
title = models.CharField(max_length=50)
|
title = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
|
||||||
|
class TodoTree(models.Model):
|
||||||
|
parent = models.ForeignKey('self', on_delete=models.CASCADE, related_name='children', null=True)
|
||||||
|
title = models.CharField(max_length=50)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from rest_framework_recursive.fields import RecursiveField
|
||||||
|
|
||||||
from .models import Todo, TodoAnother, TodoYetAnother
|
from .models import Todo, TodoAnother, TodoTree, TodoYetAnother
|
||||||
|
|
||||||
|
|
||||||
class TodoSerializer(serializers.ModelSerializer):
|
class TodoSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -25,3 +26,22 @@ class TodoYetAnotherSerializer(serializers.ModelSerializer):
|
||||||
model = TodoYetAnother
|
model = TodoYetAnother
|
||||||
fields = ('title', 'todo')
|
fields = ('title', 'todo')
|
||||||
depth = 2
|
depth = 2
|
||||||
|
|
||||||
|
|
||||||
|
class TodoTreeSerializer(serializers.ModelSerializer):
|
||||||
|
children = serializers.ListField(child=RecursiveField(), source='children.all')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TodoTree
|
||||||
|
fields = ('id', 'title', 'children')
|
||||||
|
|
||||||
|
|
||||||
|
class TodoRecursiveSerializer(serializers.ModelSerializer):
|
||||||
|
parent = RecursiveField(read_only=True)
|
||||||
|
parent_id = serializers.PrimaryKeyRelatedField(queryset=TodoTree.objects.all(), pk_field=serializers.IntegerField(),
|
||||||
|
write_only=True, allow_null=True, required=False, default=None,
|
||||||
|
source='parent')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TodoTree
|
||||||
|
fields = ('id', 'title', 'parent', 'parent_id')
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ router = routers.DefaultRouter()
|
||||||
router.register(r'', views.TodoViewSet)
|
router.register(r'', views.TodoViewSet)
|
||||||
router.register(r'another', views.TodoAnotherViewSet)
|
router.register(r'another', views.TodoAnotherViewSet)
|
||||||
router.register(r'yetanother', views.TodoYetAnotherViewSet)
|
router.register(r'yetanother', views.TodoYetAnotherViewSet)
|
||||||
|
router.register(r'tree', views.TodoTreeView)
|
||||||
|
router.register(r'recursive', views.TodoRecursiveView)
|
||||||
|
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^(?P<todo_id>\d+)/yetanother/(?P<yetanother_id>\d+)/$',
|
url(r'^(?P<todo_id>\d+)/yetanother/(?P<yetanother_id>\d+)/$',
|
||||||
views.NestedTodoView.as_view(),),
|
views.NestedTodoView.as_view(), ),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.generics import RetrieveAPIView
|
from rest_framework.generics import RetrieveAPIView
|
||||||
|
|
||||||
from .models import Todo, TodoAnother, TodoYetAnother
|
from .models import Todo, TodoAnother, TodoTree, TodoYetAnother
|
||||||
from .serializer import TodoAnotherSerializer, TodoSerializer, TodoYetAnotherSerializer
|
from .serializer import (
|
||||||
|
TodoAnotherSerializer, TodoRecursiveSerializer, TodoSerializer, TodoTreeSerializer, TodoYetAnotherSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TodoViewSet(viewsets.ReadOnlyModelViewSet):
|
class TodoViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
|
@ -25,3 +27,13 @@ class TodoYetAnotherViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
|
||||||
class NestedTodoView(RetrieveAPIView):
|
class NestedTodoView(RetrieveAPIView):
|
||||||
serializer_class = TodoYetAnotherSerializer
|
serializer_class = TodoYetAnotherSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class TodoTreeView(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = TodoTree.objects.all()
|
||||||
|
serializer_class = TodoTreeSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class TodoRecursiveView(viewsets.ModelViewSet):
|
||||||
|
queryset = TodoTree.objects.all()
|
||||||
|
serializer_class = TodoRecursiveSerializer
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,129 @@ paths:
|
||||||
description: A unique integer value identifying this todo another.
|
description: A unique integer value identifying this todo another.
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
|
/todo/recursive/:
|
||||||
|
get:
|
||||||
|
operationId: todo_recursive_list
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/TodoRecursive'
|
||||||
|
tags:
|
||||||
|
- todo
|
||||||
|
post:
|
||||||
|
operationId: todo_recursive_create
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/TodoRecursive'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/TodoRecursive'
|
||||||
|
tags:
|
||||||
|
- todo
|
||||||
|
parameters: []
|
||||||
|
/todo/recursive/{id}/:
|
||||||
|
get:
|
||||||
|
operationId: todo_recursive_read
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/TodoRecursive'
|
||||||
|
tags:
|
||||||
|
- todo
|
||||||
|
put:
|
||||||
|
operationId: todo_recursive_update
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/TodoRecursive'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/TodoRecursive'
|
||||||
|
tags:
|
||||||
|
- todo
|
||||||
|
patch:
|
||||||
|
operationId: todo_recursive_partial_update
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/TodoRecursive'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/TodoRecursive'
|
||||||
|
tags:
|
||||||
|
- todo
|
||||||
|
delete:
|
||||||
|
operationId: todo_recursive_delete
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- todo
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: A unique integer value identifying this todo tree.
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
/todo/tree/:
|
||||||
|
get:
|
||||||
|
operationId: todo_tree_list
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/TodoTree'
|
||||||
|
tags:
|
||||||
|
- todo
|
||||||
|
parameters: []
|
||||||
|
/todo/tree/{id}/:
|
||||||
|
get:
|
||||||
|
operationId: todo_tree_read
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/TodoTree'
|
||||||
|
tags:
|
||||||
|
- todo
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: A unique integer value identifying this todo tree.
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
/todo/yetanother/:
|
/todo/yetanother/:
|
||||||
get:
|
get:
|
||||||
operationId: todo_yetanother_list
|
operationId: todo_yetanother_list
|
||||||
|
|
@ -1337,6 +1460,42 @@ definitions:
|
||||||
maxLength: 50
|
maxLength: 50
|
||||||
todo:
|
todo:
|
||||||
$ref: '#/definitions/Todo'
|
$ref: '#/definitions/Todo'
|
||||||
|
TodoRecursive:
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
title: ID
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
title:
|
||||||
|
title: Title
|
||||||
|
type: string
|
||||||
|
maxLength: 50
|
||||||
|
parent:
|
||||||
|
$ref: '#/definitions/TodoRecursive'
|
||||||
|
parent_id:
|
||||||
|
type: integer
|
||||||
|
title: Parent id
|
||||||
|
TodoTree:
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- children
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
title: ID
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
title:
|
||||||
|
title: Title
|
||||||
|
type: string
|
||||||
|
maxLength: 50
|
||||||
|
children:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/TodoTree'
|
||||||
TodoYetAnother:
|
TodoYetAnother:
|
||||||
required:
|
required:
|
||||||
- title
|
- title
|
||||||
|
|
|
||||||
6
tox.ini
6
tox.ini
|
|
@ -65,7 +65,7 @@ known_standard_library =
|
||||||
collections,copy,distutils,functools,inspect,io,json,logging,operator,os,pkg_resources,re,setuptools,sys,
|
collections,copy,distutils,functools,inspect,io,json,logging,operator,os,pkg_resources,re,setuptools,sys,
|
||||||
types,warnings
|
types,warnings
|
||||||
known_third_party =
|
known_third_party =
|
||||||
coreapi,coreschema,datadiff,dj_database_url,django,django_filters,djangorestframework_camel_case,flex,gunicorn,
|
coreapi,coreschema,datadiff,dj_database_url,django,django_filters,djangorestframework_camel_case,
|
||||||
inflection,pygments,pytest,rest_framework,ruamel,setuptools_scm,swagger_spec_validator,uritemplate,user_agents,
|
rest_framework_recursive,flex,gunicorn,inflection,pygments,pytest,rest_framework,ruamel,setuptools_scm,
|
||||||
whitenoise
|
swagger_spec_validator,uritemplate,user_agents,whitenoise
|
||||||
known_first_party = drf_yasg,testproj,articles,people,snippets,todo,users,urlconfs
|
known_first_party = drf_yasg,testproj,articles,people,snippets,todo,users,urlconfs
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue