Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 120c4dd884 | |||
| 5bdce56bba | |||
| e761e2da50 | |||
| 9d330d9509 | |||
| c8f6d16891 | |||
| 857ef05b07 | |||
| 586b88d8f3 | |||
| 27cd5f7f2a | |||
| 90812f5c43 | |||
| a6ae8b0521 | |||
| 91530b64c6 | |||
| 6820d6bc70 | |||
| f50ad29a2b |
+2
-3
@@ -1,7 +1,8 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
testproj/db.sqlite3
|
testproj/db.sqlite3
|
||||||
testproj/staticfiles
|
testproj/staticfiles
|
||||||
.vscode/
|
\.pytest_cache/
|
||||||
|
docs/\.doctrees/
|
||||||
|
|
||||||
# Created by .ignore support plugin (hsz.mobi)
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
### Python template
|
### Python template
|
||||||
@@ -159,5 +160,3 @@ com_crashlytics_export_strings.xml
|
|||||||
crashlytics.properties
|
crashlytics.properties
|
||||||
crashlytics-build.properties
|
crashlytics-build.properties
|
||||||
fabric.properties
|
fabric.properties
|
||||||
|
|
||||||
\.pytest_cache/
|
|
||||||
|
|||||||
Generated
+1
@@ -18,6 +18,7 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/testproj" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/testproj" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.pytest_cache" />
|
<excludeFolder url="file://$MODULE_DIR$/.pytest_cache" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/docs/.doctrees" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/docs/_build" />
|
<excludeFolder url="file://$MODULE_DIR$/docs/_build" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/htmlcov" />
|
<excludeFolder url="file://$MODULE_DIR$/htmlcov" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/src/drf_yasg.egg-info" />
|
<excludeFolder url="file://$MODULE_DIR$/src/drf_yasg.egg-info" />
|
||||||
|
|||||||
+15
-1
@@ -3,6 +3,20 @@ Changelog
|
|||||||
#########
|
#########
|
||||||
|
|
||||||
|
|
||||||
|
**********
|
||||||
|
**1.10.2**
|
||||||
|
**********
|
||||||
|
|
||||||
|
*Release date: Sep 13, 2018*
|
||||||
|
|
||||||
|
- **ADDED:** added the ``DISPLAY_OPERATION_ID `` ``swagger-ui`` setting
|
||||||
|
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-alpha.38
|
||||||
|
- **IMPROVED:** Operation summary will now be parsed from multi-line view method docstrings (:issue:`205`)
|
||||||
|
- **IMPROVED:** ``pattern`` will now work on any field with a ``RegexValidator``
|
||||||
|
(would previously not appear on fields with special formats such as ``EmailField``)
|
||||||
|
- **FIXED:** fixed an issue with ``RelatedFieldInspector`` handling of nested serializers
|
||||||
|
- **FIXED:** fixed handling of ``reverse_lazy`` in URL settings (:issue:`209`)
|
||||||
|
|
||||||
**********
|
**********
|
||||||
**1.10.1**
|
**1.10.1**
|
||||||
**********
|
**********
|
||||||
@@ -19,7 +33,7 @@ Changelog
|
|||||||
- **FIXED:** fixed implementation of ``operation_summary`` and ``deprecated`` (:pr:`194`, :pr:`198`)
|
- **FIXED:** fixed implementation of ``operation_summary`` and ``deprecated`` (:pr:`194`, :pr:`198`)
|
||||||
- **FIXED:** fixed a bug related to nested ``typing`` hints (:pr:`195`)
|
- **FIXED:** fixed a bug related to nested ``typing`` hints (:pr:`195`)
|
||||||
- **FIXED:** removed dependency on ``future`` (:issue:`196`)
|
- **FIXED:** removed dependency on ``future`` (:issue:`196`)
|
||||||
- **FIXED:** removed exceptions loged for fields with ``default=None`` (:issue:`203`)
|
- **FIXED:** fixed exceptions logged for fields with ``default=None`` (:issue:`203`)
|
||||||
- **FIXED:** fixed ``request_body=no_body`` handling and related tests (:issue:`188`, :issue:`199`)
|
- **FIXED:** fixed ``request_body=no_body`` handling and related tests (:issue:`188`, :issue:`199`)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -155,7 +155,9 @@ texinfo_documents = [
|
|||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
autodoc_default_flags = ['private-members']
|
autodoc_default_options = {
|
||||||
|
'private-members': None
|
||||||
|
}
|
||||||
autodoc_member_order = 'bysource'
|
autodoc_member_order = 'bysource'
|
||||||
autoclass_content = 'both'
|
autoclass_content = 'both'
|
||||||
autodoc_mock_imports = []
|
autodoc_mock_imports = []
|
||||||
|
|||||||
@@ -332,6 +332,14 @@ This does not filter the operations from the display.
|
|||||||
**Default**: :python:`['get','put','post','delete','options','head','patch','trace']` |br|
|
**Default**: :python:`['get','put','post','delete','options','head','patch','trace']` |br|
|
||||||
*Maps to parameter*: ``supportedSubmitMethods``
|
*Maps to parameter*: ``supportedSubmitMethods``
|
||||||
|
|
||||||
|
DISPLAY_OPERATION_ID
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Controls the display of operationId in operations list.
|
||||||
|
|
||||||
|
**Default**: :python:`True`
|
||||||
|
*Maps to parameter*: ``displayOperationId``
|
||||||
|
|
||||||
**************
|
**************
|
||||||
REDOC_SETTINGS
|
REDOC_SETTINGS
|
||||||
**************
|
**************
|
||||||
@@ -356,6 +364,10 @@ URL which serves the UI; see :ref:`note on URL settings <url-settings>` above.
|
|||||||
LAZY_RENDERING
|
LAZY_RENDERING
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
If set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50).
|
||||||
|
In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing
|
||||||
|
progress bar on the top.
|
||||||
|
|
||||||
**NOTE:** this feature might be removed in future versions of ReDoc (see https://github.com/Rebilly/ReDoc/issues/475)
|
**NOTE:** this feature might be removed in future versions of ReDoc (see https://github.com/Rebilly/ReDoc/issues/475)
|
||||||
|
|
||||||
**Default**: :python:`False` |br|
|
**Default**: :python:`False` |br|
|
||||||
@@ -364,30 +376,42 @@ LAZY_RENDERING
|
|||||||
HIDE_HOSTNAME
|
HIDE_HOSTNAME
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
If set, the protocol and hostname is not shown in the operation definition.
|
||||||
|
|
||||||
**Default**: :python:`False` |br|
|
**Default**: :python:`False` |br|
|
||||||
*Maps to attribute*: ``hideHostname``
|
*Maps to attribute*: ``hideHostname``
|
||||||
|
|
||||||
EXPAND_RESPONSES
|
EXPAND_RESPONSES
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
Specify which responses to expand by default by response codes. Values should be passed as comma-separated list without
|
||||||
|
spaces e.g. expandResponses="200,201". Special value "all" expands all responses by default.
|
||||||
|
Be careful: this option can slow-down documentation rendering time.
|
||||||
|
|
||||||
**Default**: :python:`'all'` |br|
|
**Default**: :python:`'all'` |br|
|
||||||
*Maps to attribute*: ``expandResponses``
|
*Maps to attribute*: ``expandResponses``
|
||||||
|
|
||||||
PATH_IN_MIDDLE
|
PATH_IN_MIDDLE
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
Show path link and HTTP verb in the middle panel instead of the right one.
|
||||||
|
|
||||||
**Default**: :python:`False` |br|
|
**Default**: :python:`False` |br|
|
||||||
*Maps to attribute*: ``pathInMiddlePanel``
|
*Maps to attribute*: ``pathInMiddlePanel``
|
||||||
|
|
||||||
NATIVE_SCROLLBARS
|
NATIVE_SCROLLBARS
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
Use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs).
|
||||||
|
|
||||||
**Default**: :python:`False` |br|
|
**Default**: :python:`False` |br|
|
||||||
*Maps to attribute*: ``nativeScrollbars``
|
*Maps to attribute*: ``nativeScrollbars``
|
||||||
|
|
||||||
REQUIRED_PROPS_FIRST
|
REQUIRED_PROPS_FIRST
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
Show required properties first ordered in the same order as in required array.
|
||||||
|
|
||||||
**Default**: :python:`False` |br|
|
**Default**: :python:`False` |br|
|
||||||
*Maps to attribute*: ``requiredPropsFirst``
|
*Maps to attribute*: ``requiredPropsFirst``
|
||||||
|
|
||||||
|
|||||||
Generated
+26
-26
@@ -72,11 +72,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "3.1.0",
|
"version": "3.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
|
||||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
"integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "2.0.0"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"decko": {
|
"decko": {
|
||||||
@@ -272,18 +272,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mobx-react": {
|
"mobx-react": {
|
||||||
"version": "5.2.6",
|
"version": "5.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.2.8.tgz",
|
||||||
"integrity": "sha512-AGo3m/ICzN2SEbtNi8ZdF1ShDcAOnEKonZFc3mm8uuF2obDXg6rXqV5mtkr9ZHPYizGp51kT/vRg0TlGzok+CA==",
|
"integrity": "sha512-VAkeqkrIpdoy3VPPoqvxjdQmcTT6hi7i3TsZSwcKbSrPbSTuWc7cp1EOiX/zV1wPWEuoNAQ22bCrskQwvdYTJA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"hoist-non-react-statics": "^2.5.0",
|
"hoist-non-react-statics": "^2.5.0",
|
||||||
"react-lifecycles-compat": "^3.0.2"
|
"react-lifecycles-compat": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||||
},
|
},
|
||||||
"node-fetch": {
|
"node-fetch": {
|
||||||
"version": "1.7.3",
|
"version": "1.7.3",
|
||||||
@@ -321,9 +321,9 @@
|
|||||||
"integrity": "sha512-/2Sk/khljhdrsamjJYS5NjrH+GKEHEwh7zFSiYyxROyYKagkE4kSn2zDQDRTOMo8mpT2jikxx6yI1dG7lNP/hw=="
|
"integrity": "sha512-/2Sk/khljhdrsamjJYS5NjrH+GKEHEwh7zFSiYyxROyYKagkE4kSn2zDQDRTOMo8mpT2jikxx6yI1dG7lNP/hw=="
|
||||||
},
|
},
|
||||||
"polished": {
|
"polished": {
|
||||||
"version": "1.9.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/polished/-/polished-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/polished/-/polished-2.0.3.tgz",
|
||||||
"integrity": "sha512-4NmSD7fMFlM8roNxs7YXPv7UFRbYzb0gufR5zBxJLRzY54+zFsavxBo6zsQzP9ep6Hh3pC2pTyrpSTBEaB6IkQ=="
|
"integrity": "sha512-MMwthxXsmZZlZY/x79KX/8kXpSU0lq3ntnDmAdhpQGk9pU9EM/rQmUbsgIuuk2Cvl6zYSWjyBsDEU8sa1598xg=="
|
||||||
},
|
},
|
||||||
"postcss-value-parser": {
|
"postcss-value-parser": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
@@ -401,9 +401,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redoc": {
|
"redoc": {
|
||||||
"version": "2.0.0-alpha.37",
|
"version": "2.0.0-alpha.38",
|
||||||
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-alpha.37.tgz",
|
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-alpha.38.tgz",
|
||||||
"integrity": "sha512-yuT8sTT+oNzg8G9PfsUhu8Jhd9rWYJCTSQnbgaq9KzAFcJ2O0XfvlstkGXDK1zmtSSBBe5cy9Z/1XcQGDiGYug==",
|
"integrity": "sha512-4pT/cRJ/aKdLlCxjFOSWvkA9ZVG5b9dUfxIM+H5698hlRaF6rcu4qh29eGwEikSDw0unH0iNd6MHy9u0fAjRGw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"decko": "^1.2.0",
|
"decko": "^1.2.0",
|
||||||
@@ -418,15 +418,15 @@
|
|||||||
"mobx-react": "^5.2.5",
|
"mobx-react": "^5.2.5",
|
||||||
"openapi-sampler": "1.0.0-beta.14",
|
"openapi-sampler": "1.0.0-beta.14",
|
||||||
"perfect-scrollbar": "^1.4.0",
|
"perfect-scrollbar": "^1.4.0",
|
||||||
"polished": "^1.9.3",
|
"polished": "^2.0.2",
|
||||||
"prismjs": "^1.15.0",
|
"prismjs": "^1.15.0",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react-dropdown": "^1.6.1",
|
"react-dropdown": "^1.6.2",
|
||||||
"react-hot-loader": "^4.3.4",
|
"react-hot-loader": "^4.3.5",
|
||||||
"react-tabs": "^2.0.0",
|
"react-tabs": "^2.0.0",
|
||||||
"slugify": "^1.3.1",
|
"slugify": "^1.3.1",
|
||||||
"stickyfill": "^1.1.1",
|
"stickyfill": "^1.1.1",
|
||||||
"styled-components": "^3.4.2",
|
"styled-components": "^3.4.5",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -467,9 +467,9 @@
|
|||||||
"integrity": "sha1-OUE/7p0CXHSn5ZzuyyN4TMDxfwI="
|
"integrity": "sha1-OUE/7p0CXHSn5ZzuyyN4TMDxfwI="
|
||||||
},
|
},
|
||||||
"styled-components": {
|
"styled-components": {
|
||||||
"version": "3.4.5",
|
"version": "3.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-3.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-3.4.6.tgz",
|
||||||
"integrity": "sha512-/4c4/72+QAZ8LO+VIzTcitdjMcJleOln1laK9HZr166O+OmGpKZYt3+hRn0YhYPytwDGfx9J5c7WhU7Oys+nSw==",
|
"integrity": "sha512-HiEVVY93oH/PuekPuIE7ownn2wmO708rS8MM1yS20i0TCyFgPs3mbop8fJqKLRtC2/npVn2fO5Je6azaL6T2tg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"buffer": "^5.0.3",
|
"buffer": "^5.0.3",
|
||||||
"css-to-react-native": "^2.0.3",
|
"css-to-react-native": "^2.0.3",
|
||||||
@@ -522,9 +522,9 @@
|
|||||||
"integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA=="
|
"integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA=="
|
||||||
},
|
},
|
||||||
"whatwg-fetch": {
|
"whatwg-fetch": {
|
||||||
"version": "2.0.4",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
|
||||||
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
|
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "drf-yasg",
|
"name": "drf-yasg",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"redoc": "^2.0.0-alpha.37",
|
"redoc": "^2.0.0-alpha.38",
|
||||||
"swagger-ui-dist": "^3.18.2"
|
"swagger-ui-dist": "^3.18.2"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ SWAGGER_DEFAULTS = {
|
|||||||
'patch',
|
'patch',
|
||||||
'trace'
|
'trace'
|
||||||
],
|
],
|
||||||
|
'DISPLAY_OPERATION_ID': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
REDOC_DEFAULTS = {
|
REDOC_DEFAULTS = {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from rest_framework.settings import api_settings as rest_framework_settings
|
|||||||
from . import openapi
|
from . import openapi
|
||||||
from .app_settings import swagger_settings
|
from .app_settings import swagger_settings
|
||||||
from .errors import SwaggerGenerationError
|
from .errors import SwaggerGenerationError
|
||||||
from .inspectors.field import get_basic_type_info, get_queryset_field
|
from .inspectors.field import get_basic_type_info, get_queryset_field, get_queryset_from_view
|
||||||
from .openapi import ReferenceResolver
|
from .openapi import ReferenceResolver
|
||||||
from .utils import force_real_str, get_consumes, get_produces
|
from .utils import force_real_str, get_consumes, get_produces
|
||||||
|
|
||||||
@@ -424,7 +424,7 @@ class OpenAPISchemaGenerator(object):
|
|||||||
:rtype: list[openapi.Parameter]
|
:rtype: list[openapi.Parameter]
|
||||||
"""
|
"""
|
||||||
parameters = []
|
parameters = []
|
||||||
queryset = getattr(view_cls, 'queryset', None)
|
queryset = get_queryset_from_view(view_cls)
|
||||||
|
|
||||||
for variable in sorted(uritemplate.variables(path)):
|
for variable in sorted(uritemplate.variables(path)):
|
||||||
model, model_field = get_queryset_field(queryset, variable)
|
model, model_field = get_queryset_field(queryset, variable)
|
||||||
|
|||||||
@@ -165,6 +165,25 @@ def get_model_field(model, field_name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_queryset_from_view(view, serializer=None):
|
||||||
|
"""Try to get the queryset of the given view
|
||||||
|
|
||||||
|
:param view: the view instance or class
|
||||||
|
:param serializer: if given, will check that the view's get_serializer_class return matches this serialzier
|
||||||
|
:return: queryset or ``None``
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
queryset = getattr(view, 'queryset', None)
|
||||||
|
|
||||||
|
if queryset is not None and serializer is not None:
|
||||||
|
# make sure the view is actually using *this* serializer
|
||||||
|
assert type(serializer) == view.get_serializer_class()
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
except Exception: # pragma: no cover
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_parent_serializer(field):
|
def get_parent_serializer(field):
|
||||||
"""Get the nearest parent ``Serializer`` instance for the given field.
|
"""Get the nearest parent ``Serializer`` instance for the given field.
|
||||||
|
|
||||||
@@ -231,10 +250,18 @@ class RelatedFieldInspector(FieldInspector):
|
|||||||
else:
|
else:
|
||||||
# if the RelatedField has no queryset (e.g. read only), try to find the target model
|
# if the RelatedField has no queryset (e.g. read only), try to find the target model
|
||||||
# from the view queryset or ModelSerializer model, if present
|
# from the view queryset or ModelSerializer model, if present
|
||||||
view_queryset = getattr(self.view, 'queryset', None)
|
parent_serializer = get_parent_serializer(field)
|
||||||
serializer_meta = getattr(get_parent_serializer(field), 'Meta', None)
|
|
||||||
this_model = getattr(view_queryset, 'model', None) or getattr(serializer_meta, 'model', None)
|
serializer_meta = getattr(parent_serializer, 'Meta', None)
|
||||||
|
this_model = getattr(serializer_meta, 'model', None)
|
||||||
|
if not this_model:
|
||||||
|
view_queryset = get_queryset_from_view(self.view, parent_serializer)
|
||||||
|
this_model = getattr(view_queryset, 'model', None)
|
||||||
|
|
||||||
source = getattr(field, 'source', '') or field.field_name
|
source = getattr(field, 'source', '') or field.field_name
|
||||||
|
if not source and isinstance(field.parent, serializers.ManyRelatedField):
|
||||||
|
source = field.parent.field_name
|
||||||
|
|
||||||
model = get_related_model(this_model, source)
|
model = get_related_model(this_model, source)
|
||||||
model_field = get_model_field(model, target_field)
|
model_field = get_model_field(model, target_field)
|
||||||
|
|
||||||
@@ -256,6 +283,10 @@ def find_regex(regex_field):
|
|||||||
regex_validator = None
|
regex_validator = None
|
||||||
for validator in regex_field.validators:
|
for validator in regex_field.validators:
|
||||||
if isinstance(validator, validators.RegexValidator):
|
if isinstance(validator, validators.RegexValidator):
|
||||||
|
if isinstance(validator, validators.URLValidator) or validator == validators.validate_ipv4_address:
|
||||||
|
# skip the default url and IP regexes because they are complex and unhelpful
|
||||||
|
# validate_ipv4_address is a RegexValidator instance in Django 1.11
|
||||||
|
continue
|
||||||
if regex_validator is not None:
|
if regex_validator is not None:
|
||||||
# bail if multiple validators are found - no obvious way to choose
|
# bail if multiple validators are found - no obvious way to choose
|
||||||
return None # pragma: no cover
|
return None # pragma: no cover
|
||||||
@@ -401,7 +432,7 @@ def get_basic_type_info(field):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
pattern = None
|
pattern = None
|
||||||
if swagger_type == openapi.TYPE_STRING and format in (None, openapi.FORMAT_SLUG):
|
if swagger_type == openapi.TYPE_STRING:
|
||||||
pattern = find_regex(field)
|
pattern = find_regex(field)
|
||||||
|
|
||||||
limits = find_limits(field)
|
limits = find_limits(field)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ from rest_framework.status import is_success
|
|||||||
from .. import openapi
|
from .. import openapi
|
||||||
from ..errors import SwaggerGenerationError
|
from ..errors import SwaggerGenerationError
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
force_real_str, force_serializer_instance, get_consumes, get_produces, guess_response_status, is_list_view, no_body,
|
force_real_str, force_serializer_instance, get_consumes, get_produces, guess_response_status, is_list_view,
|
||||||
param_list_to_odict
|
merge_params, no_body, param_list_to_odict
|
||||||
)
|
)
|
||||||
from .base import ViewInspector
|
from .base import ViewInspector
|
||||||
|
|
||||||
@@ -162,21 +162,19 @@ class SwaggerAutoSchema(ViewInspector):
|
|||||||
:return: modified parameters
|
:return: modified parameters
|
||||||
:rtype: list[openapi.Parameter]
|
:rtype: list[openapi.Parameter]
|
||||||
"""
|
"""
|
||||||
parameters = param_list_to_odict(parameters)
|
|
||||||
manual_parameters = self.overrides.get('manual_parameters', None) or []
|
manual_parameters = self.overrides.get('manual_parameters', None) or []
|
||||||
|
|
||||||
if any(param.in_ == openapi.IN_BODY for param in manual_parameters): # pragma: no cover
|
if any(param.in_ == openapi.IN_BODY for param in manual_parameters): # pragma: no cover
|
||||||
raise SwaggerGenerationError("specify the body parameter as a Schema or Serializer in request_body")
|
raise SwaggerGenerationError("specify the body parameter as a Schema or Serializer in request_body")
|
||||||
if any(param.in_ == openapi.IN_FORM for param in manual_parameters): # pragma: no cover
|
if any(param.in_ == openapi.IN_FORM for param in manual_parameters): # pragma: no cover
|
||||||
if any(param.in_ == openapi.IN_BODY for param in parameters.values()):
|
if any(param.in_ == openapi.IN_BODY for param in parameters):
|
||||||
raise SwaggerGenerationError("cannot add form parameters when the request has a request body; "
|
raise SwaggerGenerationError("cannot add form parameters when the request has a request body; "
|
||||||
"did you forget to set an appropriate parser class on the view?")
|
"did you forget to set an appropriate parser class on the view?")
|
||||||
if self.method not in self.body_methods:
|
if self.method not in self.body_methods:
|
||||||
raise SwaggerGenerationError("form parameters can only be applied to (" + ','.join(self.body_methods) +
|
raise SwaggerGenerationError("form parameters can only be applied to (" + ','.join(self.body_methods) +
|
||||||
") HTTP methods")
|
") HTTP methods")
|
||||||
|
|
||||||
parameters.update(param_list_to_odict(manual_parameters))
|
return merge_params(parameters, manual_parameters)
|
||||||
return list(parameters.values())
|
|
||||||
|
|
||||||
def get_responses(self):
|
def get_responses(self):
|
||||||
"""Get the possible responses for this view as a swagger :class:`.Responses` object.
|
"""Get the possible responses for this view as a swagger :class:`.Responses` object.
|
||||||
@@ -323,16 +321,32 @@ class SwaggerAutoSchema(ViewInspector):
|
|||||||
operation_id = '_'.join(operation_keys)
|
operation_id = '_'.join(operation_keys)
|
||||||
return operation_id
|
return operation_id
|
||||||
|
|
||||||
|
def _extract_description_and_summary(self):
|
||||||
|
description = self.overrides.get('operation_description', None)
|
||||||
|
summary = self.overrides.get('operation_summary', None)
|
||||||
|
if description is None:
|
||||||
|
description = self._sch.get_description(self.path, self.method) or ''
|
||||||
|
description = description.strip().replace('\r', '')
|
||||||
|
|
||||||
|
if description and (summary is None):
|
||||||
|
# description from docstring ... do summary magic
|
||||||
|
# https://www.python.org/dev/peps/pep-0257/#multi-line-docstrings
|
||||||
|
summary_max_len = 120 # OpenAPI 2.0 spec says summary should be under 120 characters
|
||||||
|
sections = description.split('\n\n', 1)
|
||||||
|
if len(sections) == 2:
|
||||||
|
sections[0] = sections[0].strip()
|
||||||
|
if len(sections[0]) < summary_max_len:
|
||||||
|
summary, description = sections
|
||||||
|
|
||||||
|
return description, summary
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
"""Return an operation description determined as appropriate from the view's method and class docstrings.
|
"""Return an operation description determined as appropriate from the view's method and class docstrings.
|
||||||
|
|
||||||
:return: the operation description
|
:return: the operation description
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
description = self.overrides.get('operation_description', None)
|
return self._extract_description_and_summary()[0]
|
||||||
if description is None:
|
|
||||||
description = self._sch.get_description(self.path, self.method)
|
|
||||||
return description
|
|
||||||
|
|
||||||
def get_summary(self):
|
def get_summary(self):
|
||||||
"""Return a summary description for this operation.
|
"""Return a summary description for this operation.
|
||||||
@@ -340,7 +354,7 @@ class SwaggerAutoSchema(ViewInspector):
|
|||||||
:return: the summary
|
:return: the summary
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
return self.overrides.get('operation_summary', None)
|
return self._extract_description_and_summary()[1]
|
||||||
|
|
||||||
def get_security(self):
|
def get_security(self):
|
||||||
"""Return a list of security requirements for this operation.
|
"""Return a list of security requirements for this operation.
|
||||||
|
|||||||
@@ -291,6 +291,8 @@ class Paths(SwaggerDict):
|
|||||||
|
|
||||||
|
|
||||||
class PathItem(SwaggerDict):
|
class PathItem(SwaggerDict):
|
||||||
|
OPERATION_NAMES = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch']
|
||||||
|
|
||||||
def __init__(self, get=None, put=None, post=None, delete=None, options=None,
|
def __init__(self, get=None, put=None, post=None, delete=None, options=None,
|
||||||
head=None, patch=None, parameters=None, **extra):
|
head=None, patch=None, parameters=None, **extra):
|
||||||
"""Information about a single path
|
"""Information about a single path
|
||||||
@@ -315,6 +317,15 @@ class PathItem(SwaggerDict):
|
|||||||
self.parameters = filter_none(parameters)
|
self.parameters = filter_none(parameters)
|
||||||
self._insert_extras__()
|
self._insert_extras__()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def operations(self):
|
||||||
|
"""A list of all standard Operations on this PathItem object. See :attr:`.OPERATION_NAMES`.
|
||||||
|
|
||||||
|
:return: list of (method name, Operation) tuples
|
||||||
|
:rtype: list[tuple[str,Operation]]
|
||||||
|
"""
|
||||||
|
return [(k, v) for k, v in self.items() if k in PathItem.OPERATION_NAMES and v]
|
||||||
|
|
||||||
|
|
||||||
class Operation(SwaggerDict):
|
class Operation(SwaggerDict):
|
||||||
def __init__(self, operation_id, responses, parameters=None, consumes=None, produces=None, summary=None,
|
def __init__(self, operation_id, responses, parameters=None, consumes=None, produces=None, summary=None,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import warnings
|
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from django.shortcuts import render, resolve_url
|
from django.shortcuts import render, resolve_url
|
||||||
|
from django.utils.functional import Promise
|
||||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, TemplateHTMLRenderer
|
from rest_framework.renderers import BaseRenderer, JSONRenderer, TemplateHTMLRenderer
|
||||||
from rest_framework.utils import json
|
from rest_framework.utils import json
|
||||||
|
|
||||||
@@ -83,6 +82,9 @@ class _UIRenderer(BaseRenderer):
|
|||||||
renderer_context.update(self.get_auth_urls())
|
renderer_context.update(self.get_auth_urls())
|
||||||
|
|
||||||
def resolve_url(self, to):
|
def resolve_url(self, to):
|
||||||
|
if isinstance(to, Promise):
|
||||||
|
to = str(to)
|
||||||
|
|
||||||
if to is None:
|
if to is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -135,6 +137,7 @@ class SwaggerUIRenderer(_UIRenderer):
|
|||||||
'showCommonExtensions': swagger_settings.SHOW_COMMON_EXTENSIONS,
|
'showCommonExtensions': swagger_settings.SHOW_COMMON_EXTENSIONS,
|
||||||
'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
|
'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
|
||||||
'supportedSubmitMethods': swagger_settings.SUPPORTED_SUBMIT_METHODS,
|
'supportedSubmitMethods': swagger_settings.SUPPORTED_SUBMIT_METHODS,
|
||||||
|
'displayOperationId': swagger_settings.DISPLAY_OPERATION_ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
data = filter_none(data)
|
data = filter_none(data)
|
||||||
|
|||||||
+2
-2
File diff suppressed because one or more lines are too long
+6
-6
File diff suppressed because one or more lines are too long
@@ -32,7 +32,6 @@ function initSwaggerUi() {
|
|||||||
var swaggerConfig = {
|
var swaggerConfig = {
|
||||||
url: specURL,
|
url: specURL,
|
||||||
dom_id: '#swagger-ui',
|
dom_id: '#swagger-ui',
|
||||||
displayOperationId: true,
|
|
||||||
displayRequestDuration: true,
|
displayRequestDuration: true,
|
||||||
presets: [
|
presets: [
|
||||||
SwaggerUIBundle.presets.apis,
|
SwaggerUIBundle.presets.apis,
|
||||||
|
|||||||
@@ -254,6 +254,22 @@ def param_list_to_odict(parameters):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def merge_params(parameters, overrides):
|
||||||
|
"""Merge `overrides` into `parameters`. This is the same as appending `overrides` to `parameters`, but any element
|
||||||
|
of `parameters` whose ``(name, in_)`` tuple collides with an element in `overrides` is replaced by it.
|
||||||
|
|
||||||
|
Raises an ``AssertionError`` if either list contains duplicate parameters.
|
||||||
|
|
||||||
|
:param list[.Parameter] parameters: initial parameters
|
||||||
|
:param list[.Parameter] overrides: overriding parameters
|
||||||
|
:return: merged list
|
||||||
|
:rtype: list[.Parameter]
|
||||||
|
"""
|
||||||
|
parameters = param_list_to_odict(parameters)
|
||||||
|
parameters.update(param_list_to_odict(overrides))
|
||||||
|
return list(parameters.values())
|
||||||
|
|
||||||
|
|
||||||
def filter_none(obj):
|
def filter_none(obj):
|
||||||
"""Remove ``None`` values from tuples, lists or dictionaries. Return other objects as-is.
|
"""Remove ``None`` values from tuples, lists or dictionaries. Return other objects as-is.
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ class SnippetList(generics.ListCreateAPIView):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
|
"""summary from docstring
|
||||||
|
|
||||||
|
description body is here, summary is not included
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = [
|
||||||
@@ -91,7 +93,7 @@ REST_FRAMEWORK = {
|
|||||||
# drf-yasg
|
# drf-yasg
|
||||||
|
|
||||||
SWAGGER_SETTINGS = {
|
SWAGGER_SETTINGS = {
|
||||||
'LOGIN_URL': '/admin/login',
|
'LOGIN_URL': reverse_lazy('admin:login'),
|
||||||
'LOGOUT_URL': '/admin/logout',
|
'LOGOUT_URL': '/admin/logout',
|
||||||
|
|
||||||
'DEFAULT_INFO': 'testproj.urls.swagger_info',
|
'DEFAULT_INFO': 'testproj.urls.swagger_info',
|
||||||
|
|||||||
@@ -391,7 +391,8 @@ paths:
|
|||||||
- snippets
|
- snippets
|
||||||
delete:
|
delete:
|
||||||
operationId: snippetsDeleteBulk
|
operationId: snippetsDeleteBulk
|
||||||
description: SnippetList classdoc
|
summary: summary from docstring
|
||||||
|
description: description body is here, summary is not included
|
||||||
parameters:
|
parameters:
|
||||||
- name: data
|
- name: data
|
||||||
in: body
|
in: body
|
||||||
|
|||||||
Reference in New Issue
Block a user