Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ccdf646c0e | |||
| 04252ebec3 | |||
| 848fb69009 | |||
| 309a6eb8cd | |||
| a7fbba4967 | |||
| 66785b1ee8 | |||
| fb240f6a5b | |||
| 2c459024d1 | |||
| b62161f762 | |||
| 9ad55bac99 | |||
| 3d3b7899e5 | |||
| 6ea8711a1f |
+1
-1
@@ -134,7 +134,7 @@ In ``urls.py``:
|
|||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
|
url(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
|
||||||
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
|
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
|
||||||
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
|
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -3,6 +3,56 @@ Changelog
|
|||||||
#########
|
#########
|
||||||
|
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.5.1**
|
||||||
|
*********
|
||||||
|
|
||||||
|
*Release date: Mar 18, 2018*
|
||||||
|
|
||||||
|
- **IMPROVED:** updated ``swagger-ui`` to version 3.13.0
|
||||||
|
- **FIXED:** fixed a crash caused by ``serializers.OneToOneRel`` (:pr:`81`, thanks to :ghuser:`ko-pp`)
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.5.0**
|
||||||
|
*********
|
||||||
|
|
||||||
|
*Release date: Mar 12, 2018*
|
||||||
|
|
||||||
|
- **IMPROVED:** ``serializers.HiddenField`` are now hidden (:issue:`78`, :pr:`79`, thanks to :ghuser:`therefromhere`)
|
||||||
|
|
||||||
|
*NOTE:* in order for this to work, you will have to add the new ``drf_yasg.inspectors.HiddenFieldInspector`` to your
|
||||||
|
``DEFAULT_FIELD_INSPECTORS`` array if you changed it from the default value
|
||||||
|
|
||||||
|
- **IMPROVED:** type of model field is now detected for ``serializers.SlugRelatedField`` with ``read_only=True``
|
||||||
|
(:issue:`82`, :pr:`83`, thanks to :ghuser:`therefromhere`)
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.4.7**
|
||||||
|
*********
|
||||||
|
|
||||||
|
*Release date: Mar 05, 2018*
|
||||||
|
|
||||||
|
- **FIXED:** prevent crashes caused by attempting to delete object attributes which do not exist in the first place
|
||||||
|
(:issue:`76`)
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.4.6**
|
||||||
|
*********
|
||||||
|
|
||||||
|
*Release date: Mar 05, 2018*
|
||||||
|
|
||||||
|
- **IMPROVED:** updated ``swagger-ui`` to version 3.12.0
|
||||||
|
- **IMPROVED:** updated ``ReDoc`` to version 1.21.2
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.4.5**
|
||||||
|
*********
|
||||||
|
|
||||||
|
*Release date: Mar 05, 2018*
|
||||||
|
|
||||||
|
- **FIXED:** fixed an issue with modification of ``swagger_auto_schema`` arguments in-place during introspection, which
|
||||||
|
would sometimes cause an incomplete Swagger document to be generated after the first pass (:issue:`74`, :pr:`75`)
|
||||||
|
|
||||||
*********
|
*********
|
||||||
**1.4.4**
|
**1.4.4**
|
||||||
*********
|
*********
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ to this list.
|
|||||||
:class:`'drf_yasg.inspectors.ChoiceFieldInspector' <.inspectors.ChoiceFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.ChoiceFieldInspector' <.inspectors.ChoiceFieldInspector>`, |br| \
|
||||||
: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.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| \
|
||||||
``]``
|
``]``
|
||||||
|
|||||||
Generated
+10
-10
@@ -211,9 +211,9 @@
|
|||||||
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
|
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz",
|
||||||
"integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==",
|
"integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-util-is": "1.0.2",
|
"core-util-is": "1.0.2",
|
||||||
"inherits": "2.0.3",
|
"inherits": "2.0.3",
|
||||||
@@ -225,9 +225,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redoc": {
|
"redoc": {
|
||||||
"version": "1.21.0",
|
"version": "1.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/redoc/-/redoc-1.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/redoc/-/redoc-1.21.2.tgz",
|
||||||
"integrity": "sha1-RY8E7b7MqyVbQORhZA0eRG949N0=",
|
"integrity": "sha1-uMeLfDxtwFjmeZniwvYbqFjewaY=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-js": "2.5.3",
|
"core-js": "2.5.3",
|
||||||
"dropkickjs": "2.1.10",
|
"dropkickjs": "2.1.10",
|
||||||
@@ -301,7 +301,7 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"builtin-status-codes": "3.0.0",
|
"builtin-status-codes": "3.0.0",
|
||||||
"inherits": "2.0.3",
|
"inherits": "2.0.3",
|
||||||
"readable-stream": "2.3.4",
|
"readable-stream": "2.3.5",
|
||||||
"to-arraybuffer": "1.0.1",
|
"to-arraybuffer": "1.0.1",
|
||||||
"xtend": "4.0.1"
|
"xtend": "4.0.1"
|
||||||
}
|
}
|
||||||
@@ -315,9 +315,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"swagger-ui-dist": {
|
"swagger-ui-dist": {
|
||||||
"version": "3.10.0",
|
"version": "3.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.13.0.tgz",
|
||||||
"integrity": "sha1-ilrzP/ImPHFaFD9z8qjUfVOZQ+8="
|
"integrity": "sha1-NPpclKDx3EAWVBmIEV38rhOKirE="
|
||||||
},
|
},
|
||||||
"tiny-emitter": {
|
"tiny-emitter": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "drf-yasg",
|
"name": "drf-yasg",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"redoc": "^1.21.0",
|
"redoc": "^1.21.2",
|
||||||
"swagger-ui-dist": "^3.10.0"
|
"swagger-ui-dist": "^3.13.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ SWAGGER_DEFAULTS = {
|
|||||||
'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.SimpleFieldInspector',
|
'drf_yasg.inspectors.SimpleFieldInspector',
|
||||||
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
@@ -355,9 +356,9 @@ class OpenAPISchemaGenerator(object):
|
|||||||
|
|
||||||
view_inspector = view_inspector_cls(view, path, method, components, request, overrides)
|
view_inspector = view_inspector_cls(view, path, method, components, request, overrides)
|
||||||
operation = view_inspector.get_operation(operation_keys)
|
operation = view_inspector.get_operation(operation_keys)
|
||||||
if set(operation.consumes) == set(self.consumes):
|
if 'consumes' in operation and set(operation.consumes) == set(self.consumes):
|
||||||
del operation.consumes
|
del operation.consumes
|
||||||
if set(operation.produces) == set(self.produces):
|
if 'produces' in operation and set(operation.produces) == set(self.produces):
|
||||||
del operation.produces
|
del operation.produces
|
||||||
return operation
|
return operation
|
||||||
|
|
||||||
@@ -388,7 +389,7 @@ class OpenAPISchemaGenerator(object):
|
|||||||
if method in overrides:
|
if method in overrides:
|
||||||
overrides = overrides[method]
|
overrides = overrides[method]
|
||||||
|
|
||||||
return overrides
|
return copy.deepcopy(overrides)
|
||||||
|
|
||||||
def get_path_parameters(self, path, view_cls):
|
def get_path_parameters(self, path, view_cls):
|
||||||
"""Return a list of Parameter instances corresponding to any templated path variables.
|
"""Return a list of Parameter instances corresponding to any templated path variables.
|
||||||
@@ -408,9 +409,9 @@ class OpenAPISchemaGenerator(object):
|
|||||||
if getattr(view_cls, 'lookup_field', None) == variable and attrs['type'] == openapi.TYPE_STRING:
|
if getattr(view_cls, 'lookup_field', None) == variable and attrs['type'] == openapi.TYPE_STRING:
|
||||||
attrs['pattern'] = getattr(view_cls, 'lookup_value_regex', attrs.get('pattern', None))
|
attrs['pattern'] = getattr(view_cls, 'lookup_value_regex', attrs.get('pattern', None))
|
||||||
|
|
||||||
if model_field and model_field.help_text:
|
if model_field and getattr(model_field, 'help_text', False):
|
||||||
description = force_text(model_field.help_text)
|
description = force_text(model_field.help_text)
|
||||||
elif model_field and model_field.primary_key:
|
elif model_field and getattr(model_field, 'primary_key', False):
|
||||||
description = get_pk_description(model, model_field)
|
description = get_pk_description(model, model_field)
|
||||||
else:
|
else:
|
||||||
description = None
|
description = None
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ from .base import (
|
|||||||
BaseInspector, FieldInspector, FilterInspector, NotHandled, PaginatorInspector, SerializerInspector, ViewInspector
|
BaseInspector, FieldInspector, FilterInspector, NotHandled, PaginatorInspector, SerializerInspector, ViewInspector
|
||||||
)
|
)
|
||||||
from .field import (
|
from .field import (
|
||||||
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, InlineSerializerInspector,
|
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
||||||
ReferencingSerializerInspector, RelatedFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector
|
InlineSerializerInspector, ReferencingSerializerInspector, RelatedFieldInspector, SimpleFieldInspector,
|
||||||
|
StringDefaultFieldInspector
|
||||||
)
|
)
|
||||||
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
||||||
from .view import SwaggerAutoSchema
|
from .view import SwaggerAutoSchema
|
||||||
@@ -24,7 +25,7 @@ __all__ = [
|
|||||||
# field inspectors
|
# field inspectors
|
||||||
'InlineSerializerInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector', 'SimpleFieldInspector',
|
'InlineSerializerInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector', 'SimpleFieldInspector',
|
||||||
'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector', 'StringDefaultFieldInspector',
|
'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector', 'StringDefaultFieldInspector',
|
||||||
'CamelCaseJSONFilter',
|
'CamelCaseJSONFilter', 'HiddenFieldInspector',
|
||||||
|
|
||||||
# view inspectors
|
# view inspectors
|
||||||
'SwaggerAutoSchema',
|
'SwaggerAutoSchema',
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ class InlineSerializerInspector(SerializerInspector):
|
|||||||
properties=properties,
|
properties=properties,
|
||||||
required=required or None,
|
required=required or None,
|
||||||
)
|
)
|
||||||
if not ref_name:
|
if not ref_name and 'title' in result:
|
||||||
# on an inline model, the title is derived from the field name
|
# on an inline model, the title is derived from the field name
|
||||||
# but is visually displayed like the model named, which is confusing
|
# but is visually displayed like the model name, which is confusing
|
||||||
# it is better to just remove title from inline models
|
# it is better to just remove title from inline models
|
||||||
del result.title
|
del result.title
|
||||||
return result
|
return result
|
||||||
@@ -161,7 +161,11 @@ def get_related_model(model, source):
|
|||||||
:return: related model or ``None``
|
:return: related model or ``None``
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return getattr(model, source).rel.related_model
|
descriptor = getattr(model, source)
|
||||||
|
try:
|
||||||
|
return descriptor.rel.related_model
|
||||||
|
except Exception:
|
||||||
|
return descriptor.field.remote_field.model
|
||||||
except Exception: # pragma: no cover
|
except Exception: # pragma: no cover
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -465,11 +469,21 @@ class DictFieldInspector(FieldInspector):
|
|||||||
return NotHandled
|
return NotHandled
|
||||||
|
|
||||||
|
|
||||||
|
class HiddenFieldInspector(FieldInspector):
|
||||||
|
"""Hide ``HiddenField``."""
|
||||||
|
|
||||||
|
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||||
|
if isinstance(field, serializers.HiddenField):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return NotHandled
|
||||||
|
|
||||||
|
|
||||||
class StringDefaultFieldInspector(FieldInspector):
|
class StringDefaultFieldInspector(FieldInspector):
|
||||||
"""For otherwise unhandled fields, return them as plain :data:`.TYPE_STRING` objects."""
|
"""For otherwise unhandled fields, return them as plain :data:`.TYPE_STRING` objects."""
|
||||||
|
|
||||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs): # pragma: no cover
|
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs): # pragma: no cover
|
||||||
# TODO unhandled fields: TimeField HiddenField JSONField
|
# TODO unhandled fields: TimeField JSONField
|
||||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
||||||
return SwaggerType(type=openapi.TYPE_STRING)
|
return SwaggerType(type=openapi.TYPE_STRING)
|
||||||
|
|
||||||
|
|||||||
@@ -131,11 +131,3 @@ class ReDocRenderer(_UIRenderer):
|
|||||||
"""
|
"""
|
||||||
template = 'drf-yasg/redoc.html'
|
template = 'drf-yasg/redoc.html'
|
||||||
format = 'redoc'
|
format = 'redoc'
|
||||||
|
|
||||||
|
|
||||||
class ReDocAlphaRenderer(_UIRenderer):
|
|
||||||
"""Renders a ReDoc web interface for schema browisng.
|
|
||||||
Also requires :class:`.OpenAPIRenderer` as an available renderer on the same view.
|
|
||||||
"""
|
|
||||||
template = 'drf-yasg/redoc-alpha.html'
|
|
||||||
format = 'redoc'
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
@@ -45,11 +45,18 @@
|
|||||||
oauth2.auth.code = qp.code;
|
oauth2.auth.code = qp.code;
|
||||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||||
} else {
|
} else {
|
||||||
|
let oauthErrorMsg
|
||||||
|
if (qp.error) {
|
||||||
|
oauthErrorMsg = "["+qp.error+"]: " +
|
||||||
|
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||||
|
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||||
|
}
|
||||||
|
|
||||||
oauth2.errCb({
|
oauth2.errCb({
|
||||||
authId: oauth2.auth.name,
|
authId: oauth2.auth.name,
|
||||||
source: "auth",
|
source: "auth",
|
||||||
level: "error",
|
level: "error",
|
||||||
message: "Authorization failed: no accessCode received from the server"
|
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,10 +11,15 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
#: used to forcibly remove the body of a request via :func:`.swagger_auto_schema`
|
|
||||||
no_body = object()
|
|
||||||
|
|
||||||
unset = object()
|
class no_body(object):
|
||||||
|
"""Used as a sentinel value to forcibly remove the body of a request via :func:`.swagger_auto_schema`."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class unset(object):
|
||||||
|
"""Used as a sentinel value for function parameters not set by the caller where ``None`` would be a valid value."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_body=None, query_serializer=None,
|
def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_body=None, query_serializer=None,
|
||||||
@@ -33,7 +38,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
|||||||
:param .inspectors.SwaggerAutoSchema auto_schema: custom class to use for generating the Operation object;
|
:param .inspectors.SwaggerAutoSchema auto_schema: custom class to use for generating the Operation object;
|
||||||
this overrides both the class-level ``swagger_schema`` attribute and the ``DEFAULT_AUTO_SCHEMA_CLASS``
|
this overrides both the class-level ``swagger_schema`` attribute and the ``DEFAULT_AUTO_SCHEMA_CLASS``
|
||||||
setting, and can be set to ``None`` to prevent this operation from being generated
|
setting, and can be set to ``None`` to prevent this operation from being generated
|
||||||
:param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :data:`.no_body`. The value given here
|
:param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :class:`.no_body`. The value given here
|
||||||
will be used as the ``schema`` property of a :class:`.Parameter` with ``in: 'body'``.
|
will be used as the ``schema`` property of a :class:`.Parameter` with ``in: 'body'``.
|
||||||
|
|
||||||
A Schema or SchemaRef is not valid if this request consumes form-data, because ``form`` and ``body`` parameters
|
A Schema or SchemaRef is not valid if this request consumes form-data, because ``form`` and ``body`` parameters
|
||||||
|
|||||||
@@ -12,15 +12,12 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
from .app_settings import swagger_settings
|
from .app_settings import swagger_settings
|
||||||
from .generators import OpenAPISchemaGenerator
|
from .generators import OpenAPISchemaGenerator
|
||||||
from .renderers import (
|
from .renderers import OpenAPIRenderer, ReDocRenderer, SwaggerJSONRenderer, SwaggerUIRenderer, SwaggerYAMLRenderer
|
||||||
OpenAPIRenderer, ReDocAlphaRenderer, ReDocRenderer, SwaggerJSONRenderer, SwaggerUIRenderer, SwaggerYAMLRenderer
|
|
||||||
)
|
|
||||||
|
|
||||||
SPEC_RENDERERS = (SwaggerYAMLRenderer, SwaggerJSONRenderer, OpenAPIRenderer)
|
SPEC_RENDERERS = (SwaggerYAMLRenderer, SwaggerJSONRenderer, OpenAPIRenderer)
|
||||||
UI_RENDERERS = {
|
UI_RENDERERS = {
|
||||||
'swagger': (SwaggerUIRenderer, ReDocRenderer),
|
'swagger': (SwaggerUIRenderer, ReDocRenderer),
|
||||||
'redoc': (ReDocRenderer, SwaggerUIRenderer),
|
'redoc': (ReDocRenderer, SwaggerUIRenderer),
|
||||||
'redoc-alpha': (ReDocAlphaRenderer, ReDocRenderer, SwaggerUIRenderer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.11 on 2018-03-10 01:42
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('articles', '0002_article_article_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ArticleGroup',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||||
|
('title', models.CharField(help_text='title model help_text', max_length=255, unique=True)),
|
||||||
|
('slug', models.SlugField(blank=True, help_text='slug model help_text', unique=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='group',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, on_delete=django.db.models.deletion.PROTECT, related_name='articles_as_main', to='articles.ArticleGroup'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='original_group',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, on_delete=django.db.models.deletion.PROTECT, related_name='articles_as_original', to='articles.ArticleGroup'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
@@ -14,3 +16,14 @@ class Article(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
cover = models.ImageField(upload_to='article/original/', blank=True)
|
cover = models.ImageField(upload_to='article/original/', blank=True)
|
||||||
|
group = models.ForeignKey('ArticleGroup', related_name='articles_as_main', blank=True, default=None,
|
||||||
|
on_delete=models.PROTECT)
|
||||||
|
original_group = models.ForeignKey('ArticleGroup', related_name='articles_as_original', blank=True, default=None,
|
||||||
|
on_delete=models.PROTECT)
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleGroup(models.Model):
|
||||||
|
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||||
|
|
||||||
|
title = models.CharField(help_text="title model help_text", max_length=255, blank=False, unique=True)
|
||||||
|
slug = models.SlugField(help_text="slug model help_text", unique=True, blank=True)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from articles.models import Article
|
from articles.models import Article, ArticleGroup
|
||||||
|
|
||||||
|
|
||||||
class ArticleSerializer(serializers.ModelSerializer):
|
class ArticleSerializer(serializers.ModelSerializer):
|
||||||
@@ -12,11 +12,13 @@ class ArticleSerializer(serializers.ModelSerializer):
|
|||||||
)
|
)
|
||||||
uuid = serializers.UUIDField(help_text="should articles have UUIDs?", read_only=True)
|
uuid = serializers.UUIDField(help_text="should articles have UUIDs?", read_only=True)
|
||||||
cover_name = serializers.FileField(use_url=False, source='cover', read_only=True)
|
cover_name = serializers.FileField(use_url=False, source='cover', read_only=True)
|
||||||
|
group = serializers.SlugRelatedField(slug_field='uuid', queryset=ArticleGroup.objects.all())
|
||||||
|
original_group = serializers.SlugRelatedField(slug_field='uuid', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Article
|
model = Article
|
||||||
fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified',
|
fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified',
|
||||||
'references', 'uuid', 'cover', 'cover_name', 'article_type')
|
'references', 'uuid', 'cover', 'cover_name', 'article_type', 'group', 'original_group', )
|
||||||
read_only_fields = ('date_created', 'date_modified',
|
read_only_fields = ('date_created', 'date_modified',
|
||||||
'references', 'uuid', 'cover_name')
|
'references', 'uuid', 'cover_name')
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PeopleConfig(AppConfig):
|
||||||
|
name = 'people'
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.10 on 2018-03-18 16:22
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Identity',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('firstName', models.CharField(max_length=30, null=True)),
|
||||||
|
('lastName', models.CharField(max_length=30, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Person',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('Identity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='person', to='people.Identity')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.10 on 2018-03-18 17:04
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('people', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='person',
|
||||||
|
name='Identity',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='person', to='people.Identity'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Identity(models.Model):
|
||||||
|
firstName = models.CharField(max_length=30, null=True)
|
||||||
|
lastName = models.CharField(max_length=30, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Person(models.Model):
|
||||||
|
Identity = models.OneToOneField(Identity, related_name='person',
|
||||||
|
on_delete=models.PROTECT)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import Identity, Person
|
||||||
|
|
||||||
|
|
||||||
|
class IdentitySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Identity
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PersonSerializer(serializers.ModelSerializer):
|
||||||
|
identity = IdentitySerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Person
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
identity = Identity(**validated_data['identity'])
|
||||||
|
identity.save()
|
||||||
|
validated_data['identity'] = identity
|
||||||
|
return super().create(validated_data)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from .views import IdentityViewSet, PersonViewSet
|
||||||
|
|
||||||
|
person_list = PersonViewSet.as_view({
|
||||||
|
'get': 'list',
|
||||||
|
'post': 'create'
|
||||||
|
})
|
||||||
|
person_detail = PersonViewSet.as_view({
|
||||||
|
'get': 'retrieve',
|
||||||
|
'patch': 'partial_update',
|
||||||
|
'delete': 'destroy'
|
||||||
|
})
|
||||||
|
|
||||||
|
identity_detail = IdentityViewSet.as_view({
|
||||||
|
'get': 'retrieve',
|
||||||
|
'patch': 'partial_update',
|
||||||
|
})
|
||||||
|
|
||||||
|
urlpatterns = (
|
||||||
|
url(r'^$', person_list, name='people-list'),
|
||||||
|
url(r'^(?P<pk>[0-9]+)$', person_detail, name='person-detail'),
|
||||||
|
|
||||||
|
url(r'^(?P<person>[0-9]+)/identity$', identity_detail,
|
||||||
|
name='person-identity'),
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
from .models import Identity, Person
|
||||||
|
from .serializers import IdentitySerializer, PersonSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PersonViewSet(viewsets.ModelViewSet):
|
||||||
|
model = Person
|
||||||
|
queryset = Person.objects
|
||||||
|
serializer_class = PersonSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class IdentityViewSet(viewsets.ModelViewSet):
|
||||||
|
model = Identity
|
||||||
|
queryset = Identity.objects
|
||||||
|
serializer_class = IdentitySerializer
|
||||||
@@ -27,6 +27,7 @@ INSTALLED_APPS = [
|
|||||||
'users',
|
'users',
|
||||||
'articles',
|
'articles',
|
||||||
'todo',
|
'todo',
|
||||||
|
'people'
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ urlpatterns = [
|
|||||||
url(r'^swagger(?P<format>.json|.yaml)$', SchemaView.without_ui(cache_timeout=0), name='schema-json'),
|
url(r'^swagger(?P<format>.json|.yaml)$', SchemaView.without_ui(cache_timeout=0), name='schema-json'),
|
||||||
url(r'^swagger/$', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
url(r'^swagger/$', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||||
url(r'^redoc/$', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
url(r'^redoc/$', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||||
url(r'^redoc-alpha/$', SchemaView.with_ui('redoc-alpha', cache_timeout=0), name='schema-redoc-alpha'),
|
|
||||||
url(r'^cached/swagger(?P<format>.json|.yaml)$', SchemaView.without_ui(cache_timeout=None), name='cschema-json'),
|
url(r'^cached/swagger(?P<format>.json|.yaml)$', SchemaView.without_ui(cache_timeout=None), name='cschema-json'),
|
||||||
url(r'^cached/swagger/$', SchemaView.with_ui('swagger', cache_timeout=None), name='cschema-swagger-ui'),
|
url(r'^cached/swagger/$', SchemaView.with_ui('swagger', cache_timeout=None), name='cschema-swagger-ui'),
|
||||||
url(r'^cached/redoc/$', SchemaView.with_ui('redoc', cache_timeout=None), name='cschema-redoc'),
|
url(r'^cached/redoc/$', SchemaView.with_ui('redoc', cache_timeout=None), name='cschema-redoc'),
|
||||||
@@ -63,5 +63,6 @@ urlpatterns = [
|
|||||||
url(r'^articles/', include('articles.urls')),
|
url(r'^articles/', include('articles.urls')),
|
||||||
url(r'^users/', include('users.urls')),
|
url(r'^users/', include('users.urls')),
|
||||||
url(r'^todo/', include('todo.urls')),
|
url(r'^todo/', include('todo.urls')),
|
||||||
|
url(r'^people/', include('people.urls')),
|
||||||
url(r'^plain/', plain_view),
|
url(r'^plain/', plain_view),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from django.utils import timezone
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import Todo, TodoAnother, TodoYetAnother
|
from .models import Todo, TodoAnother, TodoYetAnother
|
||||||
@@ -6,7 +7,9 @@ from .models import Todo, TodoAnother, TodoYetAnother
|
|||||||
class TodoSerializer(serializers.ModelSerializer):
|
class TodoSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Todo
|
model = Todo
|
||||||
fields = ('title',)
|
fields = ('title', 'a_hidden_field',)
|
||||||
|
|
||||||
|
a_hidden_field = serializers.HiddenField(default=timezone.now)
|
||||||
|
|
||||||
|
|
||||||
class TodoAnotherSerializer(serializers.ModelSerializer):
|
class TodoAnotherSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -234,6 +234,113 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
format: slug
|
format: slug
|
||||||
pattern: '[a-z0-9]+(?:-[a-z0-9]+)'
|
pattern: '[a-z0-9]+(?:-[a-z0-9]+)'
|
||||||
|
/people/:
|
||||||
|
get:
|
||||||
|
operationId: people_list
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Person'
|
||||||
|
tags:
|
||||||
|
- people
|
||||||
|
post:
|
||||||
|
operationId: people_create
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Person'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Person'
|
||||||
|
tags:
|
||||||
|
- people
|
||||||
|
parameters: []
|
||||||
|
/people/{id}:
|
||||||
|
get:
|
||||||
|
operationId: people_read
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Person'
|
||||||
|
tags:
|
||||||
|
- people
|
||||||
|
patch:
|
||||||
|
operationId: people_partial_update
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Person'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Person'
|
||||||
|
tags:
|
||||||
|
- people
|
||||||
|
delete:
|
||||||
|
operationId: people_delete
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- people
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: A unique integer value identifying this person.
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
/people/{person}/identity:
|
||||||
|
get:
|
||||||
|
operationId: people_identity_read
|
||||||
|
description: ''
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Identity'
|
||||||
|
tags:
|
||||||
|
- people
|
||||||
|
patch:
|
||||||
|
operationId: people_identity_partial_update
|
||||||
|
description: ''
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Identity'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Identity'
|
||||||
|
tags:
|
||||||
|
- people
|
||||||
|
parameters:
|
||||||
|
- name: person
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
/plain/:
|
/plain/:
|
||||||
get:
|
get:
|
||||||
operationId: plain_list
|
operationId: plain_list
|
||||||
@@ -542,6 +649,7 @@ definitions:
|
|||||||
required:
|
required:
|
||||||
- title
|
- title
|
||||||
- body
|
- body
|
||||||
|
- group
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
title:
|
title:
|
||||||
@@ -601,6 +709,44 @@ definitions:
|
|||||||
- 3
|
- 3
|
||||||
- 7
|
- 7
|
||||||
- 8
|
- 8
|
||||||
|
group:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
original_group:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
Identity:
|
||||||
|
title: Identity
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
title: ID
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
firstName:
|
||||||
|
title: FirstName
|
||||||
|
type: string
|
||||||
|
maxLength: 30
|
||||||
|
lastName:
|
||||||
|
title: LastName
|
||||||
|
type: string
|
||||||
|
maxLength: 30
|
||||||
|
readOnly: true
|
||||||
|
Person:
|
||||||
|
required:
|
||||||
|
- Identity
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
title: ID
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
identity:
|
||||||
|
$ref: '#/definitions/Identity'
|
||||||
|
Identity:
|
||||||
|
title: Identity
|
||||||
|
type: integer
|
||||||
Project:
|
Project:
|
||||||
required:
|
required:
|
||||||
- projectName
|
- projectName
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ import json
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from rest_framework import routers, serializers, viewsets
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from drf_yasg import codecs, openapi
|
from drf_yasg import codecs, openapi
|
||||||
from drf_yasg.codecs import yaml_sane_load
|
from drf_yasg.codecs import yaml_sane_load
|
||||||
from drf_yasg.errors import SwaggerGenerationError
|
from drf_yasg.errors import SwaggerGenerationError
|
||||||
from drf_yasg.generators import OpenAPISchemaGenerator
|
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
|
||||||
|
|
||||||
def test_schema_is_valid(swagger, codec_yaml):
|
def test_schema_is_valid(swagger, codec_yaml):
|
||||||
@@ -79,3 +82,34 @@ def test_securiy_requirements(swagger_settings, mock_schema_request):
|
|||||||
|
|
||||||
swagger = generator.get_schema(mock_schema_request, public=True)
|
swagger = generator.get_schema(mock_schema_request, public=True)
|
||||||
assert swagger['security'] == []
|
assert swagger['security'] == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_replaced_serializer():
|
||||||
|
class DetailSerializer(serializers.Serializer):
|
||||||
|
detail = serializers.CharField()
|
||||||
|
|
||||||
|
class DetailViewSet(viewsets.ViewSet):
|
||||||
|
serializer_class = DetailSerializer
|
||||||
|
|
||||||
|
@swagger_auto_schema(responses={404: openapi.Response("Not found or Not accessible", DetailSerializer)})
|
||||||
|
def retrieve(self, request, pk=None):
|
||||||
|
serializer = DetailSerializer({'detail': None})
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r'details', DetailViewSet, base_name='details')
|
||||||
|
|
||||||
|
generator = OpenAPISchemaGenerator(
|
||||||
|
info=openapi.Info(title="Test generator", default_version="v1"),
|
||||||
|
version="v2",
|
||||||
|
url='',
|
||||||
|
patterns=router.urls
|
||||||
|
)
|
||||||
|
|
||||||
|
for _ in range(3):
|
||||||
|
swagger = generator.get_schema(None, True)
|
||||||
|
assert 'Detail' in swagger['definitions']
|
||||||
|
assert 'detail' in swagger['definitions']['Detail']['properties']
|
||||||
|
responses = swagger['paths']['/details/{id}/']['get']['responses']
|
||||||
|
assert '404' in responses
|
||||||
|
assert responses['404']['schema']['$ref'] == "#/definitions/Detail"
|
||||||
|
|||||||
Reference in New Issue
Block a user