Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 120c4dd884 | |||
| 5bdce56bba | |||
| e761e2da50 | |||
| 9d330d9509 | |||
| c8f6d16891 | |||
| 857ef05b07 | |||
| 586b88d8f3 | |||
| 27cd5f7f2a | |||
| 90812f5c43 | |||
| a6ae8b0521 | |||
| 91530b64c6 | |||
| 6820d6bc70 | |||
| f50ad29a2b | |||
| 27e38101a4 | |||
| 4bac38e67b | |||
| 10abf46597 | |||
| 42841e3092 | |||
| 0654aefe58 | |||
| 41b1ca4483 | |||
| b109d4c847 | |||
| 591c12f1a7 | |||
| 271918a678 | |||
| c5c40e31c4 | |||
| 3ac6c9ec3a | |||
| 5fa35d5b81 | |||
| f9b215deab | |||
| 0837873f55 | |||
| 02b8848912 | |||
| 6e39a58b2d | |||
| e146a9712e | |||
| ec7529399e | |||
| 824baf7e9d | |||
| dd50eb5e12 | |||
| 9b56e1a4c8 | |||
| 608abaf0e1 | |||
| 64e13a94da | |||
| 5051f91c97 | |||
| d1ae81a27e | |||
| 1ca401959c | |||
| cd4bd4a18e | |||
| 8a69f08c29 | |||
| e533b0827a | |||
| dc78dfdad8 | |||
| 23ebba4207 | |||
| afcba582b3 | |||
| 12f1d23048 | |||
| a5eb3dfa91 | |||
| 5cd642c9a0 | |||
| 3f2d2871f0 | |||
| 247c1a306a | |||
| 4ca634a45b | |||
| 37c00ab3fb | |||
| 65aac1da2c | |||
| 4c069138e8 | |||
| 748b5d3c2f | |||
| 1dd7cfe043 | |||
| 33eb9d381c | |||
| e80101d98c | |||
| 8de704d7ae | |||
| 165ac6c076 | |||
| 9eb0db466c | |||
| df0f43084f | |||
| 16b6ed7fd6 | |||
| 904895ba3c | |||
| 79adfc19be | |||
| 2965e08e39 | |||
| f2e05ee4c0 | |||
| bbc70a7e3d | |||
| ca43a7de0c | |||
| cc43bdf5cb | |||
| db86981dc1 | |||
| b37ce3227a | |||
| 8fbab88bda | |||
| 58b979f4ea | |||
| d9b6fbc1b8 | |||
| 20370d912e | |||
| 178390a9a0 |
+2
-3
@@ -1,7 +1,8 @@
|
||||
node_modules/
|
||||
testproj/db.sqlite3
|
||||
testproj/staticfiles
|
||||
.vscode/
|
||||
\.pytest_cache/
|
||||
docs/\.doctrees/
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Python template
|
||||
@@ -159,5 +160,3 @@ com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
\.pytest_cache/
|
||||
|
||||
Generated
+8
-3
@@ -16,10 +16,15 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/testproj" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pytest_cache" />
|
||||
<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$/htmlcov" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/src/drf_yasg.egg-info" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.6 (drf-yasg)" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3 (drf-yasg)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TemplatesService">
|
||||
@@ -31,7 +36,7 @@
|
||||
</option>
|
||||
</component>
|
||||
<component name="TestRunnerService">
|
||||
<option name="projectConfiguration" value="py.test" />
|
||||
<option name="PROJECT_TEST_RUNNER" value="py.test" />
|
||||
<option name="projectConfiguration" value="pytest" />
|
||||
<option name="PROJECT_TEST_RUNNER" value="pytest" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+1
-1
@@ -69,7 +69,7 @@
|
||||
<textMaps />
|
||||
</LinkMapSettings>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (drf-yasg)" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3 (drf-yasg)" project-jdk-type="Python SDK" />
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
|
||||
+21
-25
@@ -1,28 +1,28 @@
|
||||
language: python
|
||||
cache: pip
|
||||
|
||||
sudo: false
|
||||
python:
|
||||
- '3.4'
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
- '2.7'
|
||||
- '3.4'
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
|
||||
env:
|
||||
- DRF=3.7
|
||||
- DRF=3.8
|
||||
cache: pip
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
python: '2.7'
|
||||
env: DRF=3.7
|
||||
- # workaround for python 3.7 on travis https://github.com/travis-ci/travis-ci/issues/9815#issuecomment-401756442
|
||||
stage: test
|
||||
python: '3.7'
|
||||
dist: xenial
|
||||
sudo: required
|
||||
-
|
||||
python: '3.6'
|
||||
env: DRF=master
|
||||
-
|
||||
env: TOXENV=djmaster
|
||||
- # readthedocs uses python 3.5 for building
|
||||
python: '3.5'
|
||||
env: TOXENV=docs
|
||||
-
|
||||
python: '2.7'
|
||||
python: '3.6'
|
||||
env: TOXENV=lint
|
||||
|
||||
- stage: publish
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
allow_failures:
|
||||
- env: TOXENV=lint
|
||||
- env: DRF=master
|
||||
- env: TOXENV=djmaster
|
||||
|
||||
fast_finish: true
|
||||
|
||||
@@ -49,20 +49,16 @@ install:
|
||||
|
||||
before_script:
|
||||
- coverage erase
|
||||
- |
|
||||
[[ -z "$TOXENV" && -z "$PYPI_DEPLOY" ]] && REPORT_COVERAGE="yes" || REPORT_COVERAGE="no";
|
||||
echo "Reporting coverage: ${REPORT_COVERAGE}"
|
||||
- |
|
||||
[[ -z "$TOXENV" && ! -z "$DRF" && "$DRF" != "master" ]] && USE_DETOX="yes" || USE_DETOX="no";
|
||||
echo "Using detox: ${USE_DETOX}"
|
||||
|
||||
script:
|
||||
- 'if [[ "$USE_DETOX" == "yes" ]]; then detox; else tox; fi'
|
||||
- tox
|
||||
|
||||
after_success:
|
||||
- coverage combine
|
||||
- 'if [[ "$REPORT_COVERAGE" == "yes" ]]; then coverage report; fi'
|
||||
- 'if [[ "$REPORT_COVERAGE" == "yes" ]]; then codecov; fi'
|
||||
- |
|
||||
if [[ -z "$TOXENV" && -z "$PYPI_DEPLOY" ]]; then
|
||||
chmod +x coverage.sh
|
||||
./coverage.sh
|
||||
fi
|
||||
|
||||
branches:
|
||||
only:
|
||||
|
||||
+2
-2
@@ -35,8 +35,8 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
||||
|
||||
$ virtualenv venv
|
||||
$ source venv/bin/activate
|
||||
(venv) $ pip install -e .[validation]
|
||||
(venv) $ pip install -r requirements/dev.txt
|
||||
(venv) $ pip install -U -e .[validation]
|
||||
(venv) $ pip install -U -r requirements/dev.txt
|
||||
|
||||
#. **Make your changes and check them against the test project**
|
||||
|
||||
|
||||
+16
-19
@@ -11,9 +11,9 @@ Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framewor
|
||||
|
||||
Compatible with
|
||||
|
||||
- **Django Rest Framework**: 3.7.7, 3.8.x
|
||||
- **Django**: 1.11.x, 2.0.x
|
||||
- **Python**: 2.7, 3.4, 3.5, 3.6
|
||||
- **Django Rest Framework**: 3.7.7, 3.8
|
||||
- **Django**: 1.11, 2.0, 2.1
|
||||
- **Python**: 2.7, 3.4, 3.5, 3.6, 3.7
|
||||
|
||||
Resources:
|
||||
|
||||
@@ -85,14 +85,14 @@ The preferred instalation method is directly from pypi:
|
||||
|
||||
.. code:: console
|
||||
|
||||
pip install drf-yasg
|
||||
pip install -U drf-yasg
|
||||
|
||||
Additionally, if you want to use the built-in validation mechanisms (see `4. Validation`_), you need to install
|
||||
some extra requirements:
|
||||
|
||||
.. code:: console
|
||||
|
||||
pip install drf-yasg[validation]
|
||||
pip install -U drf-yasg[validation]
|
||||
|
||||
.. _readme-quickstart:
|
||||
|
||||
@@ -134,9 +134,9 @@ In ``urls.py``:
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
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'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
|
||||
url(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||
...
|
||||
]
|
||||
|
||||
@@ -294,7 +294,7 @@ For additional usage examples, you can take a look at the test project in the ``
|
||||
$ virtualenv venv
|
||||
$ source venv/bin/activate
|
||||
(venv) $ cd testproj
|
||||
(venv) $ pip install -r requirements.txt
|
||||
(venv) $ pip install -U -r requirements.txt
|
||||
(venv) $ python manage.py migrate
|
||||
(venv) $ python manage.py shell -c "import createsuperuser"
|
||||
(venv) $ python manage.py runserver
|
||||
@@ -315,8 +315,8 @@ From here on, the terms “OpenAPI” and “Swagger” are used interchangeably
|
||||
Swagger in Django Rest Framework
|
||||
================================
|
||||
|
||||
Since Django Rest 3.7, there is now `built in support <http://www.django-rest-framework.org/api-guide/schemas/>`__ for
|
||||
automatic OpenAPI 2.0 schema generation. However, this generation is based on the `coreapi <http://www.coreapi.org/>`__
|
||||
Since Django Rest Framework 3.7, there is now `built in support <http://www.django-rest-framework.org/api-guide/schemas/>`__
|
||||
for automatic OpenAPI 2.0 schema generation. However, this generation is based on the `coreapi <http://www.coreapi.org/>`__
|
||||
standard, which for the moment is vastly inferior to OpenAPI in both features and tooling support. In particular,
|
||||
the OpenAPI codec/compatibility layer provided has a few major problems:
|
||||
|
||||
@@ -329,18 +329,15 @@ In short this makes the generated schema unusable for code generation, and medio
|
||||
Other libraries
|
||||
===============
|
||||
|
||||
There are currently two decent Swagger schema generators that I could
|
||||
find for django-rest-framework:
|
||||
There are currently two decent Swagger schema generators that I could find for django-rest-framework:
|
||||
|
||||
* `django-rest-swagger <https://github.com/marcgibbons/django-rest-swagger>`__
|
||||
* `drf-openapi <https://github.com/limdauto/drf_openapi>`__
|
||||
|
||||
Out of the two, ``django-rest-swagger`` is just a wrapper around DRF 3.7 schema generation with an added UI, and
|
||||
thus presents the same problems. ``drf-openapi`` is a bit more involved and implements some custom handling for response
|
||||
schemas, but ultimately still falls short in code generation because the responses are plain of lacking support for
|
||||
named schemas.
|
||||
|
||||
Both projects are also currently unmantained.
|
||||
``django-rest-swagger`` is just a wrapper around DRF 3.7 schema generation with an added UI, and
|
||||
thus presents the same problems, while also being unmaintained. ``drf-openapi`` was
|
||||
`discontinued by the author <https://github.com/limdauto/drf_openapi/commit/1673c6e039eec7f089336a83bdc31613f32f7e21>`_
|
||||
on April 3rd, 2018.
|
||||
|
||||
************************
|
||||
Third-party integrations
|
||||
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
|
||||
coverage combine
|
||||
coverage report
|
||||
codecov
|
||||
+71
-2
@@ -3,6 +3,75 @@ 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**
|
||||
**********
|
||||
|
||||
*Release date: Sep 10, 2018*
|
||||
|
||||
- **ADDED:** added the ``SPEC_URL`` setting for controlling the download link in ``swagger-ui`` and ``ReDoc``
|
||||
- **ADDED:** updated ``ReDoc`` settings (added ``NATIVE_SCROLLBARS`` and ``REQUIRED_PROPS_FIRST``)
|
||||
- **ADDED:** added ``extra_styles`` and ``extra_scripts`` blocks to ui templates (:issue:`178`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.18.2
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-alpha.37
|
||||
- **FIXED:** stopped generating invalid OpenAPI by improper placement of ``readOnly`` Schemas
|
||||
- **FIXED:** fixed broken CSS when ``USE_SESSION_AUTH=False``
|
||||
- **FIXED:** fixed implementation of ``operation_summary`` and ``deprecated`` (:pr:`194`, :pr:`198`)
|
||||
- **FIXED:** fixed a bug related to nested ``typing`` hints (:pr:`195`)
|
||||
- **FIXED:** removed dependency on ``future`` (:issue:`196`)
|
||||
- **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`)
|
||||
|
||||
|
||||
**********
|
||||
**1.10.0**
|
||||
**********
|
||||
|
||||
*Release date: Aug 08, 2018*
|
||||
|
||||
- **ADDED:** added ``EXCLUDED_MEDIA_TYPES`` setting for controlling ``produces`` MIME type filtering (:issue:`158`)
|
||||
- **ADDED:** added support for ``SerializerMethodField``, via the ``swagger_serializer_method`` decorator for the
|
||||
method field, and support for Python 3.5 style type hinting of the method field return type
|
||||
(:issue:`137`, :pr:`175`, :pr:`179`)
|
||||
|
||||
*NOTE:* in order for this to work, you will have to add the new ``drf_yasg.inspectors.SerializerMethodFieldInspector``
|
||||
to your ``DEFAULT_FIELD_INSPECTORS`` array if you changed it from the default value
|
||||
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.18.0
|
||||
- **IMPROVED:** added support for Python 3.7 and Django 2.1 (:pr:`176`)
|
||||
- **IMPROVED:** ``swagger_schema_fields`` will now also work on serializer ``Field``\ s (:issue:`167`)
|
||||
- **IMPROVED:** ``ref_name`` collisions will now log a warning message (:issue:`156`)
|
||||
- **IMPROVED:** added ``operation_summary`` and ``deprecated`` arguments to ``swagger_auto_schema``
|
||||
(:issue:`149`, :issue:`173`)
|
||||
- **FIXED:** made ``swagger_auto_schema`` work with DRF 3.9 ``@action`` mappings (:issue:`177`)
|
||||
|
||||
*********
|
||||
**1.9.2**
|
||||
*********
|
||||
|
||||
*Release date: Aug 03, 2018*
|
||||
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.17.6
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-alpha.32
|
||||
- **IMPROVED:** added ``--api-version`` argument to the ``generate_swagger`` management command (:pr:`170`)
|
||||
- **FIXED:** corrected various documentation typos (:pr:`160`, :pr:`162`, :issue:`171`, :pr:`172`)
|
||||
- **FIXED:** made ``generate_swagger`` work for projects without authentication (:pr:`161`)
|
||||
- **FIXED:** fixed ``SafeText`` interaction with YAML codec (:issue:`159`)
|
||||
|
||||
*********
|
||||
**1.9.1**
|
||||
*********
|
||||
@@ -11,7 +80,7 @@ Changelog
|
||||
|
||||
- **IMPROVED:** added a ``swagger_fake_view`` marker to more easily detect mock views in view methods;
|
||||
``getattr(self, 'swagger_fake_view', False)`` inside a view method like ``get_serializer_class`` will tell you if the
|
||||
view instnace is being used for swagger schema introspection (:issue:`154`)
|
||||
view instance is being used for swagger schema introspection (:issue:`154`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.17.1
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-alpha.25
|
||||
- **FIXED:** fixed wrong handling of duplicate urls in urlconf (:pr:`155`)
|
||||
@@ -26,7 +95,7 @@ Changelog
|
||||
- **ADDED:** added ``DEFAULT_GENERATOR_CLASS`` setting and ``--generator-class`` argument to the ``generate_swagger``
|
||||
management command (:issue:`140`)
|
||||
- **FIXED:** fixed wrongly required ``'count'`` response field on ``CursorPagination`` (:issue:`141`)
|
||||
- **FIXED:** fixed some cases where ``swagger_extra_fields`` would not be handlded (:pr:`142`)
|
||||
- **FIXED:** fixed some cases where ``swagger_schema_fields`` would not be handlded (:pr:`142`)
|
||||
- **FIXED:** fixed crash when encountering ``coreapi.Fields``\ s without a ``schema`` (:issue:`143`)
|
||||
|
||||
*********
|
||||
|
||||
+3
-1
@@ -155,7 +155,9 @@ texinfo_documents = [
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
autodoc_default_flags = ['private-members']
|
||||
autodoc_default_options = {
|
||||
'private-members': None
|
||||
}
|
||||
autodoc_member_order = 'bysource'
|
||||
autoclass_content = 'both'
|
||||
autodoc_mock_imports = []
|
||||
|
||||
+55
-1
@@ -87,7 +87,7 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
||||
|
||||
* for ``ViewSet``, ``GenericViewSet``, ``ModelViewSet``, because each viewset corresponds to multiple **paths**, you have
|
||||
to decorate the *action methods*, i.e. ``list``, ``create``, ``retrieve``, etc. |br|
|
||||
Additionally, ``@action``\ s, `@list_route``\ s or ``@detail_route``\ s defined on the viewset, like function based
|
||||
Additionally, ``@action``\ s, ``@list_route``\ s or ``@detail_route``\ s defined on the viewset, like function based
|
||||
api views, can respond to multiple HTTP methods and thus have multiple operations that must be decorated separately:
|
||||
|
||||
|
||||
@@ -155,6 +155,57 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
||||
replacing/decorating methods on the base class itself.
|
||||
|
||||
|
||||
*********************************
|
||||
Support for SerializerMethodField
|
||||
*********************************
|
||||
|
||||
Schema generation of ``serializers.SerializerMethodField`` is supported in two ways:
|
||||
|
||||
1) The :func:`swagger_serializer_method <.swagger_serializer_method>` decorator for the use case where the serializer
|
||||
method is using a serializer. e.g.:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from drf_yasg.utils import swagger_serializer_method
|
||||
|
||||
class OtherStuffSerializer(serializers.Serializer):
|
||||
foo = serializers.CharField()
|
||||
|
||||
class ParentSerializer(serializers.Serializer):
|
||||
other_stuff = serializers.SerializerMethodField()
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=OtherStuffSerializer)
|
||||
def get_other_stuff(self, obj):
|
||||
return OtherStuffSerializer().data
|
||||
|
||||
|
||||
Note that the ``serializer_or_field`` parameter can accept either a subclass or an instance of ``serializers.Field``.
|
||||
|
||||
|
||||
2) For simple cases where the method is returning one of the supported types, `Python 3 type hinting`_ of the
|
||||
serializer method return value can be used. e.g.:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class SomeSerializer(serializers.Serializer):
|
||||
some_number = serializers.SerializerMethodField()
|
||||
|
||||
def get_some_number(self, obj) -> float:
|
||||
return 1.0
|
||||
|
||||
When return type hinting is not supported, the equivalent ``serializers.Field`` subclass can be used with
|
||||
:func:`swagger_serializer_method <.swagger_serializer_method>`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class SomeSerializer(serializers.Serializer):
|
||||
some_number = serializers.SerializerMethodField()
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.FloatField)
|
||||
def get_some_number(self, obj):
|
||||
return 1.0
|
||||
|
||||
|
||||
********************************
|
||||
Serializer ``Meta`` nested class
|
||||
********************************
|
||||
@@ -333,3 +384,6 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
|
||||
|
||||
Another caveat that stems from this is that any serializer named "``NestedSerializer``" will be forced inline
|
||||
unless it has a ``ref_name`` set explicitly.
|
||||
|
||||
|
||||
.. _Python 3 type hinting: https://docs.python.org/3/library/typing.html
|
||||
|
||||
+14
-3
@@ -2,6 +2,17 @@
|
||||
Customizing the web UI
|
||||
######################
|
||||
|
||||
There is currently no pluggable way of customizing the web UI apart from the settings available in
|
||||
:ref:`swagger-ui-settings` and :ref:`redoc-ui-settings`. If you really need to, you can override one of the
|
||||
``drf-yasg/swagger-ui.html`` or ``drf-yasg/redoc.html`` templates that are used for rendering.
|
||||
The web UI can be customized using the settings available in :ref:`swagger-ui-settings` and :ref:`redoc-ui-settings`.
|
||||
|
||||
You can also extend one of the ``drf-yasg/swagger-ui.html`` or ``drf-yasg/redoc.html`` templates that are used for
|
||||
rendering. The customizable blocks are currently limited to:
|
||||
|
||||
{% block extra_styles %}
|
||||
additional stylesheets
|
||||
|
||||
{% block extra_scripts %}
|
||||
additional scripts
|
||||
|
||||
{% block user_context_message %}
|
||||
*(swagger-ui session auth only)*
|
||||
logged in user message
|
||||
|
||||
+93
-17
@@ -27,16 +27,27 @@ Example:
|
||||
}
|
||||
|
||||
REDOC_SETTINGS = {
|
||||
'LAZY_RENDERING': True,
|
||||
'LAZY_RENDERING': False,
|
||||
...
|
||||
}
|
||||
|
||||
.. _url-settings:
|
||||
|
||||
All settings which configure URLs (``LOGIN_URL``, ``SPEC_URL``, ``VALIDATOR_URL``, etc.) can accept several forms of
|
||||
input:
|
||||
|
||||
* A view name: `urls.reverse()` will be used to reverse-resolve the name
|
||||
* A 2-tuple of ``(view_name, kwargs)```: `urls.reverse()` will be used to reverse-resolve the name using the given
|
||||
`kwargs`; `kwargs` must be a dict
|
||||
* A 3-tuple of ``(view_name, args, kwargs)```: `urls.reverse()` will be used to reverse-resolve the name using the given
|
||||
`args` and `kwargs`; `args`, `kwargs` must be a tuple/list and a dict respectively
|
||||
* A URL, which will be used as-is
|
||||
|
||||
The possible settings and their default values are as follows:
|
||||
|
||||
********************
|
||||
``SWAGGER_SETTINGS``
|
||||
********************
|
||||
|
||||
****************
|
||||
SWAGGER_SETTINGS
|
||||
****************
|
||||
|
||||
.. _default-class-settings:
|
||||
|
||||
@@ -76,6 +87,7 @@ to this list.
|
||||
:class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.HiddenFieldInspector' <.inspectors.HiddenFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.RecursiveFieldInspector' <.inspectors.RecursiveFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.SerializerMethodFieldInspector' <.inspectors.SerializerMethodFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.SimpleFieldInspector' <.inspectors.SimpleFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.StringDefaultFieldInspector' <.inspectors.StringDefaultFieldInspector>`, |br| \
|
||||
``]``
|
||||
@@ -104,6 +116,14 @@ Paginator inspectors given to :func:`@swagger_auto_schema <.swagger_auto_schema>
|
||||
Swagger document attributes
|
||||
===========================
|
||||
|
||||
EXCLUDED_MEDIA_TYPES
|
||||
--------------------
|
||||
|
||||
A list of keywords for excluding MIME types from ``Operation.produces``. Any MIME type string which includes one of
|
||||
the substrings in this list will be prevented from appearing in a ``produces`` array in the Swagger document.
|
||||
|
||||
**Default**: :python:`['html']`
|
||||
|
||||
.. _default-swagger-settings:
|
||||
|
||||
DEFAULT_INFO
|
||||
@@ -183,6 +203,15 @@ Swagger UI settings
|
||||
Swagger UI configuration settings. |br|
|
||||
See https://github.com/swagger-api/swagger-ui/blob/112bca906553a937ac67adc2e500bdeed96d067b/docs/usage/configuration.md#parameters.
|
||||
|
||||
SPEC_URL
|
||||
--------
|
||||
|
||||
URL pointing to a swagger document for use by swagger-ui. The default behaviour is to append ``?format=openapi`` to the
|
||||
URL which serves the UI; see :ref:`note on URL settings <url-settings>` above.
|
||||
|
||||
**Default**: :python:`None` |br|
|
||||
*Maps to parameter*: ``url``
|
||||
|
||||
VALIDATOR_URL
|
||||
-------------
|
||||
|
||||
@@ -263,10 +292,10 @@ Controls how many levels are expaned by default when showing nested models.
|
||||
**Default**: :python:`3` |br|
|
||||
*Maps to parameter*: ``defaultModelExpandDepth``
|
||||
|
||||
DEFAULT_MODEL_DEPTH
|
||||
-------------------
|
||||
SHOW_COMMON_EXTENSIONS
|
||||
----------------------
|
||||
|
||||
Controls the display of extensions (``pattern``, ``maxLength``, ``minLength``, ``maximum``, ```minimum``) fields and
|
||||
Controls the display of extensions (``pattern``, ``maxLength``, ``minLength``, ``maximum``, ``minimum``) fields and
|
||||
values for Parameters.
|
||||
|
||||
**Default**: :python:`True` |br|
|
||||
@@ -303,9 +332,17 @@ This does not filter the operations from the display.
|
||||
**Default**: :python:`['get','put','post','delete','options','head','patch','trace']` |br|
|
||||
*Maps to parameter*: ``supportedSubmitMethods``
|
||||
|
||||
******************
|
||||
``REDOC_SETTINGS``
|
||||
******************
|
||||
DISPLAY_OPERATION_ID
|
||||
--------------------
|
||||
|
||||
Controls the display of operationId in operations list.
|
||||
|
||||
**Default**: :python:`True`
|
||||
*Maps to parameter*: ``displayOperationId``
|
||||
|
||||
**************
|
||||
REDOC_SETTINGS
|
||||
**************
|
||||
|
||||
.. _redoc-ui-settings:
|
||||
|
||||
@@ -313,31 +350,70 @@ ReDoc UI settings
|
||||
=================
|
||||
|
||||
ReDoc UI configuration settings. |br|
|
||||
See https://github.com/Rebilly/ReDoc#redoc-tag-attributes.
|
||||
See https://github.com/Rebilly/ReDoc#configuration.
|
||||
|
||||
SPEC_URL
|
||||
--------
|
||||
|
||||
URL pointing to a swagger document for use by ReDoc. The default behaviour is to append ``?format=openapi`` to the
|
||||
URL which serves the UI; see :ref:`note on URL settings <url-settings>` above.
|
||||
|
||||
**Default**: :python:`None` |br|
|
||||
*Maps to attribute*: ``spec-url``
|
||||
|
||||
LAZY_RENDERING
|
||||
--------------
|
||||
|
||||
**Default**: :python:`True` |br|
|
||||
*Maps to attribute*: ``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)
|
||||
|
||||
**Default**: :python:`False` |br|
|
||||
*Maps to attribute*: ``lazyRendering``
|
||||
|
||||
HIDE_HOSTNAME
|
||||
-------------
|
||||
|
||||
If set, the protocol and hostname is not shown in the operation definition.
|
||||
|
||||
**Default**: :python:`False` |br|
|
||||
*Maps to attribute*: ``hide-hostname``
|
||||
*Maps to attribute*: ``hideHostname``
|
||||
|
||||
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|
|
||||
*Maps to attribute*: ``expand-responses``
|
||||
*Maps to attribute*: ``expandResponses``
|
||||
|
||||
PATH_IN_MIDDLE
|
||||
--------------
|
||||
|
||||
Show path link and HTTP verb in the middle panel instead of the right one.
|
||||
|
||||
**Default**: :python:`False` |br|
|
||||
*Maps to attribute*: ``path-in-middle-panel``
|
||||
*Maps to attribute*: ``pathInMiddlePanel``
|
||||
|
||||
NATIVE_SCROLLBARS
|
||||
-----------------
|
||||
|
||||
Use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs).
|
||||
|
||||
**Default**: :python:`False` |br|
|
||||
*Maps to attribute*: ``nativeScrollbars``
|
||||
|
||||
REQUIRED_PROPS_FIRST
|
||||
--------------------
|
||||
|
||||
Show required properties first ordered in the same order as in required array.
|
||||
|
||||
**Default**: :python:`False` |br|
|
||||
*Maps to attribute*: ``requiredPropsFirst``
|
||||
|
||||
|
||||
.. _FORCE_SCRIPT_NAME: https://docs.djangoproject.com/en/2.0/ref/settings/#force-script-name
|
||||
|
||||
Generated
+90
-568
@@ -3,22 +3,6 @@
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
|
||||
"requires": {
|
||||
"co": "^4.6.0",
|
||||
"fast-deep-equal": "^1.0.0",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
@@ -38,9 +22,9 @@
|
||||
"integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw=="
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz",
|
||||
"integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4"
|
||||
@@ -51,11 +35,6 @@
|
||||
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
|
||||
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms="
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
|
||||
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.2.6",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
|
||||
@@ -72,41 +51,11 @@
|
||||
"tiny-emitter": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
|
||||
"integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
|
||||
"requires": {
|
||||
"string-width": "^2.1.1",
|
||||
"strip-ansi": "^4.0.0",
|
||||
"wrap-ansi": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
||||
"requires": {
|
||||
"lru-cache": "^4.0.1",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
},
|
||||
"css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
@@ -123,18 +72,13 @@
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
|
||||
"integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"decko": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz",
|
||||
@@ -152,9 +96,9 @@
|
||||
"integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg="
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-1.0.5.tgz",
|
||||
"integrity": "sha512-W7QHgBwTbTh3/Xv0jQyckJOTN2uigyrwOTidFv0qRbT6s6HtuIO3e1LXKbA35FJ34dbptQ5Yznkd/ELAdJ2wOw=="
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-1.0.8.tgz",
|
||||
"integrity": "sha512-vetRFbN1SXSPfP3ClIiYnxTrXquSqakBEOoB5JESn0SVcSYzpu6ougjakpKnskGctYdlNpwf+riUHSkG7d4XUw=="
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.12",
|
||||
@@ -164,45 +108,16 @@
|
||||
"iconv-lite": "~0.4.13"
|
||||
}
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
|
||||
"integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
|
||||
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
|
||||
"integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA=="
|
||||
},
|
||||
"execa": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
|
||||
"integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
|
||||
"requires": {
|
||||
"cross-spawn": "^5.0.1",
|
||||
"get-stream": "^3.0.0",
|
||||
"is-stream": "^1.1.0",
|
||||
"npm-run-path": "^2.0.0",
|
||||
"p-finally": "^1.0.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"strip-eof": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
|
||||
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
|
||||
},
|
||||
"fast-json-stable-stringify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
|
||||
},
|
||||
"fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
@@ -222,14 +137,6 @@
|
||||
"ua-parser-js": "^0.7.18"
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
|
||||
"requires": {
|
||||
"locate-path": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"foreach": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
|
||||
@@ -240,16 +147,6 @@
|
||||
"resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.3.tgz",
|
||||
"integrity": "sha1-Ay3KShFiYqEsQ/TD7IVmQWxbLZU="
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
|
||||
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U="
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
},
|
||||
"global": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz",
|
||||
@@ -279,9 +176,9 @@
|
||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
@@ -291,39 +188,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
|
||||
"integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA=="
|
||||
},
|
||||
"invert-kv": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
|
||||
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||
"requires": {
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||
},
|
||||
"isomorphic-fetch": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
||||
@@ -334,9 +203,9 @@
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
||||
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.12.0",
|
||||
@@ -356,59 +225,28 @@
|
||||
}
|
||||
},
|
||||
"json-schema-ref-parser": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-5.0.3.tgz",
|
||||
"integrity": "sha512-RCZE9RpvMG2zY+dSSQkqHxsCYaqBYlh/u2PWWRdWMvtaH718YqR73lYu4IW4cGNxJIkUnIgTMuJtp+A1tIn4+w==",
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-5.1.3.tgz",
|
||||
"integrity": "sha512-CpDFlBwz/6la78hZxyB9FECVKGYjIIl3Ms3KLqFj99W7IIb7D00/RDgc++IGB4BBALl0QRhh5m4q5WNSopvLtQ==",
|
||||
"requires": {
|
||||
"call-me-maybe": "^1.0.1",
|
||||
"debug": "^3.1.0",
|
||||
"js-yaml": "^3.11.0",
|
||||
"ono": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
|
||||
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
|
||||
},
|
||||
"lcid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
|
||||
"integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
|
||||
"requires": {
|
||||
"invert-kv": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
|
||||
"integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
|
||||
"requires": {
|
||||
"p-locate": "^2.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
"js-yaml": "^3.12.0",
|
||||
"ono": "^4.0.6"
|
||||
}
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
|
||||
"integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"requires": {
|
||||
"js-tokens": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
|
||||
"integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==",
|
||||
"requires": {
|
||||
"pseudomap": "^1.0.2",
|
||||
"yallist": "^2.1.2"
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"lunr": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.0.tgz",
|
||||
"integrity": "sha1-TXwMoSvdHgRHsMExuRQgkpdAyI8="
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.3.tgz",
|
||||
"integrity": "sha512-rlAEsgU9Bnavca2w1WJ6+6cdeHMXNyadcersyk3ZpuhgWb5HBNj8l4WwJz9PjksAhYDlpQffCVXPctOn+wCIVA=="
|
||||
},
|
||||
"mark.js": {
|
||||
"version": "8.11.1",
|
||||
@@ -420,23 +258,10 @@
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.18.tgz",
|
||||
"integrity": "sha512-49i2QYhfULqaXzNZpxC808PisuCTGT2fgG0zrzdCI9N3rIfAWfW0nggvbXr6zvpynZdOG5+9xNxdzP0kwZnERw=="
|
||||
},
|
||||
"mem": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
|
||||
"integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
|
||||
"requires": {
|
||||
"mimic-fn": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"memoize-one": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-3.1.1.tgz",
|
||||
"integrity": "sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA=="
|
||||
},
|
||||
"mimic-fn": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
|
||||
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.2.tgz",
|
||||
"integrity": "sha512-ucx2DmXTeZTsS4GPPUZCbULAN7kdPT1G+H49Y34JjbQ5ESc6OGhVxKvb1iKhr9v19ZB9OtnHwNnhUnNR/7Wteg=="
|
||||
},
|
||||
"min-document": {
|
||||
"version": "2.19.0",
|
||||
@@ -446,24 +271,19 @@
|
||||
"dom-walk": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"mobx": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-4.3.1.tgz",
|
||||
"integrity": "sha1-M05aq0kWsdQ/D682BaZLG0s8y40="
|
||||
},
|
||||
"mobx-react": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.2.3.tgz",
|
||||
"integrity": "sha512-OuSlF2nJEa1PGookZcZnINbvEK4iWNNYiqUh6aebk2AkWxj3sG8OafDOQMcMYApQALTHRsrBIjOx/K8TFxcz7w==",
|
||||
"version": "5.2.8",
|
||||
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.2.8.tgz",
|
||||
"integrity": "sha512-VAkeqkrIpdoy3VPPoqvxjdQmcTT6hi7i3TsZSwcKbSrPbSTuWc7cp1EOiX/zV1wPWEuoNAQ22bCrskQwvdYTJA==",
|
||||
"requires": {
|
||||
"hoist-non-react-statics": "^2.5.0",
|
||||
"react-lifecycles-compat": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "1.7.3",
|
||||
@@ -474,103 +294,36 @@
|
||||
"is-stream": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node-readfiles": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz",
|
||||
"integrity": "sha1-271K8SE04uY1wkXvk//Pb2BnOl0=",
|
||||
"requires": {
|
||||
"es6-promise": "^3.2.1"
|
||||
}
|
||||
},
|
||||
"npm-run-path": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
|
||||
"requires": {
|
||||
"path-key": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"ono": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.5.tgz",
|
||||
"integrity": "sha512-ZVNuV9kJbr/2tWs83I2snrYo+WIS0DISF/xUfX9p9b6GyDD6F5N9PzHjW+p/dep6IGwSYylf1HCub5I/nM0R5Q==",
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.7.tgz",
|
||||
"integrity": "sha512-FJiGEETwfSVyOwVTwQZD7XN69FRekvgtlobtvPwtilc7PxIHg3gKUykdNP7E9mC/VTF2cxqKZxUZfNKA3MuQLA==",
|
||||
"requires": {
|
||||
"format-util": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"openapi-sampler": {
|
||||
"version": "1.0.0-beta.13",
|
||||
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.0.0-beta.13.tgz",
|
||||
"integrity": "sha512-NReKJh92NlUmY9CDo86L1Skkx3DMDeWmiLoqMxQSX7xBpoSx8WJ1JdTb7nEwz6M7S+WDPrQsEfS5BaIW27nMrQ==",
|
||||
"version": "1.0.0-beta.14",
|
||||
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.0.0-beta.14.tgz",
|
||||
"integrity": "sha512-NNmH9YAN5AaCE4w6MQXdCrmsOJJQTswHVSp075+h+iiG+OTonpZE8HzwocozovD2imx4lamkuxGLs4E4bO4Z+g==",
|
||||
"requires": {
|
||||
"json-pointer": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"os-locale": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
|
||||
"integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
|
||||
"requires": {
|
||||
"execa": "^0.7.0",
|
||||
"lcid": "^1.0.0",
|
||||
"mem": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
|
||||
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
|
||||
"requires": {
|
||||
"p-try": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
|
||||
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
|
||||
"requires": {
|
||||
"p-limit": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
|
||||
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
|
||||
},
|
||||
"path-key": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
|
||||
},
|
||||
"perfect-scrollbar": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.4.0.tgz",
|
||||
"integrity": "sha512-/2Sk/khljhdrsamjJYS5NjrH+GKEHEwh7zFSiYyxROyYKagkE4kSn2zDQDRTOMo8mpT2jikxx6yI1dG7lNP/hw=="
|
||||
},
|
||||
"polished": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/polished/-/polished-1.9.3.tgz",
|
||||
"integrity": "sha512-4NmSD7fMFlM8roNxs7YXPv7UFRbYzb0gufR5zBxJLRzY54+zFsavxBo6zsQzP9ep6Hh3pC2pTyrpSTBEaB6IkQ=="
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/polished/-/polished-2.0.3.tgz",
|
||||
"integrity": "sha512-MMwthxXsmZZlZY/x79KX/8kXpSU0lq3ntnDmAdhpQGk9pU9EM/rQmUbsgIuuk2Cvl6zYSWjyBsDEU8sa1598xg=="
|
||||
},
|
||||
"postcss-value-parser": {
|
||||
"version": "3.3.0",
|
||||
@@ -607,23 +360,18 @@
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
|
||||
},
|
||||
"react-dropdown": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dropdown/-/react-dropdown-1.5.0.tgz",
|
||||
"integrity": "sha512-rRv3a7NiP++yC1rzdjzkviC5ujq759i4SRa0M3C0Cr7loYT4Z3+JhSPekv1/04JiZNXX46cV3/g6A9kS7rkI4Q==",
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dropdown/-/react-dropdown-1.6.2.tgz",
|
||||
"integrity": "sha512-6RpKAFEVZLr53y91qnxC9rAWcoDvq8A9YstmeSV2AQgJxNFRcsreN1mJNRUctSvIwm0Hph07ouMvDc2GvfQBsA==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.3"
|
||||
}
|
||||
},
|
||||
"react-hot-loader": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.3.3.tgz",
|
||||
"integrity": "sha512-N2yYEfI8mIeE9DffyMHrHQz0WryDdF8d1CJ4rpQ15hNAu3xco6wQ6JtKIIc/8w0igFWh2XpV0rIkuE4tpKLivQ==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.3.6.tgz",
|
||||
"integrity": "sha512-iuBeBkLz7tdkKmKduNww9o5JY4ZH1XI0TWwkWToHIqfOSh1xMCqLMSYXUasWfgZWykWWa9IkueYab+cDq2jyWg==",
|
||||
"requires": {
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"global": "^4.3.0",
|
||||
@@ -634,9 +382,9 @@
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.4.1.tgz",
|
||||
"integrity": "sha512-xpb0PpALlFWNw/q13A+1aHeyJyLYCg0/cCHPUA43zYluZuIPHaHL3k8OBsTgQtxqW0FhyDEMvi8fZ/+7+r4OSQ=="
|
||||
"version": "16.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.5.0.tgz",
|
||||
"integrity": "sha512-kpkCGLsChXTEQJVmowQqHpCjHKJFwB4SIChYaaaiAkq8OtE2aBg5pQe8/xnFlGmz9KmMx1H4oQRUyxP7qC9v5A=="
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
@@ -644,61 +392,44 @@
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-tabs": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.2.2.tgz",
|
||||
"integrity": "sha512-jZGAAoq1yknr/XF60/2kH5X3UJdR1X8ItcsLZ1mJnHfoniKcCr3shT2TK5wiS2sO0LOmqwX2BtJQX3snKd50sg==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.3.0.tgz",
|
||||
"integrity": "sha512-pYaefgVy76/36AMEP+B8YuVVzDHa3C5UFZ3REU78zolk0qMxEhKvUFofvDCXyLZwf0RZjxIfiwok1BEb18nHyA==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.0",
|
||||
"prop-types": "^15.5.0"
|
||||
}
|
||||
},
|
||||
"redoc": {
|
||||
"version": "2.0.0-alpha.25",
|
||||
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-alpha.25.tgz",
|
||||
"integrity": "sha512-pTrOm8OFUzouBLnE/D8lGSLmwvXpjNr14FpPvE8mfysX7UOs9BT7F06oVpujSqx2TjV6V1Mr3/nsAPonSLX10g==",
|
||||
"version": "2.0.0-alpha.38",
|
||||
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-alpha.38.tgz",
|
||||
"integrity": "sha512-4pT/cRJ/aKdLlCxjFOSWvkA9ZVG5b9dUfxIM+H5698hlRaF6rcu4qh29eGwEikSDw0unH0iNd6MHy9u0fAjRGw==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.6",
|
||||
"decko": "^1.2.0",
|
||||
"dompurify": "^1.0.5",
|
||||
"dompurify": "^1.0.7",
|
||||
"eventemitter3": "^3.0.0",
|
||||
"json-pointer": "^0.6.0",
|
||||
"json-schema-ref-parser": "^5.0.0",
|
||||
"lunr": "^2.3.0",
|
||||
"json-schema-ref-parser": "^5.1.2",
|
||||
"lunr": "^2.3.2",
|
||||
"mark.js": "^8.11.1",
|
||||
"marked": "0.3.18",
|
||||
"memoize-one": "^3.1.1",
|
||||
"mobx": "^4.3.0",
|
||||
"mobx-react": "^5.2.3",
|
||||
"openapi-sampler": "1.0.0-beta.13",
|
||||
"memoize-one": "^4.0.0",
|
||||
"mobx-react": "^5.2.5",
|
||||
"openapi-sampler": "1.0.0-beta.14",
|
||||
"perfect-scrollbar": "^1.4.0",
|
||||
"polished": "^1.9.2",
|
||||
"polished": "^2.0.2",
|
||||
"prismjs": "^1.15.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-dropdown": "^1.3.0",
|
||||
"react-hot-loader": "^4.3.3",
|
||||
"react-dropdown": "^1.6.2",
|
||||
"react-hot-loader": "^4.3.5",
|
||||
"react-tabs": "^2.0.0",
|
||||
"slugify": "^1.2.1",
|
||||
"slugify": "^1.3.1",
|
||||
"stickyfill": "^1.1.1",
|
||||
"styled-components": "^3.3.3",
|
||||
"swagger2openapi": "^2.11.0",
|
||||
"styled-components": "^3.4.5",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"reftools": {
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://registry.npmjs.org/reftools/-/reftools-0.0.20.tgz",
|
||||
"integrity": "sha512-xP04tVBgU6IEfVN3AD+jKg0tuDFjzbpAVulI8vFp9jJ6avejnq6BaTj4aneY0sNseXJbAykG6uMDuFJaun1Q9w=="
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
|
||||
},
|
||||
"require-main-filename": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
|
||||
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
@@ -710,11 +441,6 @@
|
||||
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
|
||||
"optional": true
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
||||
},
|
||||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
@@ -725,76 +451,10 @@
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
|
||||
"requires": {
|
||||
"shebang-regex": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
|
||||
},
|
||||
"should": {
|
||||
"version": "13.2.1",
|
||||
"resolved": "https://registry.npmjs.org/should/-/should-13.2.1.tgz",
|
||||
"integrity": "sha512-l+/NwEMO+DcstsHEwPHRHzC9j4UOE3VQwJGcMWSsD/vqpqHbnQ+1iSHy64Ihmmjx1uiRPD9pFadTSc3MJtXAgw==",
|
||||
"requires": {
|
||||
"should-equal": "^2.0.0",
|
||||
"should-format": "^3.0.3",
|
||||
"should-type": "^1.4.0",
|
||||
"should-type-adaptors": "^1.0.1",
|
||||
"should-util": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"should-equal": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
|
||||
"integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
|
||||
"requires": {
|
||||
"should-type": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"should-format": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
|
||||
"integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
|
||||
"requires": {
|
||||
"should-type": "^1.3.0",
|
||||
"should-type-adaptors": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"should-type": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
|
||||
"integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM="
|
||||
},
|
||||
"should-type-adaptors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
|
||||
"integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
|
||||
"requires": {
|
||||
"should-type": "^1.3.0",
|
||||
"should-util": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"should-util": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz",
|
||||
"integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM="
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"slugify": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.0.tgz",
|
||||
"integrity": "sha512-vvz+9ANt7CtdTHwJpfrsHOnGkgxky+CUPnvtzDZBZYFo/H/CdZkd5lJL7z7RqtH/x9QW/ItYYfHlcGf38CBK1w=="
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.1.tgz",
|
||||
"integrity": "sha512-6BwyhjF5tG5P8s+0DPNyJmBSBePG6iMyhjvIW5zGdA3tFik9PtK+yNkZgTeiroCRGZYgkHftFA62tGVK1EI9Kw=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
@@ -806,38 +466,15 @@
|
||||
"resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz",
|
||||
"integrity": "sha1-OUE/7p0CXHSn5ZzuyyN4TMDxfwI="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"strip-eof": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
|
||||
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
|
||||
},
|
||||
"styled-components": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-3.3.3.tgz",
|
||||
"integrity": "sha1-CecCBVqxH3qOq4IpscDQuFUJVoY=",
|
||||
"version": "3.4.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-3.4.6.tgz",
|
||||
"integrity": "sha512-HiEVVY93oH/PuekPuIE7ownn2wmO708rS8MM1yS20i0TCyFgPs3mbop8fJqKLRtC2/npVn2fO5Je6azaL6T2tg==",
|
||||
"requires": {
|
||||
"buffer": "^5.0.3",
|
||||
"css-to-react-native": "^2.0.3",
|
||||
"fbjs": "^0.8.16",
|
||||
"hoist-non-react-statics": "^2.5.0",
|
||||
"is-plain-object": "^2.0.1",
|
||||
"prop-types": "^15.5.4",
|
||||
"react-is": "^16.3.1",
|
||||
"stylis": "^3.5.0",
|
||||
@@ -846,9 +483,9 @@
|
||||
}
|
||||
},
|
||||
"stylis": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.1.tgz",
|
||||
"integrity": "sha512-yM4PyeHuwhIOUHNJxi1/Mbq8kVLv4AkyE7IYLP/LK0lIFcr3tRa2H1iZlBYKIxOlf+/jruBTe8DdKSyQX9w4OA=="
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.3.tgz",
|
||||
"integrity": "sha512-TxU0aAscJghF9I3V9q601xcK3Uw1JbXvpsBGj/HULqexKOKlOEzzlIpLFRbKkCK990ccuxfXUqmPbIIo7Fq/cQ=="
|
||||
},
|
||||
"stylis-rule-sheet": {
|
||||
"version": "0.0.10",
|
||||
@@ -864,32 +501,9 @@
|
||||
}
|
||||
},
|
||||
"swagger-ui-dist": {
|
||||
"version": "3.17.1",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.17.1.tgz",
|
||||
"integrity": "sha1-2wjtaBvPuhMUn35hD0izbNTBfeg="
|
||||
},
|
||||
"swagger2openapi": {
|
||||
"version": "2.11.16",
|
||||
"resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-2.11.16.tgz",
|
||||
"integrity": "sha512-5Pv20whg9Bn1dqKDuGatH0r+7mAEU8+tHJU1PdT9ufGeZNseqJBs9Y8AOd8EMEHKRuAgAzYYWhINvgtodAlZRA==",
|
||||
"requires": {
|
||||
"ajv": "^5.0.1",
|
||||
"call-me-maybe": "^1.0.1",
|
||||
"co": "^4.6.0",
|
||||
"js-yaml": "^3.6.1",
|
||||
"node-fetch": "^2.0.0",
|
||||
"node-readfiles": "^0.2.0",
|
||||
"reftools": "0.0.20",
|
||||
"should": "^13.0.1",
|
||||
"yargs": "^11.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
|
||||
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
|
||||
}
|
||||
}
|
||||
"version": "3.18.2",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.18.2.tgz",
|
||||
"integrity": "sha512-pWAEiKkgWUJvjmLW9AojudnutJ+NTn5g6OdNLj1iIJWwCkoy40K3Upwa24DqFbmIE4vLX4XplND61hp2L+s5vg=="
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.0.2",
|
||||
@@ -908,101 +522,9 @@
|
||||
"integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA=="
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
|
||||
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"which-module": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
|
||||
"requires": {
|
||||
"string-width": "^1.0.1",
|
||||
"strip-ansi": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
|
||||
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
|
||||
},
|
||||
"yargs": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
|
||||
"integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==",
|
||||
"requires": {
|
||||
"cliui": "^4.0.0",
|
||||
"decamelize": "^1.1.1",
|
||||
"find-up": "^2.1.0",
|
||||
"get-caller-file": "^1.0.1",
|
||||
"os-locale": "^2.0.0",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^1.0.1",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^2.0.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^3.2.1",
|
||||
"yargs-parser": "^9.0.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz",
|
||||
"integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=",
|
||||
"requires": {
|
||||
"camelcase": "^4.1.0"
|
||||
}
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "drf-yasg",
|
||||
"dependencies": {
|
||||
"redoc": "^2.0.0-alpha.25",
|
||||
"swagger-ui-dist": "^3.17.1"
|
||||
"redoc": "^2.0.0-alpha.38",
|
||||
"swagger-ui-dist": "^3.18.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
-r requirements/setup.txt
|
||||
.[validation]
|
||||
-r requirements/heroku.txt
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
coreapi>=2.3.3
|
||||
coreschema>=0.0.4
|
||||
openapi_codec>=1.3.2
|
||||
ruamel.yaml>=0.15.34
|
||||
inflection>=0.3.1
|
||||
future>=0.16.0
|
||||
six>=1.10.0
|
||||
uritemplate>=3.0.0
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# requirements for local development to be installed via pip install -r requirements/dev.txt
|
||||
# requirements for local development to be installed via pip install -U -r requirements/dev.txt
|
||||
-r tox.txt
|
||||
-r test.txt
|
||||
-r lint.txt
|
||||
|
||||
@@ -4,5 +4,5 @@ sphinx_rtd_theme>=0.2.4
|
||||
Pillow>=4.3.0
|
||||
readme_renderer>=17.2
|
||||
|
||||
Django>=2.0,<2.1
|
||||
Django>=2.0
|
||||
djangorestframework_camel_case>=0.2.0
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# needed to build the package setup_requires in setup.py
|
||||
|
||||
# do not unpin this (see setup.py)
|
||||
setuptools_scm==1.15.6
|
||||
setuptools-scm>=3.0.6
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# requirements for running the tests via pytest
|
||||
pytest>=2.9
|
||||
pytest>=2.9,<3.7 # <3.7 because of incompatible pluggy requirement
|
||||
pytest-pythonpath>=0.7.1
|
||||
pytest-cov>=2.5.1
|
||||
pytest-xdist>=1.22.0
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# requirements for building and running tox
|
||||
tox>=2.9.1,<3.0.0
|
||||
detox>=0.11
|
||||
tox>=3.1.2
|
||||
|
||||
-r setup.txt
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
python-3.6.4
|
||||
python-3.6.6
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import distutils.core
|
||||
from __future__ import print_function
|
||||
|
||||
import io
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
@@ -22,88 +21,67 @@ requirements_setup = read_req('setup.txt')
|
||||
requirements_validation = read_req('validation.txt')
|
||||
|
||||
|
||||
def _install_setup_requires(attrs):
|
||||
# copied from setuptools
|
||||
dist = distutils.core.Distribution(dict(
|
||||
(k, v) for k, v in attrs.items()
|
||||
if k in ('dependency_links', 'setup_requires')
|
||||
))
|
||||
# Honor setup.cfg's options.
|
||||
dist.parse_config_files(ignore_option_errors=True)
|
||||
if dist.setup_requires:
|
||||
dist.fetch_build_eggs(dist.setup_requires)
|
||||
def drf_yasg_setup(**kwargs):
|
||||
setup(
|
||||
name='drf-yasg',
|
||||
packages=find_packages('src'),
|
||||
package_dir={'': 'src'},
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
setup_requires=requirements_setup,
|
||||
extras_require={
|
||||
'validation': requirements_validation,
|
||||
},
|
||||
license='BSD License',
|
||||
description='Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code.',
|
||||
long_description=description,
|
||||
url='https://github.com/axnsan12/drf-yasg',
|
||||
author='Cristi V.',
|
||||
author_email='cristi@cvjd.me',
|
||||
keywords='drf django django-rest-framework schema swagger openapi codegen swagger-codegen '
|
||||
'documentation drf-yasg django-rest-swagger drf-openapi',
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||
classifiers=[
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Operating System :: OS Independent',
|
||||
'Environment :: Web Environment',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Framework :: Django',
|
||||
'Framework :: Django :: 1.11',
|
||||
'Framework :: Django :: 2.0',
|
||||
'Framework :: Django :: 2.1',
|
||||
'Topic :: Documentation',
|
||||
'Topic :: Software Development :: Code Generators',
|
||||
],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
# try to install setuptools_scm before setuptools does it, otherwise our monkey patch below will come too early
|
||||
# (setuptools_scm adds find_files hooks into setuptools on install)
|
||||
_install_setup_requires({'setup_requires': requirements_setup})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if 'sdist' in sys.argv:
|
||||
try:
|
||||
# see https://github.com/pypa/setuptools_scm/issues/190, setuptools_scm includes ALL versioned files from
|
||||
# the git repo into the sdist by default, and there is no easy way to provide an opt-out;
|
||||
# this hack is ugly but does the job; because this is not really a documented interface of the module,
|
||||
# the setuptools_scm version should remain pinned to ensure it keeps working
|
||||
import setuptools_scm.integration
|
||||
|
||||
setuptools_scm.integration.find_files = lambda _: []
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
# this is a workaround for being able to install the package from source without working from a git checkout
|
||||
# it is needed for building succesfully on Heroku
|
||||
from setuptools_scm import get_version
|
||||
|
||||
version = get_version()
|
||||
version_kwargs = {'use_scm_version': True}
|
||||
except LookupError:
|
||||
if 'sdist' in sys.argv or 'bdist_wheel' in sys.argv:
|
||||
drf_yasg_setup(use_scm_version=True)
|
||||
except LookupError as e:
|
||||
if os.getenv('CI', 'false') == 'true' or os.getenv('TRAVIS', 'false') == 'true':
|
||||
# don't silently fail on travis - we don't want to accidentally push a dummy version to PyPI
|
||||
raise
|
||||
|
||||
rnd = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
|
||||
version_kwargs = {'version': '0.0.0.dummy+' + rnd}
|
||||
if 'setuptools-scm' in str(e):
|
||||
import time
|
||||
|
||||
setup(
|
||||
name='drf-yasg',
|
||||
packages=find_packages('src'),
|
||||
package_dir={'': 'src'},
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
setup_requires=requirements_setup,
|
||||
extras_require={
|
||||
'validation': requirements_validation,
|
||||
},
|
||||
license='BSD License',
|
||||
description='Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code.',
|
||||
long_description=description,
|
||||
url='https://github.com/axnsan12/drf-yasg',
|
||||
author='Cristi V.',
|
||||
author_email='cristi@cvjd.me',
|
||||
keywords='drf django django-rest-framework schema swagger openapi codegen swagger-codegen '
|
||||
'documentation drf-yasg django-rest-swagger drf-openapi',
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||
classifiers=[
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Development Status :: 4 - Beta',
|
||||
'Operating System :: OS Independent',
|
||||
'Environment :: Web Environment',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Framework :: Django',
|
||||
'Framework :: Django :: 1.11',
|
||||
'Framework :: Django :: 2.0',
|
||||
'Topic :: Documentation',
|
||||
'Topic :: Software Development :: Code Generators',
|
||||
],
|
||||
**version_kwargs
|
||||
)
|
||||
timestamp_ms = int(time.time() * 1000)
|
||||
timestamp_str = hex(timestamp_ms)[2:].zfill(16)
|
||||
dummy_version = '0.0.0rc0+noscm' + timestamp_str
|
||||
|
||||
drf_yasg_setup(version=dummy_version)
|
||||
print(str(e), file=sys.stderr)
|
||||
print("failed to detect version, build was done using dummy version " + dummy_version, file=sys.stderr)
|
||||
else:
|
||||
raise
|
||||
|
||||
@@ -14,6 +14,7 @@ SWAGGER_DEFAULTS = {
|
||||
'drf_yasg.inspectors.DictFieldInspector',
|
||||
'drf_yasg.inspectors.HiddenFieldInspector',
|
||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||
'drf_yasg.inspectors.SerializerMethodFieldInspector',
|
||||
'drf_yasg.inspectors.SimpleFieldInspector',
|
||||
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
||||
],
|
||||
@@ -25,6 +26,8 @@ SWAGGER_DEFAULTS = {
|
||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
],
|
||||
|
||||
'EXCLUDED_MEDIA_TYPES': ['html'],
|
||||
|
||||
'DEFAULT_INFO': None,
|
||||
'DEFAULT_API_URL': None,
|
||||
|
||||
@@ -37,6 +40,7 @@ SWAGGER_DEFAULTS = {
|
||||
'SECURITY_REQUIREMENTS': None,
|
||||
'LOGIN_URL': getattr(settings, 'LOGIN_URL', None),
|
||||
'LOGOUT_URL': getattr(settings, 'LOGOUT_URL', None),
|
||||
'SPEC_URL': None,
|
||||
'VALIDATOR_URL': '',
|
||||
|
||||
'OPERATIONS_SORTER': None,
|
||||
@@ -59,13 +63,17 @@ SWAGGER_DEFAULTS = {
|
||||
'patch',
|
||||
'trace'
|
||||
],
|
||||
'DISPLAY_OPERATION_ID': True,
|
||||
}
|
||||
|
||||
REDOC_DEFAULTS = {
|
||||
'LAZY_RENDERING': True,
|
||||
'SPEC_URL': None,
|
||||
'LAZY_RENDERING': False,
|
||||
'HIDE_HOSTNAME': False,
|
||||
'EXPAND_RESPONSES': 'all',
|
||||
'PATH_IN_MIDDLE': False,
|
||||
'NATIVE_SCROLLBARS': False,
|
||||
'REQUIRED_PROPS_FIRST': False,
|
||||
}
|
||||
|
||||
IMPORT_STRINGS = [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from future.utils import raise_from
|
||||
from six import raise_from
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
||||
+13
-15
@@ -5,7 +5,6 @@ from collections import OrderedDict, defaultdict
|
||||
|
||||
import uritemplate
|
||||
from coreapi.compat import urlparse
|
||||
from django.utils.encoding import force_text
|
||||
from rest_framework import versioning
|
||||
from rest_framework.compat import URLPattern, URLResolver, get_original_route
|
||||
from rest_framework.schemas.generators import EndpointEnumerator as _EndpointEnumerator
|
||||
@@ -16,9 +15,9 @@ from rest_framework.settings import api_settings as rest_framework_settings
|
||||
from . import openapi
|
||||
from .app_settings import swagger_settings
|
||||
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 .utils import get_consumes, get_produces
|
||||
from .utils import force_real_str, get_consumes, get_produces
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -32,8 +31,7 @@ class EndpointEnumerator(_EndpointEnumerator):
|
||||
|
||||
def get_path_from_regex(self, path_regex):
|
||||
if path_regex.endswith(')'):
|
||||
logger.warning("url pattern does not end in $ ('%s') - unexpected things might happen",
|
||||
path_regex)
|
||||
logger.warning("url pattern does not end in $ ('%s') - unexpected things might happen", path_regex)
|
||||
return self.unescape_path(super(EndpointEnumerator, self).get_path_from_regex(path_regex))
|
||||
|
||||
def should_include_endpoint(self, path, callback, app_name='', namespace='', url_name=None):
|
||||
@@ -72,7 +70,7 @@ class EndpointEnumerator(_EndpointEnumerator):
|
||||
|
||||
return path
|
||||
|
||||
def get_api_endpoints(self, patterns=None, prefix='', app_name=None, namespace=None, previously_seen_endpoints=None):
|
||||
def get_api_endpoints(self, patterns=None, prefix='', app_name=None, namespace=None, ignored_endpoints=None):
|
||||
"""
|
||||
Return a list of all available API endpoints by inspecting the URL conf.
|
||||
|
||||
@@ -82,8 +80,8 @@ class EndpointEnumerator(_EndpointEnumerator):
|
||||
patterns = self.patterns
|
||||
|
||||
api_endpoints = []
|
||||
if previously_seen_endpoints is None:
|
||||
previously_seen_endpoints = set()
|
||||
if ignored_endpoints is None:
|
||||
ignored_endpoints = set()
|
||||
|
||||
for pattern in patterns:
|
||||
path_regex = prefix + get_original_route(pattern)
|
||||
@@ -97,9 +95,9 @@ class EndpointEnumerator(_EndpointEnumerator):
|
||||
|
||||
# avoid adding endpoints that have already been seen,
|
||||
# as Django resolves urls in top-down order
|
||||
if path in previously_seen_endpoints:
|
||||
if path in ignored_endpoints:
|
||||
continue
|
||||
previously_seen_endpoints.add(path)
|
||||
ignored_endpoints.add(path)
|
||||
|
||||
for method in self.get_allowed_methods(callback):
|
||||
endpoint = (path, method, callback)
|
||||
@@ -113,7 +111,7 @@ class EndpointEnumerator(_EndpointEnumerator):
|
||||
prefix=path_regex,
|
||||
app_name="%s:%s" % (app_name, pattern.app_name) if app_name else pattern.app_name,
|
||||
namespace="%s:%s" % (namespace, pattern.namespace) if namespace else pattern.namespace,
|
||||
previously_seen_endpoints=previously_seen_endpoints
|
||||
ignored_endpoints=ignored_endpoints
|
||||
)
|
||||
api_endpoints.extend(nested_endpoints)
|
||||
else:
|
||||
@@ -136,7 +134,7 @@ class EndpointEnumerator(_EndpointEnumerator):
|
||||
"""Remove backslashe escapes from all path components outside {parameters}. This is needed because
|
||||
``simplify_regex`` does not handle this correctly - note however that this implementation is
|
||||
|
||||
**NOTE:** this might destructively affect some url regex patterns that contain metacharacters (e.g. \w, \d)
|
||||
**NOTE:** this might destructively affect some url regex patterns that contain metacharacters (e.g. \\w, \\d)
|
||||
outside path parameter groups; if you are in this category, God help you
|
||||
|
||||
:param str path: path possibly containing
|
||||
@@ -426,7 +424,7 @@ class OpenAPISchemaGenerator(object):
|
||||
:rtype: list[openapi.Parameter]
|
||||
"""
|
||||
parameters = []
|
||||
queryset = getattr(view_cls, 'queryset', None)
|
||||
queryset = get_queryset_from_view(view_cls)
|
||||
|
||||
for variable in sorted(uritemplate.variables(path)):
|
||||
model, model_field = get_queryset_field(queryset, variable)
|
||||
@@ -435,7 +433,7 @@ class OpenAPISchemaGenerator(object):
|
||||
attrs['pattern'] = getattr(view_cls, 'lookup_value_regex', attrs.get('pattern', None))
|
||||
|
||||
if model_field and getattr(model_field, 'help_text', False):
|
||||
description = force_text(model_field.help_text)
|
||||
description = model_field.help_text
|
||||
elif model_field and getattr(model_field, 'primary_key', False):
|
||||
description = get_pk_description(model, model_field)
|
||||
else:
|
||||
@@ -443,7 +441,7 @@ class OpenAPISchemaGenerator(object):
|
||||
|
||||
field = openapi.Parameter(
|
||||
name=variable,
|
||||
description=description,
|
||||
description=force_real_str(description),
|
||||
required=True,
|
||||
in_=openapi.IN_PATH,
|
||||
**attrs
|
||||
|
||||
@@ -5,12 +5,12 @@ from .base import (
|
||||
from .field import (
|
||||
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
||||
InlineSerializerInspector, RecursiveFieldInspector, ReferencingSerializerInspector, RelatedFieldInspector,
|
||||
SimpleFieldInspector, StringDefaultFieldInspector
|
||||
SerializerMethodFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector
|
||||
)
|
||||
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
||||
from .view import SwaggerAutoSchema
|
||||
|
||||
# these settings must be accesed only after definig/importing all the classes in this module to avoid ImportErrors
|
||||
# these settings must be accessed only after defining/importing all the classes in this module to avoid ImportErrors
|
||||
ViewInspector.field_inspectors = swagger_settings.DEFAULT_FIELD_INSPECTORS
|
||||
ViewInspector.filter_inspectors = swagger_settings.DEFAULT_FILTER_INSPECTORS
|
||||
ViewInspector.paginator_inspectors = swagger_settings.DEFAULT_PAGINATOR_INSPECTORS
|
||||
@@ -25,7 +25,7 @@ __all__ = [
|
||||
# field inspectors
|
||||
'InlineSerializerInspector', 'RecursiveFieldInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector',
|
||||
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector',
|
||||
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector',
|
||||
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector', 'SerializerMethodFieldInspector',
|
||||
|
||||
# view inspectors
|
||||
'SwaggerAutoSchema',
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import inspect
|
||||
import logging
|
||||
|
||||
from django.utils.encoding import force_text
|
||||
from rest_framework import serializers
|
||||
from rest_framework.utils import encoders, json
|
||||
|
||||
from .. import openapi
|
||||
from ..utils import decimal_as_float, is_list_view
|
||||
from ..utils import force_real_str, get_field_default, is_list_view
|
||||
|
||||
#: Sentinel value that inspectors must return to signal that they do not know how to handle an object
|
||||
NotHandled = object()
|
||||
@@ -136,6 +134,19 @@ class FieldInspector(BaseInspector):
|
||||
super(FieldInspector, self).__init__(view, path, method, components, request)
|
||||
self.field_inspectors = field_inspectors
|
||||
|
||||
def add_manual_fields(self, serializer_or_field, schema):
|
||||
"""Set fields from the ``swagger_schem_fields`` attribute on the Meta class. This method is called
|
||||
only for serializers or fields that are converted into ``openapi.Schema`` objects.
|
||||
|
||||
:param serializer_or_field: serializer or field instance
|
||||
:param openapi.Schema schema: the schema object to be modified in-place
|
||||
"""
|
||||
meta = getattr(serializer_or_field, 'Meta', None)
|
||||
swagger_schema_fields = getattr(meta, 'swagger_schema_fields', {})
|
||||
if swagger_schema_fields:
|
||||
for attr, val in swagger_schema_fields.items():
|
||||
setattr(schema, attr, val)
|
||||
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||
"""Convert a drf Serializer or Field instance into a Swagger object.
|
||||
|
||||
@@ -196,9 +207,9 @@ class FieldInspector(BaseInspector):
|
||||
"""
|
||||
assert swagger_object_type in (openapi.Schema, openapi.Parameter, openapi.Items)
|
||||
assert not isinstance(field, openapi.SwaggerDict), "passed field is already a SwaggerDict object"
|
||||
title = force_text(field.label) if field.label else None
|
||||
title = force_real_str(field.label) if field.label else None
|
||||
title = title if swagger_object_type == openapi.Schema else None # only Schema has title
|
||||
description = force_text(field.help_text) if field.help_text else None
|
||||
description = force_real_str(field.help_text) if field.help_text else None
|
||||
description = description if swagger_object_type != openapi.Items else None # Items has no description either
|
||||
|
||||
def SwaggerType(existing_object=None, **instance_kwargs):
|
||||
@@ -206,46 +217,29 @@ class FieldInspector(BaseInspector):
|
||||
instance_kwargs['required'] = field.required
|
||||
|
||||
if 'default' not in instance_kwargs and swagger_object_type != openapi.Items:
|
||||
default = getattr(field, 'default', serializers.empty)
|
||||
if default is not serializers.empty:
|
||||
if callable(default):
|
||||
try:
|
||||
if hasattr(default, 'set_context'):
|
||||
default.set_context(field)
|
||||
default = default()
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning("default for %s is callable but it raised an exception when "
|
||||
"called; 'default' field will not be added to schema", field, exc_info=True)
|
||||
default = None
|
||||
|
||||
if default is not None:
|
||||
try:
|
||||
default = field.to_representation(default)
|
||||
# JSON roundtrip ensures that the value is valid JSON;
|
||||
# for example, sets and tuples get transformed into lists
|
||||
default = json.loads(json.dumps(default, cls=encoders.JSONEncoder))
|
||||
if decimal_as_float(field):
|
||||
default = float(default)
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning("'default' on schema for %s will not be set because "
|
||||
"to_representation raised an exception", field, exc_info=True)
|
||||
default = None
|
||||
|
||||
if default is not None:
|
||||
instance_kwargs['default'] = default
|
||||
default = get_field_default(field)
|
||||
if default not in (None, serializers.empty):
|
||||
instance_kwargs['default'] = default
|
||||
|
||||
if instance_kwargs.get('type', None) != openapi.TYPE_ARRAY:
|
||||
instance_kwargs.setdefault('title', title)
|
||||
instance_kwargs.setdefault('description', description)
|
||||
if description is not None:
|
||||
instance_kwargs.setdefault('description', description)
|
||||
instance_kwargs.update(kwargs)
|
||||
|
||||
if existing_object is not None:
|
||||
assert isinstance(existing_object, swagger_object_type)
|
||||
for attr, val in sorted(instance_kwargs.items()):
|
||||
setattr(existing_object, attr, val)
|
||||
return existing_object
|
||||
for key, val in sorted(instance_kwargs.items()):
|
||||
setattr(existing_object, key, val)
|
||||
result = existing_object
|
||||
else:
|
||||
result = swagger_object_type(**instance_kwargs)
|
||||
|
||||
return swagger_object_type(**instance_kwargs)
|
||||
# Provide an option to add manual paremeters to a schema
|
||||
# for example, to add examples
|
||||
if swagger_object_type == openapi.Schema:
|
||||
self.add_manual_fields(field, result)
|
||||
return result
|
||||
|
||||
# arrays in Schema have Schema elements, arrays in Parameter and Items have Items elements
|
||||
child_swagger_type = openapi.Schema if swagger_object_type == openapi.Schema else openapi.Items
|
||||
@@ -264,7 +258,7 @@ class SerializerInspector(FieldInspector):
|
||||
return NotHandled
|
||||
|
||||
def get_request_parameters(self, serializer, in_):
|
||||
"""Convert a DRF serializer into a list of :class:`.Parameter`\ s.
|
||||
"""Convert a DRF serializer into a list of :class:`.Parameter`\\ s.
|
||||
|
||||
Should return :data:`.NotHandled` if this inspector does not know how to handle the given `serializer`.
|
||||
|
||||
@@ -371,14 +365,14 @@ class ViewInspector(BaseInspector):
|
||||
|
||||
:param serializers.BaseSerializer serializer: the ``Serializer`` instance
|
||||
:returns: the converted :class:`.Schema`, or ``None`` in case of an unknown serializer
|
||||
:rtype: openapi.Schema,openapi.SchemaRef,None
|
||||
:rtype: openapi.Schema,openapi.SchemaRef
|
||||
"""
|
||||
return self.probe_inspectors(
|
||||
self.field_inspectors, 'get_schema', serializer, {'field_inspectors': self.field_inspectors}
|
||||
)
|
||||
|
||||
def serializer_to_parameters(self, serializer, in_):
|
||||
"""Convert a serializer to a possibly empty list of :class:`.Parameter`\ s.
|
||||
"""Convert a serializer to a possibly empty list of :class:`.Parameter`\\ s.
|
||||
|
||||
:param serializers.BaseSerializer serializer: the ``Serializer`` instance
|
||||
:param str in_: the location of the parameters, one of the `openapi.IN_*` constants
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import datetime
|
||||
import inspect
|
||||
import logging
|
||||
import operator
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
from inspect import isclass
|
||||
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
@@ -10,9 +14,15 @@ from rest_framework.settings import api_settings as rest_framework_settings
|
||||
|
||||
from .. import openapi
|
||||
from ..errors import SwaggerGenerationError
|
||||
from ..utils import decimal_as_float, filter_none, get_serializer_ref_name
|
||||
from ..utils import decimal_as_float, filter_none, get_serializer_class, get_serializer_ref_name
|
||||
from .base import FieldInspector, NotHandled, SerializerInspector
|
||||
|
||||
try:
|
||||
# Python>=3.5
|
||||
import typing
|
||||
except ImportError:
|
||||
typing = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -22,19 +32,6 @@ class InlineSerializerInspector(SerializerInspector):
|
||||
#: whether to output :class:`.Schema` definitions inline or into the ``definitions`` section
|
||||
use_definitions = False
|
||||
|
||||
def add_manual_fields(self, serializer, schema):
|
||||
"""Set fields from the ``swagger_schem_fields`` attribute on the serializer's Meta class. This method is called
|
||||
only for serializers that are converted into ``openapi.Schema`` objects.
|
||||
|
||||
:param serializer: serializer instance
|
||||
:param openapi.Schema schema: the schema object to be modified in-place
|
||||
"""
|
||||
serializer_meta = getattr(serializer, 'Meta', None)
|
||||
swagger_schema_fields = getattr(serializer_meta, 'swagger_schema_fields', {})
|
||||
if swagger_schema_fields:
|
||||
for attr, val in swagger_schema_fields.items():
|
||||
setattr(schema, attr, val)
|
||||
|
||||
def get_schema(self, serializer):
|
||||
return self.probe_field_inspectors(serializer, openapi.Schema, self.use_definitions)
|
||||
|
||||
@@ -111,20 +108,25 @@ class InlineSerializerInspector(SerializerInspector):
|
||||
)
|
||||
if not ref_name and 'title' in result:
|
||||
# on an inline model, the title is derived from the field name
|
||||
# but is visually displayed like the model name, which is confusing
|
||||
# but is visno coverually displayed like the model name, which is confusing
|
||||
# it is better to just remove title from inline models
|
||||
del result.title
|
||||
|
||||
# Provide an option to add manual paremeters to a schema
|
||||
# for example, to add examples
|
||||
self.add_manual_fields(field, result)
|
||||
return result
|
||||
|
||||
if not ref_name or not use_references:
|
||||
return make_schema_definition()
|
||||
|
||||
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
|
||||
definitions.setdefault(ref_name, make_schema_definition)
|
||||
actual_schema = definitions.setdefault(ref_name, make_schema_definition)
|
||||
actual_schema._remove_read_only()
|
||||
|
||||
actual_serializer = get_serializer_class(getattr(actual_schema, '_serializer', None))
|
||||
this_serializer = get_serializer_class(field)
|
||||
if actual_serializer and actual_serializer != this_serializer: # pragma: no cover
|
||||
logger.warning("Schema for %s will override distinct serializer %s because they "
|
||||
"share the same ref_name", actual_serializer, this_serializer)
|
||||
|
||||
return openapi.SchemaRef(definitions, ref_name)
|
||||
|
||||
return NotHandled
|
||||
@@ -163,6 +165,25 @@ def get_model_field(model, field_name):
|
||||
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):
|
||||
"""Get the nearest parent ``Serializer`` instance for the given field.
|
||||
|
||||
@@ -195,7 +216,7 @@ def get_related_model(model, source):
|
||||
|
||||
|
||||
class RelatedFieldInspector(FieldInspector):
|
||||
"""Provides conversions for ``RelatedField``\ s."""
|
||||
"""Provides conversions for ``RelatedField``\\ s."""
|
||||
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
||||
@@ -229,10 +250,18 @@ class RelatedFieldInspector(FieldInspector):
|
||||
else:
|
||||
# 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
|
||||
view_queryset = getattr(self.view, 'queryset', None)
|
||||
serializer_meta = getattr(get_parent_serializer(field), 'Meta', None)
|
||||
this_model = getattr(view_queryset, 'model', None) or getattr(serializer_meta, 'model', None)
|
||||
parent_serializer = get_parent_serializer(field)
|
||||
|
||||
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
|
||||
if not source and isinstance(field.parent, serializers.ManyRelatedField):
|
||||
source = field.parent.field_name
|
||||
|
||||
model = get_related_model(this_model, source)
|
||||
model_field = get_model_field(model, target_field)
|
||||
|
||||
@@ -254,13 +283,22 @@ def find_regex(regex_field):
|
||||
regex_validator = None
|
||||
for validator in regex_field.validators:
|
||||
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:
|
||||
# bail if multiple validators are found - no obvious way to choose
|
||||
return None # pragma: no cover
|
||||
regex_validator = validator
|
||||
|
||||
# regex_validator.regex should be a compiled re object...
|
||||
pattern = getattr(getattr(regex_validator, 'regex', None), 'pattern', None)
|
||||
try:
|
||||
pattern = getattr(getattr(regex_validator, 'regex', None), 'pattern', None)
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning('failed to compile regex validator of ' + str(regex_field), exc_info=True)
|
||||
return None
|
||||
|
||||
if pattern:
|
||||
# attempt some basic cleanup to remove regex constructs not supported by JavaScript
|
||||
# -- swagger uses javascript-style regexes - see https://github.com/swagger-api/swagger-editor/issues/1601
|
||||
@@ -394,7 +432,7 @@ def get_basic_type_info(field):
|
||||
return 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)
|
||||
|
||||
limits = find_limits(field)
|
||||
@@ -409,6 +447,120 @@ def get_basic_type_info(field):
|
||||
return result
|
||||
|
||||
|
||||
def decimal_return_type():
|
||||
return openapi.TYPE_STRING if rest_framework_settings.COERCE_DECIMAL_TO_STRING else openapi.TYPE_NUMBER
|
||||
|
||||
|
||||
raw_type_info = [
|
||||
(bool, (openapi.TYPE_BOOLEAN, None)),
|
||||
(int, (openapi.TYPE_INTEGER, None)),
|
||||
(float, (openapi.TYPE_NUMBER, None)),
|
||||
(Decimal, (decimal_return_type, openapi.FORMAT_DECIMAL)),
|
||||
(uuid.UUID, (openapi.TYPE_STRING, openapi.FORMAT_UUID)),
|
||||
(datetime.datetime, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)),
|
||||
(datetime.date, (openapi.TYPE_STRING, openapi.FORMAT_DATE)),
|
||||
# TODO - support typing.List etc
|
||||
]
|
||||
|
||||
hinting_type_info = raw_type_info
|
||||
|
||||
|
||||
def get_basic_type_info_from_hint(hint_class):
|
||||
"""Given a class (eg from a SerializerMethodField's return type hint,
|
||||
return its basic type information - ``type``, ``format``, ``pattern``,
|
||||
and any applicable min/max limit values.
|
||||
|
||||
:param hint_class: the class
|
||||
:return: the extracted attributes as a dictionary, or ``None`` if the field type is not known
|
||||
:rtype: OrderedDict
|
||||
"""
|
||||
|
||||
for check_class, type_format in hinting_type_info:
|
||||
if issubclass(hint_class, check_class):
|
||||
swagger_type, format = type_format
|
||||
if callable(swagger_type):
|
||||
swagger_type = swagger_type()
|
||||
# if callable(format):
|
||||
# format = format(klass)
|
||||
break
|
||||
else: # pragma: no cover
|
||||
return None
|
||||
|
||||
pattern = None
|
||||
|
||||
result = OrderedDict([
|
||||
('type', swagger_type),
|
||||
('format', format),
|
||||
('pattern', pattern)
|
||||
])
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class SerializerMethodFieldInspector(FieldInspector):
|
||||
"""Provides conversion for SerializerMethodField, optionally using information from the swagger_serializer_method
|
||||
decorator.
|
||||
"""
|
||||
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||
if not isinstance(field, serializers.SerializerMethodField):
|
||||
return NotHandled
|
||||
|
||||
method = getattr(field.parent, field.method_name)
|
||||
if method is None:
|
||||
return NotHandled
|
||||
|
||||
serializer = getattr(method, "_swagger_serializer", None)
|
||||
|
||||
if serializer:
|
||||
# attribute added by the swagger_serializer_method decorator
|
||||
serializer = getattr(method, '_swagger_serializer', None)
|
||||
|
||||
# in order of preference for description, use:
|
||||
# 1) field.help_text from SerializerMethodField(help_text)
|
||||
# 2) serializer.help_text from swagger_serializer_method(serializer)
|
||||
# 3) method's docstring
|
||||
description = field.help_text
|
||||
if description is None:
|
||||
description = getattr(serializer, 'help_text', None)
|
||||
if description is None:
|
||||
description = method.__doc__
|
||||
|
||||
label = field.label
|
||||
if label is None:
|
||||
label = getattr(serializer, 'label', None)
|
||||
|
||||
if inspect.isclass(serializer):
|
||||
serializer_kwargs = {
|
||||
"help_text": description,
|
||||
"label": label,
|
||||
"read_only": True,
|
||||
}
|
||||
|
||||
serializer = method._swagger_serializer(**serializer_kwargs)
|
||||
else:
|
||||
serializer.help_text = description
|
||||
serializer.label = label
|
||||
serializer.read_only = True
|
||||
|
||||
return self.probe_field_inspectors(serializer, swagger_object_type, use_references, read_only=True)
|
||||
elif typing:
|
||||
# look for Python 3.5+ style type hinting of the return value
|
||||
hint_class = inspect.signature(method).return_annotation
|
||||
|
||||
if not isclass(hint_class) and hasattr(hint_class, '__args__'):
|
||||
hint_class = hint_class.__args__[0]
|
||||
if isclass(hint_class) and not issubclass(hint_class, inspect._empty):
|
||||
type_info = get_basic_type_info_from_hint(hint_class)
|
||||
|
||||
if type_info is not None:
|
||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type,
|
||||
use_references, **kwargs)
|
||||
return SwaggerType(**type_info)
|
||||
|
||||
return NotHandled
|
||||
|
||||
|
||||
class SimpleFieldInspector(FieldInspector):
|
||||
"""Provides conversions for fields which can be described using just ``type``, ``format``, ``pattern``
|
||||
and min/max validators.
|
||||
@@ -457,7 +609,7 @@ class ChoiceFieldInspector(FieldInspector):
|
||||
|
||||
|
||||
class FileFieldInspector(FieldInspector):
|
||||
"""Provides conversions for ``FileField``\ s."""
|
||||
"""Provides conversions for ``FileField``\\ s."""
|
||||
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
||||
@@ -569,14 +721,15 @@ except ImportError: # pragma: no cover
|
||||
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"
|
||||
assert ref_name is not None, "Can't create RecursiveField schema for inline " + str(type(field.proxied))
|
||||
|
||||
return openapi.SchemaRef(self.components.with_scope(openapi.SCHEMA_DEFINITIONS), ref_name,
|
||||
ignore_unresolved=True)
|
||||
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
|
||||
return openapi.SchemaRef(definitions, ref_name, ignore_unresolved=True)
|
||||
|
||||
return NotHandled
|
||||
|
||||
@@ -4,11 +4,12 @@ import coreschema
|
||||
from rest_framework.pagination import CursorPagination, LimitOffsetPagination, PageNumberPagination
|
||||
|
||||
from .. import openapi
|
||||
from ..utils import force_real_str
|
||||
from .base import FilterInspector, PaginatorInspector
|
||||
|
||||
|
||||
class CoreAPICompatInspector(PaginatorInspector, FilterInspector):
|
||||
"""Converts ``coreapi.Field``\ s to :class:`.openapi.Parameter`\ s for filters and paginators that implement a
|
||||
"""Converts ``coreapi.Field``\\ s to :class:`.openapi.Parameter`\\ s for filters and paginators that implement a
|
||||
``get_schema_fields`` method.
|
||||
"""
|
||||
|
||||
@@ -48,7 +49,7 @@ class CoreAPICompatInspector(PaginatorInspector, FilterInspector):
|
||||
in_=location_to_in[field.location],
|
||||
type=coreapi_types.get(type(field.schema), openapi.TYPE_STRING),
|
||||
required=field.required,
|
||||
description=field.schema.description if field.schema else None,
|
||||
description=force_real_str(field.schema.description) if field.schema else None,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ from rest_framework.status import is_success
|
||||
from .. import openapi
|
||||
from ..errors import SwaggerGenerationError
|
||||
from ..utils import (
|
||||
force_serializer_instance, get_consumes, get_produces, guess_response_status, is_list_view, no_body,
|
||||
param_list_to_odict
|
||||
force_real_str, force_serializer_instance, get_consumes, get_produces, guess_response_status, is_list_view,
|
||||
merge_params, no_body, param_list_to_odict
|
||||
)
|
||||
from .base import ViewInspector
|
||||
|
||||
@@ -34,21 +34,25 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
|
||||
operation_id = self.get_operation_id(operation_keys)
|
||||
description = self.get_description()
|
||||
summary = self.get_summary()
|
||||
security = self.get_security()
|
||||
assert security is None or isinstance(security, list), "security must be a list of securiy requirement objects"
|
||||
deprecated = self.is_deprecated()
|
||||
tags = self.get_tags(operation_keys)
|
||||
|
||||
responses = self.get_responses()
|
||||
|
||||
return openapi.Operation(
|
||||
operation_id=operation_id,
|
||||
description=description,
|
||||
description=force_real_str(description),
|
||||
summary=force_real_str(summary),
|
||||
responses=responses,
|
||||
parameters=parameters,
|
||||
consumes=consumes,
|
||||
produces=produces,
|
||||
tags=tags,
|
||||
security=security
|
||||
security=security,
|
||||
deprecated=deprecated
|
||||
)
|
||||
|
||||
def get_request_body_parameters(self, consumes):
|
||||
@@ -59,7 +63,7 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
- a list of primitive Parameters parsed as form data
|
||||
|
||||
:param list[str] consumes: a list of accepted MIME types as returned by :meth:`.get_consumes`
|
||||
:return: a (potentially empty) list of :class:`.Parameter`\ s either ``in: body`` or ``in: formData``
|
||||
:return: a (potentially empty) list of :class:`.Parameter`\\ s either ``in: body`` or ``in: formData``
|
||||
:rtype: list[openapi.Parameter]
|
||||
"""
|
||||
serializer = self.get_request_serializer()
|
||||
@@ -93,29 +97,42 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
self.method, self.path, type(self.view).__name__, exc_info=True)
|
||||
return None
|
||||
|
||||
def get_request_serializer(self):
|
||||
"""Return the request serializer (used for parsing the request payload) for this endpoint.
|
||||
def _get_request_body_override(self):
|
||||
"""Parse the request_body key in the override dict. This method is not public API.
|
||||
|
||||
:return: the request serializer, or one of :class:`.Schema`, :class:`.SchemaRef`, ``None``
|
||||
:return:
|
||||
"""
|
||||
body_override = self.overrides.get('request_body', None)
|
||||
|
||||
if body_override is not None:
|
||||
if body_override is no_body:
|
||||
return None
|
||||
return no_body
|
||||
if self.method not in self.body_methods:
|
||||
raise SwaggerGenerationError("request_body can only be applied to (" + ','.join(self.body_methods) +
|
||||
"); are you looking for query_serializer or manual_parameters?")
|
||||
if isinstance(body_override, openapi.Schema.OR_REF):
|
||||
return body_override
|
||||
return force_serializer_instance(body_override)
|
||||
elif self.method in self.implicit_body_methods:
|
||||
|
||||
return body_override
|
||||
|
||||
def get_request_serializer(self):
|
||||
"""Return the request serializer (used for parsing the request payload) for this endpoint.
|
||||
|
||||
:return: the request serializer, or one of :class:`.Schema`, :class:`.SchemaRef`, ``None``
|
||||
"""
|
||||
body_override = self._get_request_body_override()
|
||||
|
||||
if body_override is None and self.method in self.implicit_body_methods:
|
||||
return self.get_view_serializer()
|
||||
|
||||
return None
|
||||
if body_override is no_body:
|
||||
return None
|
||||
|
||||
return body_override
|
||||
|
||||
def get_request_form_parameters(self, serializer):
|
||||
"""Given a Serializer, return a list of ``in: formData`` :class:`.Parameter`\ s.
|
||||
"""Given a Serializer, return a list of ``in: formData`` :class:`.Parameter`\\ s.
|
||||
|
||||
:param serializer: the view's request serializer as returned by :meth:`.get_request_serializer`
|
||||
:rtype: list[openapi.Parameter]
|
||||
@@ -145,21 +162,19 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
:return: modified parameters
|
||||
:rtype: list[openapi.Parameter]
|
||||
"""
|
||||
parameters = param_list_to_odict(parameters)
|
||||
manual_parameters = self.overrides.get('manual_parameters', None) or []
|
||||
|
||||
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")
|
||||
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; "
|
||||
"did you forget to set an appropriate parser class on the view?")
|
||||
if self.method not in self.body_methods:
|
||||
raise SwaggerGenerationError("form parameters can only be applied to (" + ','.join(self.body_methods) +
|
||||
") HTTP methods")
|
||||
|
||||
parameters.update(param_list_to_odict(manual_parameters))
|
||||
return list(parameters.values())
|
||||
return merge_params(parameters, manual_parameters)
|
||||
|
||||
def get_responses(self):
|
||||
"""Get the possible responses for this view as a swagger :class:`.Responses` object.
|
||||
@@ -172,6 +187,18 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
responses=self.get_response_schemas(response_serializers)
|
||||
)
|
||||
|
||||
def get_default_response_serializer(self):
|
||||
"""Return the default response serializer for this endpoint. This is derived from either the ``request_body``
|
||||
override or the request serializer (:meth:`.get_view_serializer`).
|
||||
|
||||
:return: response serializer, :class:`.Schema`, :class:`.SchemaRef`, ``None``
|
||||
"""
|
||||
body_override = self._get_request_body_override()
|
||||
if body_override and body_override is not no_body:
|
||||
return body_override
|
||||
|
||||
return self.get_view_serializer()
|
||||
|
||||
def get_default_responses(self):
|
||||
"""Get the default responses determined for this view from the request serializer and request method.
|
||||
|
||||
@@ -182,11 +209,9 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
default_status = guess_response_status(method)
|
||||
default_schema = ''
|
||||
if method in ('get', 'post', 'put', 'patch'):
|
||||
default_schema = self.get_request_serializer() or self.get_view_serializer()
|
||||
default_schema = self.get_default_response_serializer()
|
||||
|
||||
default_schema = default_schema or ''
|
||||
if any(is_form_media_type(encoding) for encoding in self.get_consumes()):
|
||||
default_schema = ''
|
||||
if default_schema and not isinstance(default_schema, openapi.Schema):
|
||||
default_schema = self.serializer_to_schema(default_schema) or ''
|
||||
|
||||
@@ -201,7 +226,7 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
def get_response_serializers(self):
|
||||
"""Return the response codes that this view is expected to return, and the serializer for each response body.
|
||||
The return value should be a dict where the keys are possible status codes, and values are either strings,
|
||||
``Serializer``\ s, :class:`.Schema`, :class:`.SchemaRef` or :class:`.Response` objects. See
|
||||
``Serializer``\\ s, :class:`.Schema`, :class:`.SchemaRef` or :class:`.Response` objects. See
|
||||
:func:`@swagger_auto_schema <.swagger_auto_schema>` for more details.
|
||||
|
||||
:return: the response serializers
|
||||
@@ -228,7 +253,7 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
for sc, serializer in response_serializers.items():
|
||||
if isinstance(serializer, str):
|
||||
response = openapi.Response(
|
||||
description=serializer
|
||||
description=force_real_str(serializer)
|
||||
)
|
||||
elif not serializer:
|
||||
continue
|
||||
@@ -296,16 +321,40 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
operation_id = '_'.join(operation_keys)
|
||||
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):
|
||||
"""Return an operation description determined as appropriate from the view's method and class docstrings.
|
||||
|
||||
:return: the operation description
|
||||
:rtype: str
|
||||
"""
|
||||
description = self.overrides.get('operation_description', None)
|
||||
if description is None:
|
||||
description = self._sch.get_description(self.path, self.method)
|
||||
return description
|
||||
return self._extract_description_and_summary()[0]
|
||||
|
||||
def get_summary(self):
|
||||
"""Return a summary description for this operation.
|
||||
|
||||
:return: the summary
|
||||
:rtype: str
|
||||
"""
|
||||
return self._extract_description_and_summary()[1]
|
||||
|
||||
def get_security(self):
|
||||
"""Return a list of security requirements for this operation.
|
||||
@@ -317,6 +366,14 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
:rtype: list[dict[str,list[str]]]"""
|
||||
return self.overrides.get('security', None)
|
||||
|
||||
def is_deprecated(self):
|
||||
"""Return ``True`` if this operation is to be marked as deprecated.
|
||||
|
||||
:return: deprecation status
|
||||
:rtype: bool
|
||||
"""
|
||||
return self.overrides.get('deprecated', None)
|
||||
|
||||
def get_tags(self, operation_keys):
|
||||
"""Get a list of tags for this operation. Tags determine how operations relate with each other, and in the UI
|
||||
each tag will show as a group containing the operations that use it.
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
from collections import OrderedDict
|
||||
from importlib import import_module
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.base import BaseCommand
|
||||
from rest_framework.test import APIRequestFactory, force_authenticate
|
||||
@@ -59,9 +59,13 @@ class Command(BaseCommand):
|
||||
help='Use a mock request when generating the swagger schema. This is useful if your views or serializers'
|
||||
'depend on context from a request in order to function.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--api-version', dest='api_version',
|
||||
type=str,
|
||||
help='Version to use to generate schema. This option implies --mock-request.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--user', dest='user',
|
||||
default='',
|
||||
help='Username of an existing user to use for mocked authentication. This option implies --mock-request.'
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -103,7 +107,7 @@ class Command(BaseCommand):
|
||||
request = APIView().initialize_request(request)
|
||||
return request
|
||||
|
||||
def handle(self, output_file, overwrite, format, api_url, mock, user, private, generator_class_name,
|
||||
def handle(self, output_file, overwrite, format, api_url, mock, api_version, user, private, generator_class_name,
|
||||
*args, **kwargs):
|
||||
# disable logs of WARNING and below
|
||||
logging.disable(logging.WARNING)
|
||||
@@ -122,20 +126,30 @@ class Command(BaseCommand):
|
||||
|
||||
api_url = api_url or swagger_settings.DEFAULT_API_URL
|
||||
|
||||
user = User.objects.get(username=user) if user else None
|
||||
mock = mock or private or (user is not None)
|
||||
if user:
|
||||
# Only call get_user_model if --user was passed in order to
|
||||
# avoid crashing if auth is not configured in the project
|
||||
user = get_user_model().objects.get(username=user)
|
||||
|
||||
mock = mock or private or (user is not None) or (api_version is not None)
|
||||
if mock and not api_url:
|
||||
raise ImproperlyConfigured(
|
||||
'--mock-request requires an API url; either provide '
|
||||
'the --url argument or set the DEFAULT_API_URL setting'
|
||||
)
|
||||
|
||||
request = self.get_mock_request(api_url, format, user) if mock else None
|
||||
request = None
|
||||
if mock:
|
||||
request = self.get_mock_request(api_url, format, user)
|
||||
|
||||
if request and api_version:
|
||||
request.version = api_version
|
||||
|
||||
generator_class = import_class(generator_class_name) or swagger_settings.DEFAULT_GENERATOR_CLASS
|
||||
generator = generator_class(
|
||||
info=info,
|
||||
url=api_url
|
||||
version=api_version,
|
||||
url=api_url,
|
||||
)
|
||||
schema = generator.get_schema(request=request, public=not private)
|
||||
|
||||
|
||||
+36
-8
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
|
||||
@@ -7,6 +8,8 @@ from inflection import camelize
|
||||
|
||||
from .utils import filter_none
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TYPE_OBJECT = "object" #:
|
||||
TYPE_STRING = "string" #:
|
||||
TYPE_NUMBER = "number" #:
|
||||
@@ -288,6 +291,8 @@ class Paths(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,
|
||||
head=None, patch=None, parameters=None, **extra):
|
||||
"""Information about a single path
|
||||
@@ -312,6 +317,15 @@ class PathItem(SwaggerDict):
|
||||
self.parameters = filter_none(parameters)
|
||||
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):
|
||||
def __init__(self, operation_id, responses, parameters=None, consumes=None, produces=None, summary=None,
|
||||
@@ -384,8 +398,6 @@ class Parameter(SwaggerDict):
|
||||
:param default: default value if the parameter is not provided; must conform to parameter type
|
||||
"""
|
||||
super(Parameter, self).__init__(**extra)
|
||||
if (not schema and not type) or (schema and type):
|
||||
raise AssertionError("either schema or type are required for Parameter object!")
|
||||
self.name = name
|
||||
self.in_ = in_
|
||||
self.description = description
|
||||
@@ -398,6 +410,10 @@ class Parameter(SwaggerDict):
|
||||
self.items = items
|
||||
self.default = default
|
||||
self._insert_extras__()
|
||||
if (not schema and not type) or (schema and type):
|
||||
raise AssertionError("either schema or type are required for Parameter object (not both)!")
|
||||
if schema and isinstance(schema, Schema):
|
||||
schema._remove_read_only()
|
||||
if self['in'] == IN_PATH:
|
||||
# path parameters must always be required
|
||||
assert required is not False, "path parameter cannot be optional"
|
||||
@@ -429,9 +445,9 @@ class Schema(SwaggerDict):
|
||||
:param bool,.Schema,.SchemaRef additional_properties: allow wildcard properties not listed in `properties`
|
||||
:param list[str] required: list of requried property names
|
||||
:param .Schema,.SchemaRef items: type of array items, only valid if `type` is ``array``
|
||||
:param default: only valid when insider another ``Schema``\ 's ``properties``;
|
||||
:param default: only valid when insider another ``Schema``\\ 's ``properties``;
|
||||
the default value of this property if it is not provided, must conform to the type of this Schema
|
||||
:param read_only: only valid when insider another ``Schema``\ 's ``properties``;
|
||||
:param read_only: only valid when insider another ``Schema``\\ 's ``properties``;
|
||||
declares the property as read only - it must only be sent as part of responses, never in requests
|
||||
"""
|
||||
super(Schema, self).__init__(**extra)
|
||||
@@ -462,6 +478,11 @@ class Schema(SwaggerDict):
|
||||
if pattern and type != TYPE_STRING:
|
||||
raise AssertionError("pattern can only be used when type is string")
|
||||
|
||||
def _remove_read_only(self):
|
||||
# readOnly is only valid for Schemas inside another Schema's properties;
|
||||
# when placing Schema elsewhere we must take care to remove the readOnly flag
|
||||
self.pop('readOnly', '')
|
||||
|
||||
|
||||
class _Ref(SwaggerDict):
|
||||
ref_name_re = re.compile(r"#/(?P<scope>.+)/(?P<name>[^/]+)$")
|
||||
@@ -494,12 +515,12 @@ class _Ref(SwaggerDict):
|
||||
ref_match = self.ref_name_re.match(self.ref)
|
||||
return resolver.get(ref_match.group('name'), scope=ref_match.group('scope'))
|
||||
|
||||
def __setitem__(self, key, value, **kwargs):
|
||||
def __setitem__(self, key, value):
|
||||
if key == "$ref":
|
||||
return super(_Ref, self).__setitem__(key, value, **kwargs)
|
||||
return super(_Ref, self).__setitem__(key, value)
|
||||
raise NotImplementedError("only $ref can be set on Reference objects (not %s)" % key)
|
||||
|
||||
def __delitem__(self, key, **kwargs):
|
||||
def __delitem__(self, key):
|
||||
raise NotImplementedError("cannot delete property of Reference object")
|
||||
|
||||
|
||||
@@ -557,6 +578,8 @@ class Response(SwaggerDict):
|
||||
self.schema = schema
|
||||
self.examples = examples
|
||||
self._insert_extras__()
|
||||
if schema and isinstance(schema, Schema):
|
||||
schema._remove_read_only()
|
||||
|
||||
|
||||
class ReferenceResolver(object):
|
||||
@@ -628,8 +651,13 @@ class ReferenceResolver(object):
|
||||
ret = self.getdefault(name, None, scope)
|
||||
if ret is None:
|
||||
ret = maker()
|
||||
value = self.getdefault(name, None, scope)
|
||||
assert ret is not None, "maker returned None; referenced objects cannot be None/null"
|
||||
self.set(name, ret, scope)
|
||||
if value is None:
|
||||
self.set(name, ret, scope)
|
||||
elif value != ret:
|
||||
logger.debug("during setdefault, maker for %s inserted a value and returned a different value", name)
|
||||
ret = value
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
+73
-39
@@ -1,11 +1,14 @@
|
||||
import six
|
||||
|
||||
from django.shortcuts import render, resolve_url
|
||||
from django.utils.functional import Promise
|
||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, TemplateHTMLRenderer
|
||||
from rest_framework.utils import json
|
||||
|
||||
from drf_yasg.openapi import Swagger
|
||||
|
||||
from .app_settings import redoc_settings, swagger_settings
|
||||
from .codecs import VALIDATORS, OpenAPICodecJson, OpenAPICodecYaml
|
||||
from .openapi import Swagger
|
||||
from .utils import filter_none
|
||||
|
||||
|
||||
class _SpecRenderer(BaseRenderer):
|
||||
@@ -74,23 +77,55 @@ class _UIRenderer(BaseRenderer):
|
||||
def set_context(self, renderer_context, swagger):
|
||||
renderer_context['title'] = swagger.info.title
|
||||
renderer_context['version'] = swagger.info.version
|
||||
renderer_context['swagger_settings'] = json.dumps(self.get_swagger_ui_settings())
|
||||
renderer_context['redoc_settings'] = json.dumps(self.get_redoc_settings())
|
||||
renderer_context['oauth2_config'] = json.dumps(self.get_oauth2_config())
|
||||
renderer_context['USE_SESSION_AUTH'] = swagger_settings.USE_SESSION_AUTH
|
||||
renderer_context.update(self.get_auth_urls())
|
||||
|
||||
def get_auth_urls(self):
|
||||
urls = {}
|
||||
if swagger_settings.LOGIN_URL is not None:
|
||||
urls['LOGIN_URL'] = resolve_url(swagger_settings.LOGIN_URL)
|
||||
if swagger_settings.LOGOUT_URL is not None:
|
||||
urls['LOGOUT_URL'] = resolve_url(swagger_settings.LOGOUT_URL)
|
||||
def resolve_url(self, to):
|
||||
if isinstance(to, Promise):
|
||||
to = str(to)
|
||||
|
||||
return urls
|
||||
if to is None:
|
||||
return None
|
||||
|
||||
args, kwargs = None, None
|
||||
if not isinstance(to, six.string_types):
|
||||
if len(to) > 2:
|
||||
to, args, kwargs = to
|
||||
elif len(to) == 2:
|
||||
to, kwargs = to
|
||||
|
||||
args = args or ()
|
||||
kwargs = kwargs or {}
|
||||
|
||||
return resolve_url(to, *args, **kwargs)
|
||||
|
||||
def get_auth_urls(self):
|
||||
urls = {
|
||||
'LOGIN_URL': self.resolve_url(swagger_settings.LOGIN_URL),
|
||||
'LOGOUT_URL': self.resolve_url(swagger_settings.LOGOUT_URL),
|
||||
}
|
||||
|
||||
return filter_none(urls)
|
||||
|
||||
def get_oauth2_config(self):
|
||||
data = swagger_settings.OAUTH2_CONFIG
|
||||
assert isinstance(data, dict), "OAUTH2_CONFIG must be a dict"
|
||||
return data
|
||||
|
||||
|
||||
class SwaggerUIRenderer(_UIRenderer):
|
||||
"""Renders a swagger-ui web interface for schema browisng."""
|
||||
template = 'drf-yasg/swagger-ui.html'
|
||||
format = 'swagger'
|
||||
|
||||
def set_context(self, renderer_context, swagger):
|
||||
super(SwaggerUIRenderer, self).set_context(renderer_context, swagger)
|
||||
renderer_context['swagger_settings'] = json.dumps(self.get_swagger_ui_settings())
|
||||
|
||||
def get_swagger_ui_settings(self):
|
||||
data = {
|
||||
'url': self.resolve_url(swagger_settings.SPEC_URL),
|
||||
'operationsSorter': swagger_settings.OPERATIONS_SORTER,
|
||||
'tagsSorter': swagger_settings.TAGS_SORTER,
|
||||
'docExpansion': swagger_settings.DOC_EXPANSION,
|
||||
@@ -102,40 +137,39 @@ class _UIRenderer(BaseRenderer):
|
||||
'showCommonExtensions': swagger_settings.SHOW_COMMON_EXTENSIONS,
|
||||
'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
|
||||
'supportedSubmitMethods': swagger_settings.SUPPORTED_SUBMIT_METHODS,
|
||||
'displayOperationId': swagger_settings.DISPLAY_OPERATION_ID,
|
||||
}
|
||||
data = {k: v for k, v in data.items() if v is not None}
|
||||
|
||||
data = filter_none(data)
|
||||
if swagger_settings.VALIDATOR_URL != '':
|
||||
data['validatorUrl'] = swagger_settings.VALIDATOR_URL
|
||||
data['validatorUrl'] = self.resolve_url(swagger_settings.VALIDATOR_URL)
|
||||
|
||||
return data
|
||||
|
||||
def get_redoc_settings(self):
|
||||
data = {
|
||||
'lazyRendering': redoc_settings.LAZY_RENDERING,
|
||||
'hideHostname': redoc_settings.HIDE_HOSTNAME,
|
||||
'expandResponses': redoc_settings.EXPAND_RESPONSES,
|
||||
'pathInMiddle': redoc_settings.PATH_IN_MIDDLE,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
def get_oauth2_config(self):
|
||||
data = swagger_settings.OAUTH2_CONFIG
|
||||
assert isinstance(data, dict), "OAUTH2_CONFIG must be a dict"
|
||||
return data
|
||||
|
||||
|
||||
class SwaggerUIRenderer(_UIRenderer):
|
||||
"""Renders a swagger-ui web interface for schema browisng.
|
||||
Also requires :class:`.OpenAPIRenderer` as an available renderer on the same view.
|
||||
"""
|
||||
template = 'drf-yasg/swagger-ui.html'
|
||||
format = 'swagger'
|
||||
|
||||
|
||||
class ReDocRenderer(_UIRenderer):
|
||||
"""Renders a ReDoc web interface for schema browisng.
|
||||
Also requires :class:`.OpenAPIRenderer` as an available renderer on the same view.
|
||||
"""
|
||||
"""Renders a ReDoc web interface for schema browisng."""
|
||||
template = 'drf-yasg/redoc.html'
|
||||
format = 'redoc'
|
||||
|
||||
def set_context(self, renderer_context, swagger):
|
||||
super(ReDocRenderer, self).set_context(renderer_context, swagger)
|
||||
renderer_context['redoc_settings'] = json.dumps(self.get_redoc_settings())
|
||||
|
||||
def get_redoc_settings(self):
|
||||
data = {
|
||||
'url': self.resolve_url(redoc_settings.SPEC_URL),
|
||||
'lazyRendering': redoc_settings.LAZY_RENDERING,
|
||||
'hideHostname': redoc_settings.HIDE_HOSTNAME,
|
||||
'expandResponses': redoc_settings.EXPAND_RESPONSES,
|
||||
'pathInMiddlePanel': redoc_settings.PATH_IN_MIDDLE,
|
||||
'nativeScrollbars': redoc_settings.NATIVE_SCROLLBARS,
|
||||
'requiredPropsFirst': redoc_settings.REQUIRED_PROPS_FIRST,
|
||||
}
|
||||
|
||||
return filter_none(data)
|
||||
|
||||
|
||||
class ReDocOldRenderer(ReDocRenderer):
|
||||
"""Renders a ReDoc 1.x.x web interface for schema browisng."""
|
||||
template = 'drf-yasg/redoc-old.html'
|
||||
|
||||
@@ -3,19 +3,26 @@
|
||||
var currentPath = window.location.protocol + "//" + window.location.host + window.location.pathname;
|
||||
var specURL = currentPath + '?format=openapi';
|
||||
var redoc = document.createElement("redoc");
|
||||
redoc.setAttribute("spec-url", specURL);
|
||||
|
||||
var redocSettings = JSON.parse(document.getElementById('redoc-settings').innerHTML);
|
||||
if (redocSettings.lazyRendering) {
|
||||
redoc.setAttribute("lazy-rendering", '');
|
||||
if (redocSettings.url) {
|
||||
specURL = redocSettings.url;
|
||||
delete redocSettings.url;
|
||||
}
|
||||
if (redocSettings.pathInMiddle) {
|
||||
redoc.setAttribute("path-in-middle-panel", '');
|
||||
redoc.setAttribute("spec-url", specURL);
|
||||
|
||||
function camelToKebab(str) {
|
||||
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
}
|
||||
if (redocSettings.hideHostname) {
|
||||
redoc.setAttribute("hide-hostname", '');
|
||||
|
||||
for (var p in redocSettings) {
|
||||
if (redocSettings.hasOwnProperty(p)) {
|
||||
if (redocSettings[p] !== null && redocSettings[p] !== undefined && redocSettings[p] !== false) {
|
||||
redoc.setAttribute(camelToKebab(p), redocSettings[p].toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
redoc.setAttribute("expand-responses", redocSettings.expandResponses);
|
||||
|
||||
document.body.appendChild(redoc);
|
||||
|
||||
function hideEmptyVersion() {
|
||||
|
||||
File diff suppressed because one or more lines are too long
+38
-50
File diff suppressed because one or more lines are too long
@@ -19,10 +19,6 @@ body.swagger-body {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
#django-session-auth {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
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
@@ -6,6 +6,11 @@ function patchSwaggerUi() {
|
||||
var authWrapper = document.querySelector('.auth-wrapper');
|
||||
var authorizeButton = document.querySelector('.auth-wrapper .authorize');
|
||||
var djangoSessionAuth = document.querySelector('#django-session-auth');
|
||||
if (!djangoSessionAuth) {
|
||||
console.log("WARNING: session auth disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.querySelector('.auth-wrapper #django-session-auth')) {
|
||||
console.log("WARNING: session auth already patched; skipping patchSwaggerUi()");
|
||||
return;
|
||||
@@ -27,7 +32,6 @@ function initSwaggerUi() {
|
||||
var swaggerConfig = {
|
||||
url: specURL,
|
||||
dom_id: '#swagger-ui',
|
||||
displayOperationId: true,
|
||||
displayRequestDuration: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
@@ -57,7 +61,7 @@ function initSwaggerUi() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(swaggerSettings);
|
||||
console.log('swaggerSettings', swaggerSettings);
|
||||
for (var p in swaggerSettings) {
|
||||
if (swaggerSettings.hasOwnProperty(p)) {
|
||||
swaggerConfig[p] = swaggerSettings[p];
|
||||
@@ -66,7 +70,7 @@ function initSwaggerUi() {
|
||||
window.ui = SwaggerUIBundle(swaggerConfig);
|
||||
|
||||
var oauth2Config = JSON.parse(document.getElementById('oauth2-config').innerHTML);
|
||||
console.log(oauth2Config);
|
||||
console.log('oauth2Config', oauth2Config);
|
||||
window.ui.initOAuth(oauth2Config);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'drf-yasg/style.css' %}"/>
|
||||
</head>
|
||||
<body>
|
||||
<script id="redoc-settings" type="application/json">{{ redoc_settings | safe }}</script>
|
||||
|
||||
<script src="{% static 'drf-yasg/insQ.min.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/redoc-init.js' %}"> </script>
|
||||
<script src="{% static 'drf-yasg/redoc-old/redoc.min.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -7,12 +7,18 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'drf-yasg/style.css' %}"/>
|
||||
{% block extra_styles %}
|
||||
{# -- Add any additional CSS scripts here -- #}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<script id="redoc-settings" type="application/json">{{ redoc_settings | safe }}</script>
|
||||
<script id="redoc-settings" type="application/json">{{ redoc_settings | safe }}</script>
|
||||
|
||||
<script src="{% static 'drf-yasg/insQ.min.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/redoc-init.js' %}"> </script>
|
||||
<script src="{% static 'drf-yasg/redoc/redoc.min.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/insQ.min.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/redoc-init.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/redoc/redoc.min.js' %}"></script>
|
||||
{% block extra_scripts %}
|
||||
{# -- Add any additional scripts here -- #}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -11,53 +11,14 @@
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'drf-yasg/swagger-ui-dist/swagger-ui.css' %}">
|
||||
<link rel="icon" type="image/png" href="{% static 'drf-yasg/swagger-ui-dist/favicon-32x32.png' %}" sizes="32x32"/>
|
||||
<link rel="icon" type="image/png" href="{% static 'drf-yasg/swagger-ui-dist/favicon-16x16.png' %}" sizes="16x16"/>
|
||||
|
||||
{% block extra_styles %}
|
||||
{# -- Add any additional CSS scripts here -- #}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body class="swagger-body">
|
||||
|
||||
<svg class="swagger-defs" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<symbol viewBox="0 0 20 20" id="unlocked">
|
||||
<path
|
||||
d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="locked">
|
||||
<path
|
||||
d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="close">
|
||||
<path
|
||||
d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow">
|
||||
<path
|
||||
d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow-down">
|
||||
<path
|
||||
d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"></path>
|
||||
</symbol>
|
||||
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="jump-to">
|
||||
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="expand">
|
||||
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"></path>
|
||||
</symbol>
|
||||
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div id="swagger-ui"></div>
|
||||
<div id="spec-error" class="hidden alert alert-danger"></div>
|
||||
|
||||
|
||||
<script id="swagger-settings" type="application/json">{{ swagger_settings | safe }}</script>
|
||||
<script id="oauth2-config" type="application/json">{{ oauth2_config | safe }}</script>
|
||||
@@ -66,33 +27,43 @@
|
||||
<script src="{% static 'drf-yasg/swagger-ui-dist/swagger-ui-standalone-preset.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/insQ.min.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/swagger-ui-init.js' %}"></script>
|
||||
{% block extra_scripts %}
|
||||
{# -- Add any additional scripts here -- #}
|
||||
{% endblock %}
|
||||
|
||||
<a id="oauth2-redirect-url" href="{% static 'drf-yasg/swagger-ui-dist/oauth2-redirect.html' %}" class="hidden"></a>
|
||||
|
||||
<div id="django-session-auth" class="hidden">
|
||||
{% if USE_SESSION_AUTH %}
|
||||
{% if USE_SESSION_AUTH %}
|
||||
<div id="django-session-auth" class="hidden">
|
||||
{% csrf_token %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="hello">
|
||||
<span class="django-session">Django</span> <span class="label label-primary">{{ request.user }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block user_context_message %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="hello">
|
||||
<span class="django-session">Django</span> <span class="label label-primary">{{ request.user }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class='btn authorize'>
|
||||
<a id="auth" class="header__btn" href="{{ LOGOUT_URL }}?next={{ request.path }}" data-sw-translate>
|
||||
Django Logout
|
||||
{% block django_logout_message %}
|
||||
Django Logout
|
||||
{% endblock %}
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class='btn authorize'>
|
||||
<a id="auth" class="header__btn" href="{{ LOGIN_URL }}?next={{ request.path }}" data-sw-translate>
|
||||
Django Login
|
||||
{% block django_login_message %}
|
||||
Django Login
|
||||
{% endblock %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
+135
-20
@@ -3,12 +3,16 @@ import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.db import models
|
||||
from django.utils.encoding import force_text
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from rest_framework.request import is_form_media_type
|
||||
from rest_framework.settings import api_settings as rest_framework_settings
|
||||
from rest_framework.utils import encoders, json
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .app_settings import swagger_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -23,9 +27,9 @@ class unset(object):
|
||||
|
||||
|
||||
def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_body=None, query_serializer=None,
|
||||
manual_parameters=None, operation_id=None, operation_description=None, security=None,
|
||||
responses=None, field_inspectors=None, filter_inspectors=None, paginator_inspectors=None,
|
||||
**extra_overrides):
|
||||
manual_parameters=None, operation_id=None, operation_description=None, operation_summary=None,
|
||||
security=None, deprecated=None, responses=None, field_inspectors=None, filter_inspectors=None,
|
||||
paginator_inspectors=None, **extra_overrides):
|
||||
"""Decorate a view method to customize the :class:`.Operation` object generated from it.
|
||||
|
||||
`method` and `methods` are mutually exclusive and must only be present when decorating a view method that accepts
|
||||
@@ -46,7 +50,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
||||
the `manual_parameters` argument.
|
||||
|
||||
If a ``Serializer`` class or instance is given, it will be automatically converted into a :class:`.Schema`
|
||||
used as a ``body`` :class:`.Parameter`, or into a list of ``form`` :class:`.Parameter`\ s, as appropriate.
|
||||
used as a ``body`` :class:`.Parameter`, or into a list of ``form`` :class:`.Parameter`\\ s, as appropriate.
|
||||
|
||||
:param .Serializer query_serializer: if you use a ``Serializer`` to parse query parameters, you can pass it here
|
||||
and have :class:`.Parameter` objects be generated automatically from it.
|
||||
@@ -59,16 +63,18 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
||||
|
||||
:param list[.Parameter] manual_parameters: a list of manual parameters to override the automatically generated ones
|
||||
|
||||
:class:`.Parameter`\ s are identified by their (``name``, ``in``) combination, and any parameters given
|
||||
:class:`.Parameter`\\ s are identified by their (``name``, ``in``) combination, and any parameters given
|
||||
here will fully override automatically generated parameters if they collide.
|
||||
|
||||
It is an error to supply ``form`` parameters when the request does not consume form-data.
|
||||
|
||||
:param str operation_id: operation ID override; the operation ID must be unique accross the whole API
|
||||
:param str operation_description: operation description override
|
||||
:param str operation_summary: operation summary string
|
||||
:param list[dict] security: security requirements override; used to specify which authetication mechanism
|
||||
is requried to call this API; an empty list marks the endpoint as unauthenticated (i.e. removes all accepted
|
||||
authentication schemes), and ``None`` will inherit the top-level secuirty requirements
|
||||
:param bool deprecated: deprecation status for operation
|
||||
:param dict[str,(.Schema,.SchemaRef,.Response,str,Serializer)] responses: a dict of documented manual responses
|
||||
keyed on response status code. If no success (``2xx``) response is given, one will automatically be
|
||||
generated from the request body and http method. If any ``2xx`` response is given the automatic response is
|
||||
@@ -101,6 +107,8 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
||||
'query_serializer': query_serializer,
|
||||
'manual_parameters': manual_parameters,
|
||||
'operation_id': operation_id,
|
||||
'operation_summary': operation_summary,
|
||||
'deprecated': deprecated,
|
||||
'operation_description': operation_description,
|
||||
'security': security,
|
||||
'responses': responses,
|
||||
@@ -114,21 +122,24 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
||||
data.update(extra_overrides)
|
||||
if not data: # pragma: no cover
|
||||
# no overrides to set, no use in doing more work
|
||||
return
|
||||
return view_method
|
||||
|
||||
# if the method is an @action, it will have a bind_to_methods attribute
|
||||
# if the method is an @action, it will have a bind_to_methods attribute, or a mapping attribute for drf>3.8
|
||||
bind_to_methods = getattr(view_method, 'bind_to_methods', [])
|
||||
mapping = getattr(view_method, 'mapping', {})
|
||||
mapping_methods = [mth for mth, name in mapping.items() if name == view_method.__name__]
|
||||
action_http_methods = bind_to_methods + mapping_methods
|
||||
|
||||
# if the method is actually a function based view (@api_view), it will have a 'cls' attribute
|
||||
view_cls = getattr(view_method, 'cls', None)
|
||||
http_method_names = [m for m in getattr(view_cls, 'http_method_names', []) if hasattr(view_cls, m)]
|
||||
api_view_http_methods = [m for m in getattr(view_cls, 'http_method_names', []) if hasattr(view_cls, m)]
|
||||
|
||||
available_methods = http_method_names + bind_to_methods
|
||||
available_http_methods = api_view_http_methods + action_http_methods
|
||||
existing_data = getattr(view_method, '_swagger_auto_schema', {})
|
||||
|
||||
_methods = methods
|
||||
if methods or method:
|
||||
assert available_methods or http_method_names, "`method` or `methods` can only be specified " \
|
||||
"on @action or @api_view views"
|
||||
assert available_http_methods, "`method` or `methods` can only be specified on @action or @api_view views"
|
||||
assert bool(methods) != bool(method), "specify either method or methods"
|
||||
assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \
|
||||
" use `method` for a single argument"
|
||||
@@ -136,20 +147,20 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
||||
_methods = [method.lower()]
|
||||
else:
|
||||
_methods = [mth.lower() for mth in methods]
|
||||
assert all(mth in available_methods for mth in _methods), "http method not bound to view"
|
||||
assert all(mth in available_http_methods for mth in _methods), "http method not bound to view"
|
||||
assert not any(mth in existing_data for mth in _methods), "http method defined multiple times"
|
||||
|
||||
if available_methods:
|
||||
if available_http_methods:
|
||||
# action or api_view
|
||||
assert bool(http_method_names) != bool(bind_to_methods), "this should never happen"
|
||||
assert bool(api_view_http_methods) != bool(action_http_methods), "this should never happen"
|
||||
|
||||
if len(available_methods) > 1:
|
||||
if len(available_http_methods) > 1:
|
||||
assert _methods, \
|
||||
"on multi-method api_view, action, detail_route or list_route, you must specify " \
|
||||
"swagger_auto_schema on a per-method basis using one of the `method` or `methods` arguments"
|
||||
else:
|
||||
# for a single-method view we assume that single method as the decorator target
|
||||
_methods = _methods or available_methods
|
||||
_methods = _methods or available_http_methods
|
||||
|
||||
assert not any(hasattr(getattr(view_cls, mth, None), '_swagger_auto_schema') for mth in _methods), \
|
||||
"swagger_auto_schema applied twice to method"
|
||||
@@ -169,6 +180,23 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
||||
return decorator
|
||||
|
||||
|
||||
def swagger_serializer_method(serializer_or_field):
|
||||
"""
|
||||
Decorates the method of a serializers.SerializerMethodField
|
||||
to hint as to how Swagger should be generated for this field.
|
||||
|
||||
:param serializer_or_field: ``Serializer``/``Field`` class or instance
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(serializer_method):
|
||||
# stash the serializer for SerializerMethodFieldInspector to find
|
||||
serializer_method._swagger_serializer = serializer_or_field
|
||||
return serializer_method
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def is_list_view(path, method, view):
|
||||
"""Check if the given path/method appears to represent a list view (as opposed to a detail/instance view).
|
||||
|
||||
@@ -226,6 +254,22 @@ def param_list_to_odict(parameters):
|
||||
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):
|
||||
"""Remove ``None`` values from tuples, lists or dictionaries. Return other objects as-is.
|
||||
|
||||
@@ -250,6 +294,7 @@ def force_serializer_instance(serializer):
|
||||
|
||||
:param serializer: serializer class or instance
|
||||
:return: serializer instance
|
||||
:rtype: serializers.BaseSerializer
|
||||
"""
|
||||
if inspect.isclass(serializer):
|
||||
assert issubclass(serializer, serializers.BaseSerializer), "Serializer required, not %s" % serializer.__name__
|
||||
@@ -260,6 +305,26 @@ def force_serializer_instance(serializer):
|
||||
return serializer
|
||||
|
||||
|
||||
def get_serializer_class(serializer):
|
||||
"""Given a ``Serializer`` class or intance, return the ``Serializer`` class. If `serializer` is not a ``Serializer``
|
||||
class or instance, raises an assertion error.
|
||||
|
||||
:param serializer: serializer class or instance, or ``None``
|
||||
:return: serializer class
|
||||
:rtype: type[serializers.BaseSerializer]
|
||||
"""
|
||||
if serializer is None:
|
||||
return None
|
||||
|
||||
if inspect.isclass(serializer):
|
||||
assert issubclass(serializer, serializers.BaseSerializer), "Serializer required, not %s" % serializer.__name__
|
||||
return serializer
|
||||
|
||||
assert isinstance(serializer, serializers.BaseSerializer), \
|
||||
"Serializer class or instance required, not %s" % type(serializer).__name__
|
||||
return type(serializer)
|
||||
|
||||
|
||||
def get_consumes(parser_classes):
|
||||
"""Extract ``consumes`` MIME types from a list of parser classes.
|
||||
|
||||
@@ -283,7 +348,8 @@ def get_produces(renderer_classes):
|
||||
:rtype: list[str]
|
||||
"""
|
||||
media_types = [renderer.media_type for renderer in renderer_classes or []]
|
||||
media_types = [encoding for encoding in media_types if 'html' not in encoding]
|
||||
media_types = [encoding for encoding in media_types
|
||||
if not any(excluded in encoding for excluded in swagger_settings.EXCLUDED_MEDIA_TYPES)]
|
||||
return media_types
|
||||
|
||||
|
||||
@@ -304,18 +370,67 @@ 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
|
||||
:return: Serializer's ``ref_name`` or ``None`` for inline serializer
|
||||
:rtype: str
|
||||
"""
|
||||
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))
|
||||
logger.debug("Forcing inline output for ModelSerializer named 'NestedSerializer':\n" + str(serializer))
|
||||
ref_name = None
|
||||
else:
|
||||
ref_name = serializer_name
|
||||
if ref_name.endswith('Serializer'):
|
||||
ref_name = ref_name[:-len('Serializer')]
|
||||
return ref_name
|
||||
|
||||
|
||||
def force_real_str(s, encoding='utf-8', strings_only=False, errors='strict'):
|
||||
"""
|
||||
Force `s` into a ``str`` instance.
|
||||
|
||||
Fix for https://github.com/axnsan12/drf-yasg/issues/159
|
||||
"""
|
||||
if s is not None:
|
||||
s = force_text(s, encoding, strings_only, errors)
|
||||
if type(s) != str:
|
||||
s = '' + s
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def get_field_default(field):
|
||||
"""
|
||||
Get the default value for a field, converted to a JSON-compatible value while properly handling callables.
|
||||
|
||||
:param field: field instance
|
||||
:return: default value
|
||||
"""
|
||||
default = getattr(field, 'default', serializers.empty)
|
||||
if default is not serializers.empty:
|
||||
if callable(default):
|
||||
try:
|
||||
if hasattr(default, 'set_context'):
|
||||
default.set_context(field)
|
||||
default = default()
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning("default for %s is callable but it raised an exception when "
|
||||
"called; 'default' will not be set on schema", field, exc_info=True)
|
||||
default = serializers.empty
|
||||
|
||||
if default is not serializers.empty and default is not None:
|
||||
try:
|
||||
default = field.to_representation(default)
|
||||
# JSON roundtrip ensures that the value is valid JSON;
|
||||
# for example, sets and tuples get transformed into lists
|
||||
default = json.loads(json.dumps(default, cls=encoders.JSONEncoder))
|
||||
if decimal_as_float(field):
|
||||
default = float(default)
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning("'default' on schema for %s will not be set because "
|
||||
"to_representation raised an exception", field, exc_info=True)
|
||||
default = serializers.empty
|
||||
|
||||
return default
|
||||
|
||||
+10
-5
@@ -11,12 +11,15 @@ from rest_framework.settings import api_settings
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .app_settings import swagger_settings
|
||||
from .renderers import OpenAPIRenderer, ReDocRenderer, SwaggerJSONRenderer, SwaggerUIRenderer, SwaggerYAMLRenderer
|
||||
from .renderers import (
|
||||
OpenAPIRenderer, ReDocOldRenderer, ReDocRenderer, SwaggerJSONRenderer, SwaggerUIRenderer, SwaggerYAMLRenderer
|
||||
)
|
||||
|
||||
SPEC_RENDERERS = (SwaggerYAMLRenderer, SwaggerJSONRenderer, OpenAPIRenderer)
|
||||
UI_RENDERERS = {
|
||||
'swagger': (SwaggerUIRenderer, ReDocRenderer),
|
||||
'redoc': (ReDocRenderer, SwaggerUIRenderer),
|
||||
'redoc-old': (ReDocOldRenderer, ReDocRenderer, SwaggerUIRenderer),
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +48,7 @@ def deferred_never_cache(view_func):
|
||||
|
||||
|
||||
def get_schema_view(info=None, url=None, patterns=None, urlconf=None, public=False, validators=None,
|
||||
generator_class=swagger_settings.DEFAULT_GENERATOR_CLASS,
|
||||
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
|
||||
generator_class=None, authentication_classes=None, permission_classes=None):
|
||||
"""Create a SchemaView class with default renderers and generators.
|
||||
|
||||
:param .Info info: information about the API; if omitted, defaults to :ref:`DEFAULT_INFO <default-swagger-settings>`
|
||||
@@ -63,9 +64,13 @@ def get_schema_view(info=None, url=None, patterns=None, urlconf=None, public=Fal
|
||||
:rtype: type[.SchemaView]
|
||||
"""
|
||||
_public = public
|
||||
_generator_class = generator_class
|
||||
_generator_class = generator_class or swagger_settings.DEFAULT_GENERATOR_CLASS
|
||||
_auth_classes = authentication_classes
|
||||
if _auth_classes is None:
|
||||
_auth_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
||||
_perm_classes = permission_classes
|
||||
if _perm_classes is None:
|
||||
_perm_classes = api_settings.DEFAULT_PERMISSION_CLASSES
|
||||
info = info or swagger_settings.DEFAULT_INFO
|
||||
validators = validators or []
|
||||
_spec_renderers = tuple(renderer.with_validators(validators) for renderer in SPEC_RENDERERS)
|
||||
|
||||
@@ -15,7 +15,7 @@ from articles.models import Article
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.app_settings import swagger_settings
|
||||
from drf_yasg.inspectors import CoreAPICompatInspector, FieldInspector, NotHandled, SwaggerAutoSchema
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from drf_yasg.utils import no_body, swagger_auto_schema
|
||||
|
||||
|
||||
class DjangoFilterDescriptionInspector(CoreAPICompatInspector):
|
||||
@@ -133,11 +133,13 @@ class ArticleViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
pass
|
||||
|
||||
@swagger_auto_schema(request_body=no_body, operation_id='no_body_test')
|
||||
def update(self, request, *args, **kwargs):
|
||||
"""update method docstring"""
|
||||
return super(ArticleViewSet, self).update(request, *args, **kwargs)
|
||||
|
||||
@swagger_auto_schema(operation_description="partial_update description override", responses={404: 'slug not found'})
|
||||
@swagger_auto_schema(operation_description="partial_update description override", responses={404: 'slug not found'},
|
||||
operation_summary='partial_update summary', deprecated=True)
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
"""partial_update method docstring"""
|
||||
return super(ArticleViewSet, self).partial_update(request, *args, **kwargs)
|
||||
|
||||
Regular → Executable
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 2.1 on 2018-08-06 13:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('people', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='identity',
|
||||
name='lastName',
|
||||
field=models.CharField(help_text="<strong>Here's some HTML!</strong>", max_length=30, null=True),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='identity',
|
||||
old_name='firstName',
|
||||
new_name='first_name',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='identity',
|
||||
old_name='lastName',
|
||||
new_name='last_name',
|
||||
),
|
||||
]
|
||||
@@ -1,9 +1,10 @@
|
||||
from django.db import models
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
class Identity(models.Model):
|
||||
firstName = models.CharField(max_length=30, null=True)
|
||||
lastName = models.CharField(max_length=30, null=True)
|
||||
first_name = models.CharField(max_length=30, null=True)
|
||||
last_name = models.CharField(max_length=30, null=True, help_text=mark_safe("<strong>Here's some HTML!</strong>"))
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
|
||||
@@ -23,12 +23,34 @@ class ExampleProjectSerializer(serializers.Serializer):
|
||||
ref_name = 'Project'
|
||||
|
||||
|
||||
class UnixTimestampField(serializers.DateTimeField):
|
||||
def to_representation(self, value):
|
||||
""" Return epoch time for a datetime object or ``None``"""
|
||||
from django.utils.dateformat import format
|
||||
try:
|
||||
return int(format(value, 'U'))
|
||||
except (AttributeError, TypeError):
|
||||
return None
|
||||
|
||||
def to_internal_value(self, value):
|
||||
import datetime
|
||||
return datetime.datetime.fromtimestamp(int(value))
|
||||
|
||||
class Meta:
|
||||
swagger_schema_fields = {
|
||||
'format': 'integer',
|
||||
'title': 'Client date time suu',
|
||||
'description': 'Date time in unix timestamp format',
|
||||
}
|
||||
|
||||
|
||||
class SnippetSerializer(serializers.Serializer):
|
||||
"""SnippetSerializer classdoc
|
||||
|
||||
create: docstring for create from serializer classdoc
|
||||
"""
|
||||
id = serializers.IntegerField(read_only=True, help_text="id serializer help text")
|
||||
created = UnixTimestampField(read_only=True)
|
||||
owner = serializers.PrimaryKeyRelatedField(
|
||||
queryset=get_user_model().objects.all(),
|
||||
default=serializers.CurrentUserDefault(),
|
||||
|
||||
@@ -46,6 +46,10 @@ class SnippetList(generics.ListCreateAPIView):
|
||||
),
|
||||
)
|
||||
def delete(self, *args, **kwargs):
|
||||
"""summary from docstring
|
||||
|
||||
description body is here, summary is not included
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import os
|
||||
|
||||
# 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__))))
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
@@ -91,7 +93,7 @@ REST_FRAMEWORK = {
|
||||
# drf-yasg
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
'LOGIN_URL': '/admin/login',
|
||||
'LOGIN_URL': reverse_lazy('admin:login'),
|
||||
'LOGOUT_URL': '/admin/logout',
|
||||
|
||||
'DEFAULT_INFO': 'testproj.urls.swagger_info',
|
||||
@@ -108,6 +110,10 @@ SWAGGER_SETTINGS = {
|
||||
}
|
||||
}
|
||||
|
||||
REDOC_SETTINGS = {
|
||||
'SPEC_URL': ('schema-json', {'format': '.json'}),
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ urlpatterns = [
|
||||
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'^redoc/$', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||
url(r'^redoc-old/$', SchemaView.with_ui('redoc-old', cache_timeout=0), name='schema-redoc-old'),
|
||||
|
||||
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'),
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import datetime
|
||||
import decimal
|
||||
import uuid
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class Unknown(object):
|
||||
pass
|
||||
|
||||
|
||||
class MethodFieldExampleSerializer(serializers.Serializer):
|
||||
"""
|
||||
Implementation of SerializerMethodField using type hinting for Python >= 3.5
|
||||
"""
|
||||
|
||||
hinted_bool = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a bool")
|
||||
|
||||
def get_hinted_bool(self, obj) -> bool:
|
||||
return True
|
||||
|
||||
hinted_int = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be an integer")
|
||||
|
||||
def get_hinted_int(self, obj) -> int:
|
||||
return 1
|
||||
|
||||
hinted_float = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a number")
|
||||
|
||||
def get_hinted_float(self, obj) -> float:
|
||||
return 1.0
|
||||
|
||||
hinted_decimal = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a decimal")
|
||||
|
||||
def get_hinted_decimal(self, obj) -> decimal.Decimal:
|
||||
return decimal.Decimal(1)
|
||||
|
||||
hinted_datetime = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a datetime")
|
||||
|
||||
def get_hinted_datetime(self, obj) -> datetime.datetime:
|
||||
return datetime.datetime.now()
|
||||
|
||||
hinted_date = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a date")
|
||||
|
||||
def get_hinted_date(self, obj) -> datetime.date:
|
||||
return datetime.date.today()
|
||||
|
||||
hinted_uuid = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a uuid")
|
||||
|
||||
def get_hinted_uuid(self, obj) -> uuid.UUID:
|
||||
return uuid.uuid4()
|
||||
|
||||
hinted_unknown = serializers.SerializerMethodField(
|
||||
help_text="type hint is unknown, so is expected to fallback to string")
|
||||
|
||||
def get_hinted_unknown(self, obj) -> Unknown:
|
||||
return Unknown()
|
||||
|
||||
non_hinted_number = serializers.SerializerMethodField(
|
||||
help_text="No hint on the method, so this is expected to fallback to string")
|
||||
|
||||
def get_non_hinted_number(self, obj):
|
||||
return 1.0
|
||||
@@ -0,0 +1,82 @@
|
||||
import datetime
|
||||
import decimal
|
||||
import uuid
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_yasg.utils import swagger_serializer_method
|
||||
|
||||
|
||||
class Unknown(object):
|
||||
pass
|
||||
|
||||
|
||||
class MethodFieldExampleSerializer(serializers.Serializer):
|
||||
"""
|
||||
Fallback implementation of SerializerMethodField type hinting for Python < 3.5
|
||||
|
||||
`->` syntax isn't supported, instead decorate with a serializer that returns the same type
|
||||
a bit of a hack, but it provides a cross-check between hinting and decorator functionality.
|
||||
"""
|
||||
|
||||
hinted_bool = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a bool")
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.BooleanField)
|
||||
def get_hinted_bool(self, obj):
|
||||
return True
|
||||
|
||||
hinted_int = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be an integer")
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.IntegerField)
|
||||
def get_hinted_int(self, obj):
|
||||
return 1
|
||||
|
||||
hinted_float = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a number")
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.FloatField)
|
||||
def get_hinted_float(self, obj):
|
||||
return 1.0
|
||||
|
||||
hinted_decimal = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a decimal")
|
||||
|
||||
# note that in this case an instance is required since DecimalField has required arguments
|
||||
@swagger_serializer_method(serializer_or_field=serializers.DecimalField(max_digits=6, decimal_places=4))
|
||||
def get_hinted_decimal(self, obj):
|
||||
return decimal.Decimal(1)
|
||||
|
||||
hinted_datetime = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a datetime")
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.DateTimeField)
|
||||
def get_hinted_datetime(self, obj):
|
||||
return datetime.datetime.now()
|
||||
|
||||
hinted_date = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a date")
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.DateField)
|
||||
def get_hinted_date(self, obj):
|
||||
return datetime.date.today()
|
||||
|
||||
hinted_uuid = serializers.SerializerMethodField(
|
||||
help_text="the type hint on the method should determine this to be a uuid")
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.UUIDField)
|
||||
def get_hinted_uuid(self, obj):
|
||||
return uuid.uuid4()
|
||||
|
||||
hinted_unknown = serializers.SerializerMethodField(
|
||||
help_text="type hint is unknown, so is expected to fallback to string")
|
||||
|
||||
def get_hinted_unknown(self, obj):
|
||||
return Unknown()
|
||||
|
||||
non_hinted_number = serializers.SerializerMethodField(
|
||||
help_text="No hint on the method, so this is expected to fallback to string")
|
||||
|
||||
def get_non_hinted_number(self, obj):
|
||||
return 1.0
|
||||
@@ -1,8 +1,19 @@
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_yasg.utils import swagger_serializer_method
|
||||
from snippets.models import Snippet
|
||||
|
||||
try:
|
||||
import typing # noqa: F401
|
||||
from .method_serializers_with_typing import MethodFieldExampleSerializer
|
||||
except ImportError:
|
||||
from .method_serializers_without_typing import MethodFieldExampleSerializer
|
||||
|
||||
|
||||
class OtherStuffSerializer(serializers.Serializer):
|
||||
foo = serializers.CharField()
|
||||
|
||||
|
||||
class UserSerializerrr(serializers.ModelSerializer):
|
||||
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
|
||||
@@ -10,10 +21,61 @@ class UserSerializerrr(serializers.ModelSerializer):
|
||||
last_connected_ip = serializers.IPAddressField(help_text="i'm out of ideas", protocol='ipv4', read_only=True)
|
||||
last_connected_at = serializers.DateField(help_text="really?", read_only=True)
|
||||
|
||||
other_stuff = serializers.SerializerMethodField(
|
||||
help_text="the decorator should determine the serializer class for this")
|
||||
|
||||
hint_example = MethodFieldExampleSerializer()
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=OtherStuffSerializer)
|
||||
def get_other_stuff(self, obj):
|
||||
"""
|
||||
method_field that uses a serializer internally.
|
||||
|
||||
By using the decorator, we can tell drf-yasg how to represent this in Swagger
|
||||
:param obj:
|
||||
:return:
|
||||
"""
|
||||
return OtherStuffSerializer().data
|
||||
|
||||
help_text_example_1 = serializers.SerializerMethodField(
|
||||
help_text="help text on field is set, so this should appear in swagger"
|
||||
)
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.IntegerField(
|
||||
help_text="decorated instance help_text shouldn't appear in swagger because field has priority"))
|
||||
def get_help_text_example_1(self):
|
||||
"""
|
||||
method docstring shouldn't appear in swagger because field has priority
|
||||
:return:
|
||||
"""
|
||||
return 1
|
||||
|
||||
help_text_example_2 = serializers.SerializerMethodField()
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.IntegerField(
|
||||
help_text="instance help_text is set, so should appear in swagger"))
|
||||
def get_help_text_example_2(self):
|
||||
"""
|
||||
method docstring shouldn't appear in swagger because decorator has priority
|
||||
:return:
|
||||
"""
|
||||
return 1
|
||||
|
||||
help_text_example_3 = serializers.SerializerMethodField()
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.IntegerField())
|
||||
def get_help_text_example_3(self):
|
||||
"""
|
||||
docstring is set so should appear in swagger as fallback
|
||||
:return:
|
||||
"""
|
||||
return 1
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('id', 'username', 'email', 'articles', 'snippets',
|
||||
'last_connected_ip', 'last_connected_at', 'article_slugs')
|
||||
'last_connected_ip', 'last_connected_at', 'article_slugs', 'other_stuff', 'hint_example',
|
||||
'help_text_example_1', 'help_text_example_2', 'help_text_example_3')
|
||||
|
||||
|
||||
class UserListQuerySerializer(serializers.Serializer):
|
||||
|
||||
@@ -6,7 +6,7 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.utils import no_body, swagger_auto_schema
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from users.serializers import UserListQuerySerializer, UserSerializerrr
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class UserList(APIView):
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@swagger_auto_schema(request_body=no_body, operation_id="users_dummy", operation_description="dummy operation")
|
||||
@swagger_auto_schema(operation_id="users_dummy", operation_description="dummy operation")
|
||||
def patch(self, request):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from six import StringIO
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
@@ -6,6 +8,7 @@ from collections import OrderedDict
|
||||
import pytest
|
||||
from datadiff.tools import assert_equal
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
from rest_framework.test import APIRequestFactory
|
||||
from rest_framework.views import APIView
|
||||
|
||||
@@ -64,6 +67,21 @@ def validate_schema(db):
|
||||
return validate_schema
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def call_generate_swagger():
|
||||
def call_generate_swagger(output_file='-', overwrite=False, format='', api_url='',
|
||||
mock=False, user=None, private=False, generator_class_name='', **kwargs):
|
||||
out = StringIO()
|
||||
call_command(
|
||||
'generate_swagger', stdout=out,
|
||||
output_file=output_file, overwrite=overwrite, format=format, api_url=api_url, mock=mock, user=user,
|
||||
private=private, generator_class_name=generator_class_name, **kwargs
|
||||
)
|
||||
return out.getvalue()
|
||||
|
||||
return call_generate_swagger
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def compare_schemas():
|
||||
def compare_schemas(schema1, schema2):
|
||||
|
||||
+132
-12
@@ -134,14 +134,9 @@ paths:
|
||||
tags:
|
||||
- articles
|
||||
put:
|
||||
operationId: articles_update
|
||||
operationId: no_body_test
|
||||
description: update method docstring
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Article'
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
@@ -151,6 +146,7 @@ paths:
|
||||
- articles
|
||||
patch:
|
||||
operationId: articles_partial_update
|
||||
summary: partial_update summary
|
||||
description: partial_update description override
|
||||
parameters:
|
||||
- name: data
|
||||
@@ -167,6 +163,7 @@ paths:
|
||||
description: slug not found
|
||||
tags:
|
||||
- articles
|
||||
deprecated: true
|
||||
delete:
|
||||
operationId: articles_delete
|
||||
description: destroy method docstring
|
||||
@@ -192,6 +189,8 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/Article'
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
tags:
|
||||
@@ -228,6 +227,8 @@ paths:
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/ImageUpload'
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
tags:
|
||||
@@ -390,7 +391,8 @@ paths:
|
||||
- snippets
|
||||
delete:
|
||||
operationId: snippetsDeleteBulk
|
||||
description: SnippetList classdoc
|
||||
summary: summary from docstring
|
||||
description: description body is here, summary is not included
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
@@ -890,6 +892,32 @@ definitions:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
ImageUpload:
|
||||
required:
|
||||
- image_styles
|
||||
type: object
|
||||
properties:
|
||||
what_am_i_doing:
|
||||
description: test
|
||||
type: string
|
||||
pattern: ^69$
|
||||
default: '69'
|
||||
minLength: 1
|
||||
image_styles:
|
||||
description: Parameter with Items
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- wide
|
||||
- tall
|
||||
- thumb
|
||||
- social
|
||||
upload:
|
||||
description: image serializer help_text
|
||||
type: string
|
||||
readOnly: true
|
||||
format: uri
|
||||
Identity:
|
||||
title: Identity
|
||||
type: object
|
||||
@@ -898,13 +926,14 @@ definitions:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
firstName:
|
||||
title: FirstName
|
||||
first_name:
|
||||
title: First name
|
||||
type: string
|
||||
maxLength: 30
|
||||
minLength: 1
|
||||
lastName:
|
||||
title: LastName
|
||||
last_name:
|
||||
title: Last name
|
||||
description: <strong>Here's some HTML!</strong>
|
||||
type: string
|
||||
maxLength: 30
|
||||
minLength: 1
|
||||
@@ -946,6 +975,12 @@ definitions:
|
||||
description: id serializer help text
|
||||
type: integer
|
||||
readOnly: true
|
||||
created:
|
||||
title: Client date time suu
|
||||
type: string
|
||||
format: integer
|
||||
readOnly: true
|
||||
description: Date time in unix timestamp format
|
||||
owner:
|
||||
title: Owner
|
||||
description: The ID of the user that created this snippet; if none is provided,
|
||||
@@ -1581,11 +1616,76 @@ definitions:
|
||||
todo:
|
||||
title: child
|
||||
todo: null
|
||||
OtherStuff:
|
||||
title: Other stuff
|
||||
description: the decorator should determine the serializer class for this
|
||||
required:
|
||||
- foo
|
||||
type: object
|
||||
properties:
|
||||
foo:
|
||||
title: Foo
|
||||
type: string
|
||||
minLength: 1
|
||||
MethodFieldExample:
|
||||
title: Hint example
|
||||
type: object
|
||||
properties:
|
||||
hinted_bool:
|
||||
title: Hinted bool
|
||||
description: the type hint on the method should determine this to be a bool
|
||||
type: boolean
|
||||
readOnly: true
|
||||
hinted_int:
|
||||
title: Hinted int
|
||||
description: the type hint on the method should determine this to be an integer
|
||||
type: integer
|
||||
readOnly: true
|
||||
hinted_float:
|
||||
title: Hinted float
|
||||
description: the type hint on the method should determine this to be a number
|
||||
type: number
|
||||
readOnly: true
|
||||
hinted_decimal:
|
||||
title: Hinted decimal
|
||||
description: the type hint on the method should determine this to be a decimal
|
||||
type: string
|
||||
format: decimal
|
||||
readOnly: true
|
||||
hinted_datetime:
|
||||
title: Hinted datetime
|
||||
description: the type hint on the method should determine this to be a datetime
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
hinted_date:
|
||||
title: Hinted date
|
||||
description: the type hint on the method should determine this to be a date
|
||||
type: string
|
||||
format: date
|
||||
readOnly: true
|
||||
hinted_uuid:
|
||||
title: Hinted uuid
|
||||
description: the type hint on the method should determine this to be a uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
hinted_unknown:
|
||||
title: Hinted unknown
|
||||
description: type hint is unknown, so is expected to fallback to string
|
||||
type: string
|
||||
readOnly: true
|
||||
non_hinted_number:
|
||||
title: Non hinted number
|
||||
description: No hint on the method, so this is expected to fallback to string
|
||||
type: string
|
||||
readOnly: true
|
||||
UserSerializerrr:
|
||||
required:
|
||||
- username
|
||||
- articles
|
||||
- snippets
|
||||
- hint_example
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
@@ -1636,3 +1736,23 @@ definitions:
|
||||
pattern: ^[-a-zA-Z0-9_]+$
|
||||
readOnly: true
|
||||
uniqueItems: true
|
||||
other_stuff:
|
||||
$ref: '#/definitions/OtherStuff'
|
||||
hint_example:
|
||||
$ref: '#/definitions/MethodFieldExample'
|
||||
help_text_example_1:
|
||||
title: Help text example 1
|
||||
description: help text on field is set, so this should appear in swagger
|
||||
type: integer
|
||||
readOnly: true
|
||||
help_text_example_2:
|
||||
title: Help text example 2
|
||||
description: instance help_text is set, so should appear in swagger
|
||||
type: integer
|
||||
readOnly: true
|
||||
help_text_example_3:
|
||||
title: Help text example 3
|
||||
description: "\n docstring is set so should appear in swagger as fallback\n\
|
||||
\ :return:\n "
|
||||
type: integer
|
||||
readOnly: true
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from six import StringIO
|
||||
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
@@ -9,25 +7,13 @@ from collections import OrderedDict
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.codecs import yaml_sane_load
|
||||
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||
|
||||
|
||||
def call_generate_swagger(output_file='-', overwrite=False, format='', api_url='',
|
||||
mock=False, user='', private=False, generator_class_name='', **kwargs):
|
||||
out = StringIO()
|
||||
call_command(
|
||||
'generate_swagger', stdout=out,
|
||||
output_file=output_file, overwrite=overwrite, format=format, api_url=api_url, mock=mock, user=user,
|
||||
private=private, generator_class_name=generator_class_name, **kwargs
|
||||
)
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
def test_reference_schema(db, reference_schema):
|
||||
def test_reference_schema(call_generate_swagger, db, reference_schema):
|
||||
User.objects.create_superuser('admin', 'admin@admin.admin', 'blabla')
|
||||
|
||||
output = call_generate_swagger(format='yaml', api_url='http://test.local:8002/', user='admin')
|
||||
@@ -35,13 +21,13 @@ def test_reference_schema(db, reference_schema):
|
||||
assert output_schema == reference_schema
|
||||
|
||||
|
||||
def test_non_public(db):
|
||||
def test_non_public(call_generate_swagger, db):
|
||||
output = call_generate_swagger(format='yaml', api_url='http://test.local:8002/', private=True)
|
||||
output_schema = yaml_sane_load(output)
|
||||
assert len(output_schema['paths']) == 0
|
||||
|
||||
|
||||
def test_no_mock(db):
|
||||
def test_no_mock(call_generate_swagger, db):
|
||||
output = call_generate_swagger()
|
||||
output_schema = json.loads(output, object_pairs_hook=OrderedDict)
|
||||
assert len(output_schema['paths']) > 0
|
||||
@@ -52,7 +38,7 @@ class EmptySchemaGenerator(OpenAPISchemaGenerator):
|
||||
return openapi.Paths(paths={}), ''
|
||||
|
||||
|
||||
def test_generator_class(db):
|
||||
def test_generator_class(call_generate_swagger, db):
|
||||
output = call_generate_swagger(generator_class_name='test_management.EmptySchemaGenerator')
|
||||
output_schema = json.loads(output, object_pairs_hook=OrderedDict)
|
||||
assert len(output_schema['paths']) == 0
|
||||
@@ -65,7 +51,7 @@ def silentremove(filename):
|
||||
pass
|
||||
|
||||
|
||||
def test_file_output(db):
|
||||
def test_file_output(call_generate_swagger, db):
|
||||
prefix = os.path.join(tempfile.gettempdir(), tempfile.gettempprefix())
|
||||
name = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(8))
|
||||
yaml_file = prefix + name + '.yaml'
|
||||
|
||||
@@ -25,8 +25,8 @@ def test_validator_url(swagger_settings, swagger):
|
||||
swagger_settings['VALIDATOR_URL'] = None
|
||||
_check_swagger_setting(swagger, 'validatorUrl', None)
|
||||
|
||||
swagger_settings['VALIDATOR_URL'] = 'not none'
|
||||
_check_swagger_setting(swagger, 'validatorUrl', 'not none')
|
||||
swagger_settings['VALIDATOR_URL'] = 'http://not.none/'
|
||||
_check_swagger_setting(swagger, 'validatorUrl', 'http://not.none/')
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
swagger_settings['VALIDATOR_URL'] = ''
|
||||
|
||||
@@ -147,3 +147,46 @@ def test_url_order():
|
||||
|
||||
# get_endpoints only includes one endpoint
|
||||
assert len(generator.get_endpoints(None)['/test/'][1]) == 1
|
||||
|
||||
|
||||
try:
|
||||
from rest_framework.decorators import action, MethodMapper
|
||||
except ImportError:
|
||||
action = MethodMapper = None
|
||||
|
||||
|
||||
@pytest.mark.skipif(not MethodMapper or not action, reason="action.mapping test (djangorestframework>=3.9 required)")
|
||||
def test_action_mapping():
|
||||
class ActionViewSet(viewsets.ViewSet):
|
||||
@swagger_auto_schema(method='get', operation_id='mapping_get')
|
||||
@swagger_auto_schema(method='delete', operation_id='mapping_delete')
|
||||
@action(detail=False, methods=['get', 'delete'], url_path='test')
|
||||
def action_main(self, request):
|
||||
"""mapping docstring get/delete"""
|
||||
pass
|
||||
|
||||
@swagger_auto_schema(operation_id='mapping_post')
|
||||
@action_main.mapping.post
|
||||
def action_post(self, request):
|
||||
"""mapping docstring post"""
|
||||
pass
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'action', ActionViewSet, base_name='action')
|
||||
|
||||
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)
|
||||
action_ops = swagger['paths']['/test/']
|
||||
methods = ['get', 'post', 'delete']
|
||||
assert all(mth in action_ops for mth in methods)
|
||||
assert all(action_ops[mth]['operationId'] == 'mapping_' + mth for mth in methods)
|
||||
assert action_ops['post']['description'] == 'mapping docstring post'
|
||||
assert action_ops['get']['description'] == 'mapping docstring get/delete'
|
||||
assert action_ops['delete']['description'] == 'mapping docstring get/delete'
|
||||
|
||||
@@ -7,6 +7,18 @@ def _get_versioned_schema(prefix, client, validate_schema):
|
||||
response = client.get(prefix + '/swagger.yaml')
|
||||
assert response.status_code == 200
|
||||
swagger = yaml_sane_load(response.content.decode('utf-8'))
|
||||
_check_base(swagger, prefix, validate_schema)
|
||||
return swagger
|
||||
|
||||
|
||||
def _get_versioned_schema_management(prefix, call_generate_swagger, validate_schema, kwargs):
|
||||
output = call_generate_swagger(format='yaml', api_url='http://localhost' + prefix + '/swagger.yaml', **kwargs)
|
||||
swagger = yaml_sane_load(output)
|
||||
_check_base(swagger, prefix, validate_schema)
|
||||
return swagger
|
||||
|
||||
|
||||
def _check_base(swagger, prefix, validate_schema):
|
||||
assert swagger['basePath'] == prefix
|
||||
validate_schema(swagger)
|
||||
assert '/snippets/' in swagger['paths']
|
||||
@@ -51,3 +63,17 @@ def test_ns_v1(client, validate_schema):
|
||||
def test_ns_v2(client, validate_schema):
|
||||
swagger = _get_versioned_schema('/versioned/ns/v2.0', client, validate_schema)
|
||||
_check_v2(swagger)
|
||||
|
||||
|
||||
@pytest.mark.urls('urlconfs.url_versioning')
|
||||
def test_url_v2_management(call_generate_swagger, validate_schema):
|
||||
kwargs = {'api_version': '2.0'}
|
||||
swagger = _get_versioned_schema_management('/versioned/url/v2.0', call_generate_swagger, validate_schema, kwargs)
|
||||
_check_v2(swagger)
|
||||
|
||||
|
||||
@pytest.mark.urls('urlconfs.ns_versioning')
|
||||
def test_ns_v2_management(call_generate_swagger, validate_schema):
|
||||
kwargs = {'api_version': '2.0'}
|
||||
swagger = _get_versioned_schema_management('/versioned/ns/v2.0', call_generate_swagger, validate_schema, kwargs)
|
||||
_check_v2(swagger)
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
[tox]
|
||||
# https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django
|
||||
envlist =
|
||||
py27-django111-drf37,
|
||||
py{34,35,36}-django{111,20}-drf{37,38},
|
||||
py36-django20-drfmaster,
|
||||
lint, docs
|
||||
|
||||
[travis:env]
|
||||
DRF =
|
||||
3.7: drf37
|
||||
3.8: drf38
|
||||
master: drfmaster
|
||||
py{27,34,35,36}-django111-drf{37,38},
|
||||
py{34,35,36,37}-django20-drf{37,38},
|
||||
py{35,36,37}-django21-drf{37,38},
|
||||
djmaster, lint, docs
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
django111: Django>=1.11,<2.0
|
||||
django20: Django>=2.0,<2.1
|
||||
django21: Django>=2.1,<2.2
|
||||
|
||||
drf37: djangorestframework>=3.7.7,<3.8
|
||||
drf38: djangorestframework>=3.8.0,<3.9
|
||||
|
||||
# test with the latest build of django-rest-framework to get early warning of compatibility issues
|
||||
drfmaster: https://github.com/encode/django-rest-framework/archive/master.tar.gz
|
||||
djmaster: https://github.com/encode/django-rest-framework/archive/master.tar.gz
|
||||
djmaster: https://github.com/django/django/archive/master.tar.gz
|
||||
|
||||
# other dependencies
|
||||
-rrequirements/setup.txt
|
||||
-rrequirements/validation.txt
|
||||
-rrequirements/test.txt
|
||||
|
||||
@@ -32,12 +30,14 @@ commands =
|
||||
[testenv:lint]
|
||||
skip_install = true
|
||||
deps =
|
||||
-rrequirements/setup.txt
|
||||
-rrequirements/lint.txt
|
||||
commands =
|
||||
flake8 src/drf_yasg testproj tests setup.py
|
||||
|
||||
[testenv:docs]
|
||||
deps =
|
||||
-rrequirements/setup.txt
|
||||
-rrequirements/docs.txt
|
||||
commands =
|
||||
python setup.py check --restructuredtext --metadata --strict
|
||||
@@ -46,7 +46,7 @@ commands =
|
||||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = testproj.settings.local
|
||||
python_paths = testproj
|
||||
addopts = -n 3 --ignore=node_modules
|
||||
addopts = -n 2 --ignore=node_modules
|
||||
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
@@ -54,13 +54,13 @@ exclude = **/migrations/*
|
||||
ignore = F405
|
||||
|
||||
[isort]
|
||||
skip = .eggs,.tox,docs,env,venv
|
||||
skip = .eggs,.tox,docs,env,venv,node_modules
|
||||
skip_glob = **/migrations/*
|
||||
not_skip = __init__.py
|
||||
atomic = true
|
||||
multi_line_output = 5
|
||||
line_length = 120
|
||||
known_future_library = future,six
|
||||
known_future_library = six
|
||||
known_standard_library =
|
||||
collections,copy,distutils,functools,inspect,io,json,logging,operator,os,pkg_resources,re,setuptools,sys,
|
||||
types,warnings
|
||||
|
||||
Regular → Executable
+2
@@ -3,6 +3,8 @@ set -ev
|
||||
npm update
|
||||
|
||||
cp node_modules/redoc/bundles/redoc.standalone.js src/drf_yasg/static/drf-yasg/redoc/redoc.min.js
|
||||
wget https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js -O src/drf_yasg/static/drf-yasg/redoc-old/redoc.min.js
|
||||
|
||||
cp -r node_modules/swagger-ui-dist src/drf_yasg/static/drf-yasg/
|
||||
pushd src/drf_yasg/static/drf-yasg/swagger-ui-dist/ >/dev/null
|
||||
rm -f package.json .npmignore README.md
|
||||
|
||||
Reference in New Issue
Block a user