Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee46f59fb1 | |||
| 3f7ad62950 | |||
| b38d3e6805 | |||
| 058fd7096d | |||
| a32321d43a | |||
| f15e70b7db | |||
| 64c280e222 | |||
| d5073081d9 | |||
| 10c7e22940 | |||
| 6c497b32b4 | |||
| 73c60a8fda | |||
| 97082e8898 | |||
| 743396617e | |||
| 97cd1b63d9 | |||
| 71dee6eb45 | |||
| 7fa0cc0639 | |||
| e8c688f981 | |||
| c01a6ead26 |
Generated
+3
-3
@@ -13,9 +13,9 @@
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/testproj" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.6 (drf-yasg)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
@@ -24,7 +24,7 @@
|
||||
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||
<option name="TEMPLATE_FOLDERS">
|
||||
<list>
|
||||
<option value="$MODULE_DIR$/drf_yasg/templates" />
|
||||
<option value="$MODULE_DIR$/src/drf_yasg/templates" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
@@ -32,4 +32,4 @@
|
||||
<option name="projectConfiguration" value="py.test" />
|
||||
<option name="PROJECT_TEST_RUNNER" value="py.test" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
+1
-1
@@ -10,7 +10,7 @@ License
|
||||
BSD 3-Clause License
|
||||
********************
|
||||
|
||||
Copyright (c) 2017, Cristian V. <cristi@cvjd.me> |br|\ All rights reserved.
|
||||
Copyright (c) 2018, Cristian V. <cristi@cvjd.me> |br|\ All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+1
-1
@@ -362,7 +362,7 @@ provided out of the box - if you have ``djangorestframework-camel-case`` install
|
||||
:alt: Codecov
|
||||
|
||||
.. |pypi-version| image:: https://img.shields.io/pypi/v/drf-yasg.svg
|
||||
:target: https://pypi.python.org/pypi/drf-yasg/
|
||||
:target: https://pypi.org/project/drf-yasg/
|
||||
:alt: PyPI
|
||||
|
||||
.. |rtd-badge| image:: https://img.shields.io/readthedocs/drf-yasg.svg
|
||||
|
||||
@@ -2,10 +2,66 @@
|
||||
Changelog
|
||||
#########
|
||||
|
||||
|
||||
*********
|
||||
**1.4.4**
|
||||
*********
|
||||
|
||||
*Release date: Feb 26, 2018*
|
||||
|
||||
- **IMPROVED:** ``type`` for ``ChoiceField`` generated by a ``ModelSerializer`` from a model field with ``choices=...``
|
||||
will now be set according to the associated model field (:issue:`69`)
|
||||
- **FIXED:** ``lookup_field`` and ``lookup_value_regex`` on the same ``ViewSet`` will no longer trigger an exception
|
||||
(:issue:`68`)
|
||||
|
||||
*********
|
||||
**1.4.3**
|
||||
*********
|
||||
|
||||
*Release date: Feb 22, 2018*
|
||||
|
||||
- **FIXED:** added a missing assignment that would cause the ``default`` argument to ``openapi.Parameter.__init__`` to
|
||||
be ignored
|
||||
|
||||
*********
|
||||
**1.4.2**
|
||||
*********
|
||||
|
||||
*Release date: Feb 22, 2018*
|
||||
|
||||
- **FIXED:** fixed a bug that causes a ``ModelViewSet`` generated from models with nested ``ForeignKey`` to output
|
||||
models named ``Nested`` into the ``definitions`` section (:issue:`59`, :pr:`65`)
|
||||
- **FIXED:** ``Response`` objects without a ``schema`` are now properly handled when passed through
|
||||
``swagger_auto_schema`` (:issue:`66`)
|
||||
|
||||
*********
|
||||
**1.4.1**
|
||||
*********
|
||||
|
||||
*Release date: Feb 21, 2018*
|
||||
|
||||
- **FIXED:** the ``coerce_to_string`` is now respected when setting the type, default value and min/max values of
|
||||
``DecimalField`` in the OpenAPI schema (:issue:`62`)
|
||||
- **FIXED:** error responses from web UI views are now rendered with ``TemplateHTMLRenderer`` instead of throwing
|
||||
confusing errors (:issue:`58`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.10.0
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 1.21.0
|
||||
|
||||
*********
|
||||
**1.4.0**
|
||||
*********
|
||||
|
||||
*Release date: Feb 04, 2018*
|
||||
|
||||
- **ADDED:** added settings for OAuth2 client configuration in ``swagger-ui`` (:issue:`53`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.9.3
|
||||
|
||||
*********
|
||||
**1.3.1**
|
||||
*********
|
||||
|
||||
*Release date: Jan 24, 2018*
|
||||
|
||||
- **FIXED:** fixed a bug that would sometimes cause endpoints to wrongly be output as form operations (:issue:`50`)
|
||||
- **IMPROVED:** added generation of ``produces`` based on renderer classes
|
||||
- **IMPROVED:** added generation of top-level ``consumes`` and ``produces`` based on
|
||||
@@ -15,6 +71,8 @@ Changelog
|
||||
**1.3.0**
|
||||
*********
|
||||
|
||||
*Release date: Jan 23, 2018*
|
||||
|
||||
- **ADDED:** security requirements are now correctly set and can be customized; this should fix problems related
|
||||
to authentication in ``swagger-ui`` Try it out! (:issue:`50`, :pr:`54`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.9.2
|
||||
@@ -25,6 +83,8 @@ Changelog
|
||||
**1.2.2**
|
||||
*********
|
||||
|
||||
*Release date: Jan 12, 2018*
|
||||
|
||||
- **FIXED:** djangorestframework>=3.7.7 is now required because of breaking changes
|
||||
(:issue:`44`, :pr:`45`, thanks to :ghuser:`h-hirokawa`)
|
||||
|
||||
@@ -32,12 +92,16 @@ Changelog
|
||||
**1.2.1**
|
||||
*********
|
||||
|
||||
*Release date: Jan 12, 2018*
|
||||
|
||||
- Fixed deployment issues
|
||||
|
||||
*********
|
||||
**1.2.0**
|
||||
*********
|
||||
|
||||
*Release date: Jan 12, 2018 (missing from PyPI due to deployment issues)*
|
||||
|
||||
- **ADDED:** ``basePath`` is now generated by taking into account the ``SCRIPT_NAME`` variable and the
|
||||
longest common prefix of API urls (:issue:`37`, :pr:`42`)
|
||||
- **IMPROVED:** removed inline scripts and styles from bundled HTML templates to increase CSP compatibility
|
||||
@@ -49,6 +113,8 @@ Changelog
|
||||
**1.1.3**
|
||||
*********
|
||||
|
||||
*Release date: Jan 02, 2018*
|
||||
|
||||
- **FIXED:** schema view cache will now always ``Vary`` on the ``Cookie`` and ``Authentication`` (the
|
||||
``Vary`` header was previously only added if ``public`` was set to ``True``) - this fixes issues related to Django
|
||||
authentication in ``swagger-ui`` and ``CurrentUserDefault`` values in the schema
|
||||
@@ -57,6 +123,8 @@ Changelog
|
||||
**1.1.2**
|
||||
*********
|
||||
|
||||
*Release date: Jan 01, 2018*
|
||||
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.8.1
|
||||
- **IMPROVED:** removed some unneeded static files
|
||||
|
||||
@@ -64,6 +132,8 @@ Changelog
|
||||
**1.1.1**
|
||||
*********
|
||||
|
||||
*Release date: Dec 27, 2017*
|
||||
|
||||
- **ADDED:** :ref:`generate_swagger management command <management-command>`
|
||||
(:issue:`29`, :pr:`31`, thanks to :ghuser:`beaugunderson`)
|
||||
- **FIXED:** fixed improper generation of ``\Z`` regex tokens - will now be repalced by ``$``
|
||||
@@ -72,6 +142,8 @@ Changelog
|
||||
**1.1.0**
|
||||
*********
|
||||
|
||||
*Release date: Dec 27, 2017*
|
||||
|
||||
- **ADDED:** added support for APIs versioned with ``URLPathVersioning`` or ``NamespaceVersioning``
|
||||
- **ADDED:** added ability to recursively customize schema generation
|
||||
:ref:`using pluggable inspector classes <custom-spec-inspectors>`
|
||||
@@ -87,6 +159,8 @@ Changelog
|
||||
**1.0.6**
|
||||
*********
|
||||
|
||||
*Release date: Dec 23, 2017*
|
||||
|
||||
- **FIXED:** Swagger UI "Try it out!" should now work with Django login
|
||||
- **FIXED:** callable ``default`` values on serializer fields will now be properly called (:pr:`24`, :issue:`25`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.8.0
|
||||
@@ -98,6 +172,8 @@ Changelog
|
||||
**1.0.5**
|
||||
*********
|
||||
|
||||
*Release date: Dec 18, 2017*
|
||||
|
||||
- **FIXED:** fixed a crash caused by having read-only Serializers nested by reference
|
||||
- **FIXED:** removed erroneous backslashes in paths when routes are generated using Django 2
|
||||
`path() <https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.path>`_
|
||||
@@ -109,6 +185,8 @@ Changelog
|
||||
**1.0.4**
|
||||
*********
|
||||
|
||||
*Release date: Dec 16, 2017*
|
||||
|
||||
- **FIXED:** fixed improper generation of YAML references
|
||||
- **ADDED:** added ``query_serializer`` parameter to
|
||||
:func:`@swagger_auto_schema <.swagger_auto_schema>` (:issue:`16`, :pr:`17`)
|
||||
@@ -117,6 +195,8 @@ Changelog
|
||||
**1.0.3**
|
||||
*********
|
||||
|
||||
*Release date: Dec 15, 2017*
|
||||
|
||||
- **FIXED:** fixed bug that caused schema views returned from cache to fail (:issue:`14`)
|
||||
- **FIXED:** disabled automatic generation of response schemas for form operations to avoid confusing errors caused by
|
||||
attempting to shove file parameters into Schema objects
|
||||
@@ -125,4 +205,6 @@ Changelog
|
||||
**1.0.2**
|
||||
*********
|
||||
|
||||
*Release date: Dec 13, 2017*
|
||||
|
||||
- First published version
|
||||
|
||||
+24
-24
@@ -39,7 +39,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'drf-yasg'
|
||||
copyright = '2017, Cristi V.'
|
||||
copyright = '2018, Cristi V.'
|
||||
author = 'Cristi V.'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
@@ -162,6 +162,18 @@ autodoc_mock_imports = []
|
||||
|
||||
nitpick_ignore = [
|
||||
('py:class', 'object'),
|
||||
('py:class', 'bool'),
|
||||
('py:class', 'dict'),
|
||||
('py:class', 'list'),
|
||||
('py:class', 'str'),
|
||||
('py:class', 'int'),
|
||||
('py:class', 'bytes'),
|
||||
('py:class', 'tuple'),
|
||||
('py:class', 'callable'),
|
||||
('py:class', 'type'),
|
||||
('py:class', 'OrderedDict'),
|
||||
('py:class', 'None'),
|
||||
|
||||
('py:class', 'Exception'),
|
||||
('py:class', 'collections.OrderedDict'),
|
||||
|
||||
@@ -174,29 +186,17 @@ nitpick_ignore = [
|
||||
('py:class', 'OpenAPICodecJson'),
|
||||
('py:class', 'OpenAPISchemaGenerator'),
|
||||
|
||||
('py:obj', 'bool'),
|
||||
('py:obj', 'dict'),
|
||||
('py:obj', 'list'),
|
||||
('py:obj', 'str'),
|
||||
('py:obj', 'int'),
|
||||
('py:obj', 'bytes'),
|
||||
('py:obj', 'tuple'),
|
||||
('py:obj', 'callable'),
|
||||
('py:obj', 'type'),
|
||||
('py:obj', 'OrderedDict'),
|
||||
('py:obj', 'None'),
|
||||
|
||||
('py:obj', 'coreapi.Field'),
|
||||
('py:obj', 'BaseFilterBackend'),
|
||||
('py:obj', 'BasePagination'),
|
||||
('py:obj', 'Request'),
|
||||
('py:obj', 'rest_framework.request.Request'),
|
||||
('py:obj', 'rest_framework.serializers.Field'),
|
||||
('py:obj', 'serializers.Field'),
|
||||
('py:obj', 'serializers.BaseSerializer'),
|
||||
('py:obj', 'Serializer'),
|
||||
('py:obj', 'BaseSerializer'),
|
||||
('py:obj', 'APIView'),
|
||||
('py:class', 'coreapi.Field'),
|
||||
('py:class', 'BaseFilterBackend'),
|
||||
('py:class', 'BasePagination'),
|
||||
('py:class', 'Request'),
|
||||
('py:class', 'rest_framework.request.Request'),
|
||||
('py:class', 'rest_framework.serializers.Field'),
|
||||
('py:class', 'serializers.Field'),
|
||||
('py:class', 'serializers.BaseSerializer'),
|
||||
('py:class', 'Serializer'),
|
||||
('py:class', 'BaseSerializer'),
|
||||
('py:class', 'APIView'),
|
||||
]
|
||||
|
||||
# even though the package should be already installed, the sphinx build on RTD
|
||||
|
||||
+27
-1
@@ -172,7 +172,11 @@ You can define some per-serializer options by adding a ``Meta`` class to your se
|
||||
Currently, the only option you can add here is
|
||||
|
||||
* ``ref_name`` - a string which will be used as the model definition name for this serializer class; setting it to
|
||||
``None`` will force the serializer to be generated as an inline model everywhere it is used
|
||||
``None`` will force the serializer to be generated as an inline model everywhere it is used. If two serializers
|
||||
have the same ``ref_name``, both their usages will be replaced with a reference to the same definition.
|
||||
If this option is not specified, all serializers have an implicit name derived from their class name, minus any
|
||||
``Serializer`` suffix (e.g. ``UserSerializer`` -> ``User``, ``SerializerWithSuffix`` -> ``SerializerWithSuffix``)
|
||||
|
||||
|
||||
*************************
|
||||
Subclassing and extending
|
||||
@@ -301,3 +305,25 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
|
||||
This means that you should generally avoid view or method-specific ``FieldInspector``\ s if you are dealing with
|
||||
references (a.k.a named models), because you can never know which view will be the first to generate the schema
|
||||
for a given serializer.
|
||||
|
||||
**IMPORTANT:** nested fields on ``ModelSerializer``\ s that are generated from model ``ForeignKeys`` will always be
|
||||
output by value. If you want the by-reference behaviour you have to explictly set the serializer class of nested
|
||||
fields instead of letting ``ModelSerializer`` generate one automatically; for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class OneSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SomeModel
|
||||
fields = ('id',)
|
||||
|
||||
|
||||
class AnotherSerializer(serializers.ModelSerializer):
|
||||
chilf = OneSerializer()
|
||||
|
||||
class Meta:
|
||||
model = SomeParentModel
|
||||
fields = ('id', 'child')
|
||||
|
||||
Another caveat that stems from this is that any serializer named "``NestedSerializer``" will be forced inline
|
||||
unless it has a ``ref_name`` set explicitly.
|
||||
|
||||
@@ -47,3 +47,19 @@ Operation-level overrides can be added using the ``security`` parameter of
|
||||
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`.
|
||||
|
||||
|
||||
-------------------------------
|
||||
``swagger-ui`` as OAuth2 client
|
||||
-------------------------------
|
||||
|
||||
It is possible to configure ``swagger-ui`` to authenticate against your (or a third party) OAuth2 service when sending
|
||||
"Try it out" requests. This client-side configuration does not remove the requirement of a spec-side
|
||||
:ref:`security definiiton <security-definitions-settings>`, but merely allows you to test OAuth2 APIs using
|
||||
``swagger-ui`` as a client.
|
||||
|
||||
**DISCLAIMER**: this setup is very poorly tested as I do not currently implement OAuth in any of my projects. All
|
||||
contributions relating to documentation, bugs, mistakes or anything else are welcome as an issue or pull request. The
|
||||
settings described below were added as a result of discussion in issue :issue:`53`.
|
||||
|
||||
The settings of interest can be found on the :ref:`settings page <oauth2-settings>`. Configuration options are similar
|
||||
to most OAuth client setups like web or mobile applications. Reading the relevant ``swagger-ui`` docmentation linked
|
||||
will also probably help.
|
||||
|
||||
@@ -253,6 +253,25 @@ Controls how many levels are expaned by default when showing nested models.
|
||||
**Default**: :python:`3` |br|
|
||||
*Maps to parameter*: ``defaultModelExpandDepth``
|
||||
|
||||
.. _oauth2-settings:
|
||||
|
||||
OAUTH2_REDIRECT_URL
|
||||
-------------------
|
||||
|
||||
Used when OAuth2 authenitcation of API requests via swagger-ui is desired.
|
||||
|
||||
**Default**: :python:`None` |br|
|
||||
*Maps to parameter*: ``oauth2RedirectUrl``
|
||||
|
||||
OAUTH2_CONFIG
|
||||
-------------
|
||||
|
||||
Used when OAuth2 authenitcation of API requests via swagger-ui is desired. Provides OAuth2 configuration parameters
|
||||
to the ``SwaggerUIBundle#initOAuth`` method, and must be a dictionary. See
|
||||
`OAuth2 configuration <https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/oauth2.md>`_.
|
||||
|
||||
**Default**: :python:`{}`
|
||||
|
||||
******************
|
||||
``REDOC_SETTINGS``
|
||||
******************
|
||||
|
||||
Generated
+45
-42
@@ -4,9 +4,9 @@
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"argparse": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
|
||||
"integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "1.0.3"
|
||||
}
|
||||
@@ -38,9 +38,9 @@
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.13.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
|
||||
"integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==",
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz",
|
||||
"integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==",
|
||||
"optional": true
|
||||
},
|
||||
"core-js": {
|
||||
@@ -73,9 +73,9 @@
|
||||
"integrity": "sha1-8TyUAhQdoJ50rfTmN5jXkiBEOPI="
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.2.tgz",
|
||||
"integrity": "sha512-LSas5vsuA6Q4nEdf9wokY5/AJYXry98i0IzXsv49rYsgDGDNDPbqAYR1Pe23iFxygfbGZNR/5VrHXBCh2BhvUQ=="
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
|
||||
"integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ=="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.0",
|
||||
@@ -126,7 +126,7 @@
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
|
||||
"integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
|
||||
"requires": {
|
||||
"argparse": "1.0.9",
|
||||
"argparse": "1.0.10",
|
||||
"esprima": "4.0.0"
|
||||
}
|
||||
},
|
||||
@@ -145,10 +145,10 @@
|
||||
"requires": {
|
||||
"call-me-maybe": "1.0.1",
|
||||
"debug": "3.1.0",
|
||||
"es6-promise": "4.2.2",
|
||||
"es6-promise": "4.2.4",
|
||||
"js-yaml": "3.10.0",
|
||||
"ono": "4.0.3",
|
||||
"z-schema": "3.19.0"
|
||||
"z-schema": "3.19.1"
|
||||
}
|
||||
},
|
||||
"lodash.get": {
|
||||
@@ -185,9 +185,12 @@
|
||||
}
|
||||
},
|
||||
"openapi-sampler": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-0.4.3.tgz",
|
||||
"integrity": "sha512-Ml6o1gt++ZQ4JKL344YRo/fX05yuM6C+l/mGVX2yjhu1BRKyrRK4Z46uBTKSVaag1xINBFwYG7dZdz/10AmPzA=="
|
||||
"version": "1.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.0.0-beta.8.tgz",
|
||||
"integrity": "sha1-v0P/R3N/xOH5iNDiCC1JeI9B3q0=",
|
||||
"requires": {
|
||||
"json-pointer": "0.6.0"
|
||||
}
|
||||
},
|
||||
"perfect-scrollbar": {
|
||||
"version": "0.8.1",
|
||||
@@ -195,36 +198,36 @@
|
||||
"integrity": "sha512-RNC5tX/JMRYR+qVdJTEAWnRxw0Yf9lvbO8lTuAOvgDODkiA8lveTSkvrNMhmaGKEyimJpJl+myb/syVS9YyPuw=="
|
||||
},
|
||||
"prismjs": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.10.0.tgz",
|
||||
"integrity": "sha1-d+UYfCrmsyU/zDEwKc8l/lN3hyE=",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.11.0.tgz",
|
||||
"integrity": "sha1-KXrvM+t5Qhv9sZJzpQkspRWXDSk=",
|
||||
"requires": {
|
||||
"clipboard": "1.7.1"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
|
||||
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
|
||||
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz",
|
||||
"integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==",
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "1.0.7",
|
||||
"process-nextick-args": "2.0.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
"string_decoder": "1.0.3",
|
||||
"util-deprecate": "1.0.2"
|
||||
}
|
||||
},
|
||||
"redoc": {
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/redoc/-/redoc-1.20.0.tgz",
|
||||
"integrity": "sha1-1c16xoQKJ8/7RzvSiYAFUq+CHq8=",
|
||||
"version": "1.21.0",
|
||||
"resolved": "https://registry.npmjs.org/redoc/-/redoc-1.21.0.tgz",
|
||||
"integrity": "sha1-RY8E7b7MqyVbQORhZA0eRG949N0=",
|
||||
"requires": {
|
||||
"core-js": "2.5.3",
|
||||
"dropkickjs": "2.1.10",
|
||||
@@ -234,9 +237,9 @@
|
||||
"json-schema-ref-parser": "3.3.1",
|
||||
"lunr": "1.0.0",
|
||||
"mark.js": "8.11.1",
|
||||
"openapi-sampler": "0.4.3",
|
||||
"openapi-sampler": "1.0.0-beta.8",
|
||||
"perfect-scrollbar": "0.8.1",
|
||||
"prismjs": "1.10.0",
|
||||
"prismjs": "1.11.0",
|
||||
"remarkable": "1.7.1",
|
||||
"scrollparent": "2.0.1",
|
||||
"slugify": "1.2.9",
|
||||
@@ -298,7 +301,7 @@
|
||||
"requires": {
|
||||
"builtin-status-codes": "3.0.0",
|
||||
"inherits": "2.0.3",
|
||||
"readable-stream": "2.3.3",
|
||||
"readable-stream": "2.3.4",
|
||||
"to-arraybuffer": "1.0.1",
|
||||
"xtend": "4.0.1"
|
||||
}
|
||||
@@ -312,9 +315,9 @@
|
||||
}
|
||||
},
|
||||
"swagger-ui-dist": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.9.2.tgz",
|
||||
"integrity": "sha1-XuK7G2IFPC3ydGnlVZDGX8AOidQ="
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.10.0.tgz",
|
||||
"integrity": "sha1-ilrzP/ImPHFaFD9z8qjUfVOZQ+8="
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.0.2",
|
||||
@@ -348,9 +351,9 @@
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"validator": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-9.2.0.tgz",
|
||||
"integrity": "sha512-6Ij4Eo0KM4LkR0d0IegOwluG5453uqT5QyF5SV5Ezvm8/zmkKI/L4eoraafZGlZPC9guLkwKzgypcw8VGWWnGA=="
|
||||
"version": "9.4.1",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz",
|
||||
"integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA=="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
@@ -358,14 +361,14 @@
|
||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
|
||||
},
|
||||
"z-schema": {
|
||||
"version": "3.19.0",
|
||||
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.19.0.tgz",
|
||||
"integrity": "sha512-V94f3ODuluBS4kQLLjNhwoMek0dyIXCsvNu/A17dAyJ6sMhT5KkJQwSn07R0naByLIXJWMDk+ruMfI/3G3hS4Q==",
|
||||
"version": "3.19.1",
|
||||
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.19.1.tgz",
|
||||
"integrity": "sha512-jPNzqmOu3+AGbb4krDODqo4QBzwUGDVzyfGyy1HtWaUnafltQotatSpxxWd6Mp0iSZOUwHU5sqKYi+U8HsHMkg==",
|
||||
"requires": {
|
||||
"commander": "2.13.0",
|
||||
"commander": "2.14.1",
|
||||
"lodash.get": "4.4.2",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"validator": "9.2.0"
|
||||
"validator": "9.4.1"
|
||||
}
|
||||
},
|
||||
"zone.js": {
|
||||
|
||||
+8
-3
@@ -1,7 +1,12 @@
|
||||
{
|
||||
"name": "drf-yasg",
|
||||
"dependencies": {
|
||||
"redoc": "^1.20.0",
|
||||
"swagger-ui-dist": "^3.9.2"
|
||||
}
|
||||
"redoc": "^1.21.0",
|
||||
"swagger-ui-dist": "^3.10.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/axnsan12/drf-yasg.git"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ openapi_codec>=1.3.2
|
||||
ruamel.yaml>=0.15.34
|
||||
inflection>=0.3.1
|
||||
future>=0.16.0
|
||||
six>=1.11.0
|
||||
six>=1.10.0
|
||||
uritemplate>=3.0.0
|
||||
|
||||
djangorestframework>=3.7.7
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# used by the 'docs' tox env for building the documentation
|
||||
Sphinx>=1.6.5
|
||||
Sphinx>=1.7.0
|
||||
sphinx_rtd_theme>=0.2.4
|
||||
Pillow>=4.3.0
|
||||
readme_renderer>=17.2
|
||||
|
||||
@@ -43,6 +43,8 @@ SWAGGER_DEFAULTS = {
|
||||
'SHOW_EXTENSIONS': True,
|
||||
'DEFAULT_MODEL_RENDERING': 'model',
|
||||
'DEFAULT_MODEL_DEPTH': 3,
|
||||
'OAUTH2_REDIRECT_URL': None,
|
||||
'OAUTH2_CONFIG': {},
|
||||
}
|
||||
|
||||
REDOC_DEFAULTS = {
|
||||
|
||||
@@ -405,8 +405,8 @@ class OpenAPISchemaGenerator(object):
|
||||
for variable in uritemplate.variables(path):
|
||||
model, model_field = get_queryset_field(queryset, variable)
|
||||
attrs = get_basic_type_info(model_field) or {'type': openapi.TYPE_STRING}
|
||||
if hasattr(view_cls, 'lookup_value_regex') and getattr(view_cls, 'lookup_field', None) == variable:
|
||||
attrs['pattern'] = view_cls.lookup_value_regex
|
||||
if getattr(view_cls, 'lookup_field', None) == variable and attrs['type'] == openapi.TYPE_STRING:
|
||||
attrs['pattern'] = getattr(view_cls, 'lookup_value_regex', attrs.get('pattern', None))
|
||||
|
||||
if model_field and model_field.help_text:
|
||||
description = force_text(model_field.help_text)
|
||||
|
||||
@@ -6,7 +6,7 @@ from rest_framework import serializers
|
||||
from rest_framework.utils import encoders, json
|
||||
|
||||
from .. import openapi
|
||||
from ..utils import is_list_view
|
||||
from ..utils import decimal_as_float, is_list_view
|
||||
|
||||
#: Sentinel value that inspectors must return to signal that they do not know how to handle an object
|
||||
NotHandled = object()
|
||||
@@ -224,6 +224,8 @@ class FieldInspector(BaseInspector):
|
||||
# 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)
|
||||
@@ -232,7 +234,8 @@ class FieldInspector(BaseInspector):
|
||||
if default is not None:
|
||||
instance_kwargs['default'] = default
|
||||
|
||||
instance_kwargs.setdefault('title', title)
|
||||
if instance_kwargs.get('type', None) != openapi.TYPE_ARRAY:
|
||||
instance_kwargs.setdefault('title', title)
|
||||
instance_kwargs.setdefault('description', description)
|
||||
instance_kwargs.update(kwargs)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import logging
|
||||
import operator
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
@@ -8,9 +10,11 @@ from rest_framework.settings import api_settings as rest_framework_settings
|
||||
|
||||
from .. import openapi
|
||||
from ..errors import SwaggerGenerationError
|
||||
from ..utils import filter_none
|
||||
from ..utils import decimal_as_float, filter_none
|
||||
from .base import FieldInspector, NotHandled, SerializerInspector
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InlineSerializerInspector(SerializerInspector):
|
||||
"""Provides serializer conversions using :meth:`.FieldInspector.field_to_swagger_object`."""
|
||||
@@ -53,10 +57,14 @@ class InlineSerializerInspector(SerializerInspector):
|
||||
|
||||
serializer = field
|
||||
serializer_meta = getattr(serializer, 'Meta', None)
|
||||
serializer_name = type(serializer).__name__
|
||||
if hasattr(serializer_meta, 'ref_name'):
|
||||
ref_name = serializer_meta.ref_name
|
||||
elif serializer_name == 'NestedSerializer' and isinstance(serializer, serializers.ModelSerializer):
|
||||
logger.debug("Forcing inline output for ModelSerializer named 'NestedSerializer': " + str(serializer))
|
||||
ref_name = None
|
||||
else:
|
||||
ref_name = type(serializer).__name__
|
||||
ref_name = serializer_name
|
||||
if ref_name.endswith('Serializer'):
|
||||
ref_name = ref_name[:-len('Serializer')]
|
||||
|
||||
@@ -76,11 +84,17 @@ class InlineSerializerInspector(SerializerInspector):
|
||||
if child.required:
|
||||
required.append(property_name)
|
||||
|
||||
return SwaggerType(
|
||||
result = SwaggerType(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties=properties,
|
||||
required=required or None,
|
||||
)
|
||||
if not ref_name:
|
||||
# on an inline model, the title is derived from the field name
|
||||
# but is visually displayed like the model named, which is confusing
|
||||
# it is better to just remove title from inline models
|
||||
del result.title
|
||||
return result
|
||||
|
||||
if not ref_name or not use_references:
|
||||
return make_schema_definition()
|
||||
@@ -258,18 +272,29 @@ def find_limits(field):
|
||||
if isinstance(field, field_class)
|
||||
]
|
||||
|
||||
if isinstance(field, serializers.DecimalField) and not decimal_as_float(field):
|
||||
return limits
|
||||
|
||||
for validator in field.validators:
|
||||
if not hasattr(validator, 'limit_value'):
|
||||
continue
|
||||
|
||||
limit_value = validator.limit_value
|
||||
if isinstance(limit_value, Decimal) and decimal_as_float(field):
|
||||
limit_value = float(limit_value)
|
||||
|
||||
for validator_class, attr, improves in applicable_limits:
|
||||
if isinstance(validator, validator_class):
|
||||
if attr not in limits or improves(validator.limit_value, limits[attr]):
|
||||
limits[attr] = validator.limit_value
|
||||
if attr not in limits or improves(limit_value, limits[attr]):
|
||||
limits[attr] = limit_value
|
||||
|
||||
return OrderedDict(sorted(limits.items()))
|
||||
|
||||
|
||||
def decimal_field_type(field):
|
||||
return openapi.TYPE_NUMBER if decimal_as_float(field) else openapi.TYPE_STRING
|
||||
|
||||
|
||||
model_field_to_basic_type = [
|
||||
(models.AutoField, (openapi.TYPE_INTEGER, None)),
|
||||
(models.BinaryField, (openapi.TYPE_STRING, openapi.FORMAT_BINARY)),
|
||||
@@ -277,7 +302,7 @@ model_field_to_basic_type = [
|
||||
(models.NullBooleanField, (openapi.TYPE_BOOLEAN, None)),
|
||||
(models.DateTimeField, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)),
|
||||
(models.DateField, (openapi.TYPE_STRING, openapi.FORMAT_DATE)),
|
||||
(models.DecimalField, (openapi.TYPE_NUMBER, None)),
|
||||
(models.DecimalField, (decimal_field_type, openapi.FORMAT_DECIMAL)),
|
||||
(models.DurationField, (openapi.TYPE_INTEGER, None)),
|
||||
(models.FloatField, (openapi.TYPE_NUMBER, None)),
|
||||
(models.IntegerField, (openapi.TYPE_INTEGER, None)),
|
||||
@@ -300,9 +325,11 @@ serializer_field_to_basic_type = [
|
||||
(serializers.UUIDField, (openapi.TYPE_STRING, openapi.FORMAT_UUID)),
|
||||
(serializers.RegexField, (openapi.TYPE_STRING, None)),
|
||||
(serializers.CharField, (openapi.TYPE_STRING, None)),
|
||||
((serializers.BooleanField, serializers.NullBooleanField), (openapi.TYPE_BOOLEAN, None)),
|
||||
(serializers.BooleanField, (openapi.TYPE_BOOLEAN, None)),
|
||||
(serializers.NullBooleanField, (openapi.TYPE_BOOLEAN, None)),
|
||||
(serializers.IntegerField, (openapi.TYPE_INTEGER, None)),
|
||||
((serializers.FloatField, serializers.DecimalField), (openapi.TYPE_NUMBER, None)),
|
||||
(serializers.FloatField, (openapi.TYPE_NUMBER, None)),
|
||||
(serializers.DecimalField, (decimal_field_type, openapi.FORMAT_DECIMAL)),
|
||||
(serializers.DurationField, (openapi.TYPE_NUMBER, None)), # ?
|
||||
(serializers.DateField, (openapi.TYPE_STRING, openapi.FORMAT_DATE)),
|
||||
(serializers.DateTimeField, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)),
|
||||
@@ -326,13 +353,18 @@ def get_basic_type_info(field):
|
||||
for field_class, type_format in basic_type_info:
|
||||
if isinstance(field, field_class):
|
||||
swagger_type, format = type_format
|
||||
if callable(swagger_type):
|
||||
swagger_type = swagger_type(field)
|
||||
if callable(format):
|
||||
format = format(field)
|
||||
break
|
||||
else: # pragma: no cover
|
||||
return None
|
||||
|
||||
pattern = find_regex(field) if format in (None, openapi.FORMAT_SLUG) else None
|
||||
pattern = None
|
||||
if swagger_type == openapi.TYPE_STRING and format in (None, openapi.FORMAT_SLUG):
|
||||
pattern = find_regex(field)
|
||||
|
||||
limits = find_limits(field)
|
||||
|
||||
result = OrderedDict([
|
||||
@@ -365,16 +397,27 @@ class ChoiceFieldInspector(FieldInspector):
|
||||
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)
|
||||
|
||||
if isinstance(field, serializers.MultipleChoiceField):
|
||||
return SwaggerType(
|
||||
type=openapi.TYPE_ARRAY,
|
||||
items=ChildSwaggerType(
|
||||
type=openapi.TYPE_STRING,
|
||||
enum=list(field.choices.keys())
|
||||
if isinstance(field, serializers.ChoiceField):
|
||||
enum_type = openapi.TYPE_STRING
|
||||
|
||||
# for ModelSerializer, try to infer the type from the associated model field
|
||||
serializer = get_parent_serializer(field)
|
||||
if isinstance(serializer, serializers.ModelSerializer):
|
||||
model = getattr(getattr(serializer, 'Meta'), 'model')
|
||||
model_field = get_model_field(model, field.source)
|
||||
if model_field:
|
||||
enum_type = get_basic_type_info(model_field).get('type', enum_type)
|
||||
|
||||
if isinstance(field, serializers.MultipleChoiceField):
|
||||
return SwaggerType(
|
||||
type=openapi.TYPE_ARRAY,
|
||||
items=ChildSwaggerType(
|
||||
type=enum_type,
|
||||
enum=list(field.choices.keys())
|
||||
)
|
||||
)
|
||||
)
|
||||
elif isinstance(field, serializers.ChoiceField):
|
||||
return SwaggerType(type=openapi.TYPE_STRING, enum=list(field.choices.keys()))
|
||||
|
||||
return SwaggerType(type=enum_type, enum=list(field.choices.keys()))
|
||||
|
||||
return NotHandled
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
)
|
||||
elif isinstance(serializer, openapi.Response):
|
||||
response = serializer
|
||||
if not isinstance(response.schema, openapi.Schema.OR_REF):
|
||||
if hasattr(response, 'schema') and not isinstance(response.schema, openapi.Schema.OR_REF):
|
||||
serializer = force_serializer_instance(response.schema)
|
||||
response.schema = self.serializer_to_schema(serializer)
|
||||
elif isinstance(serializer, openapi.Schema.OR_REF):
|
||||
|
||||
@@ -35,6 +35,7 @@ FORMAT_URI = "uri" #:
|
||||
# pulled out of my ass
|
||||
FORMAT_UUID = "uuid" #:
|
||||
FORMAT_SLUG = "slug" #:
|
||||
FORMAT_DECIMAL = "decimal"
|
||||
|
||||
IN_BODY = 'body' #:
|
||||
IN_PATH = 'path' #:
|
||||
@@ -395,6 +396,7 @@ class Parameter(SwaggerDict):
|
||||
self.enum = enum
|
||||
self.pattern = pattern
|
||||
self.items = items
|
||||
self.default = default
|
||||
self._insert_extras__()
|
||||
if self['in'] == IN_PATH:
|
||||
# path parameters must always be required
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from django.shortcuts import render, resolve_url
|
||||
from rest_framework.renderers import BaseRenderer
|
||||
from rest_framework.renderers import BaseRenderer, 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
|
||||
|
||||
@@ -51,6 +53,11 @@ class _UIRenderer(BaseRenderer):
|
||||
template = ''
|
||||
|
||||
def render(self, swagger, accepted_media_type=None, renderer_context=None):
|
||||
if not isinstance(swagger, Swagger):
|
||||
# if `swagger` is not a ``Swagger`` object, it means we somehow got a non-success ``Response``
|
||||
# in that case, it's probably better to let the default ``TemplateHTMLRenderer`` render it
|
||||
# see https://github.com/axnsan12/drf-yasg/issues/58
|
||||
return TemplateHTMLRenderer().render(swagger, accepted_media_type, renderer_context)
|
||||
self.set_context(renderer_context, swagger)
|
||||
return render(
|
||||
renderer_context['request'],
|
||||
@@ -63,6 +70,7 @@ class _UIRenderer(BaseRenderer):
|
||||
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())
|
||||
|
||||
@@ -85,6 +93,7 @@ class _UIRenderer(BaseRenderer):
|
||||
'defaultModelRendering': swagger_settings.DEFAULT_MODEL_RENDERING,
|
||||
'defaultModelExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
|
||||
'defaultModelsExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
|
||||
'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
|
||||
}
|
||||
data = {k: v for k, v in data.items() if v is not None}
|
||||
if swagger_settings.VALIDATOR_URL != '':
|
||||
@@ -102,6 +111,11 @@ class _UIRenderer(BaseRenderer):
|
||||
|
||||
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.
|
||||
|
||||
+2
-2
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
@@ -49,6 +49,7 @@ function initSwaggerUi() {
|
||||
};
|
||||
|
||||
var swaggerSettings = JSON.parse(document.getElementById('swagger-settings').innerHTML);
|
||||
console.log(swaggerSettings);
|
||||
|
||||
for (var p in swaggerSettings) {
|
||||
if (swaggerSettings.hasOwnProperty(p)) {
|
||||
@@ -56,6 +57,10 @@ function initSwaggerUi() {
|
||||
}
|
||||
}
|
||||
window.ui = SwaggerUIBundle(swaggerConfig);
|
||||
|
||||
var oauth2Config = JSON.parse(document.getElementById('oauth2-config').innerHTML);
|
||||
console.log(oauth2Config);
|
||||
window.ui.initOAuth(oauth2Config);
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
|
||||
|
||||
<script id="swagger-settings" type="application/json">{{ swagger_settings | safe }}</script>
|
||||
<script id="oauth2-config" type="application/json">{{ oauth2_config | safe }}</script>
|
||||
|
||||
<script src="{% static 'drf-yasg/swagger-ui-dist/swagger-ui-bundle.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/swagger-ui-dist/swagger-ui-standalone-preset.js' %}"></script>
|
||||
|
||||
@@ -2,9 +2,11 @@ import inspect
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.db import models
|
||||
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.views import APIView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -118,6 +120,8 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
||||
|
||||
_methods = methods
|
||||
if methods or method:
|
||||
assert available_methods or http_method_names, "`method` or `methods` can only be specified " \
|
||||
"on @detail_route 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"
|
||||
@@ -273,3 +277,15 @@ def get_produces(renderer_classes):
|
||||
media_types = [renderer.media_type for renderer in renderer_classes or []]
|
||||
media_types = [encoding for encoding in media_types if 'html' not in encoding]
|
||||
return media_types
|
||||
|
||||
|
||||
def decimal_as_float(field):
|
||||
"""
|
||||
Returns true if ``field`` is a django-rest-framework DecimalField and its ``coerce_to_string`` attribute or the
|
||||
``COERCE_DECIMAL_TO_STRING`` setting is set to ``False``.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
if isinstance(field, serializers.DecimalField) or isinstance(field, models.DecimalField):
|
||||
return not getattr(field, 'coerce_to_string', rest_framework_settings.COERCE_DECIMAL_TO_STRING)
|
||||
return False
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.0.1 on 2018-02-26 18:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('articles', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='article_type',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, 'first'), (2, 'second'), (3, 'third'), (7, 'seven'), (8, 'eight')], help_text='IntegerField declared on model with choices=(...) and exposed via ModelSerializer', null=True),
|
||||
),
|
||||
]
|
||||
@@ -8,5 +8,9 @@ class Article(models.Model):
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
author = models.ForeignKey('auth.User', related_name='articles', on_delete=models.CASCADE)
|
||||
article_type = models.PositiveSmallIntegerField(
|
||||
help_text="IntegerField declared on model with choices=(...) and exposed via ModelSerializer",
|
||||
choices=((1, "first"), (2, "second"), (3, "third"), (7, "seven"), (8, "eight")), null=True
|
||||
)
|
||||
|
||||
cover = models.ImageField(upload_to='article/original/', blank=True)
|
||||
|
||||
@@ -16,7 +16,7 @@ class ArticleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Article
|
||||
fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified',
|
||||
'references', 'uuid', 'cover', 'cover_name')
|
||||
'references', 'uuid', 'cover', 'cover_name', 'article_type')
|
||||
read_only_fields = ('date_created', 'date_modified',
|
||||
'references', 'uuid', 'cover_name')
|
||||
lookup_field = 'slug'
|
||||
@@ -31,7 +31,7 @@ class ArticleSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class ImageUploadSerializer(serializers.Serializer):
|
||||
what_am_i_doing = serializers.RegexField(regex=r"^69$", help_text="test")
|
||||
what_am_i_doing = serializers.RegexField(regex=r"^69$", help_text="test", default="69")
|
||||
image_styles = serializers.ListSerializer(
|
||||
child=serializers.ChoiceField(choices=['wide', 'tall', 'thumb', 'social']),
|
||||
help_text="Parameter with Items"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
from rest_framework.compat import MinValueValidator
|
||||
|
||||
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet
|
||||
|
||||
|
||||
class LanguageSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.ChoiceField(
|
||||
choices=LANGUAGE_CHOICES, default='python', help_text='The name of the programming language')
|
||||
|
||||
@@ -14,7 +16,6 @@ class LanguageSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
class ExampleProjectSerializer(serializers.Serializer):
|
||||
|
||||
project_name = serializers.CharField(help_text='Name of the project')
|
||||
github_repo = serializers.CharField(required=True, help_text='Github repository of the project')
|
||||
|
||||
@@ -49,6 +50,10 @@ class SnippetSerializer(serializers.Serializer):
|
||||
example_projects = serializers.ListSerializer(child=ExampleProjectSerializer(), read_only=True)
|
||||
difficulty_factor = serializers.FloatField(help_text="this is here just to test FloatField",
|
||||
read_only=True, default=lambda: 6.9)
|
||||
rate_as_string = serializers.DecimalField(max_digits=6, decimal_places=3, default=Decimal('0.0'),
|
||||
validators=[MinValueValidator(Decimal('0.0'))])
|
||||
rate = serializers.DecimalField(max_digits=6, decimal_places=3, default=Decimal('0.0'), coerce_to_string=False,
|
||||
validators=[MinValueValidator(Decimal('0.0'))])
|
||||
|
||||
def create(self, validated_data):
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from djangorestframework_camel_case.parser import CamelCaseJSONParser
|
||||
from djangorestframework_camel_case.render import CamelCaseJSONRenderer
|
||||
from inflection import camelize
|
||||
from rest_framework import generics
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.parsers import FormParser
|
||||
|
||||
from drf_yasg import openapi
|
||||
@@ -62,8 +62,12 @@ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
type=openapi.TYPE_INTEGER,
|
||||
description="path parameter override",
|
||||
required=True
|
||||
),
|
||||
], responses={
|
||||
status.HTTP_204_NO_CONTENT: openapi.Response(
|
||||
description="This should not crash"
|
||||
)
|
||||
])
|
||||
})
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""delete method docstring"""
|
||||
return super(SnippetDetail, self).patch(request, *args, **kwargs)
|
||||
|
||||
@@ -26,6 +26,7 @@ INSTALLED_APPS = [
|
||||
'snippets',
|
||||
'users',
|
||||
'articles',
|
||||
'todo',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@@ -62,5 +62,6 @@ urlpatterns = [
|
||||
url(r'^snippets/', include('snippets.urls')),
|
||||
url(r'^articles/', include('articles.urls')),
|
||||
url(r'^users/', include('users.urls')),
|
||||
url(r'^todo/', include('todo.urls')),
|
||||
url(r'^plain/', plain_view),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-02-21 23:26
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Todo',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=50)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TodoAnother',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=50)),
|
||||
('todo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todo.Todo')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TodoYetAnother',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=50)),
|
||||
('todo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todo.TodoAnother')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Todo(models.Model):
|
||||
title = models.CharField(max_length=50)
|
||||
|
||||
|
||||
class TodoAnother(models.Model):
|
||||
todo = models.ForeignKey(Todo, on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=50)
|
||||
|
||||
|
||||
class TodoYetAnother(models.Model):
|
||||
todo = models.ForeignKey(TodoAnother, on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=50)
|
||||
@@ -0,0 +1,24 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Todo, TodoAnother, TodoYetAnother
|
||||
|
||||
|
||||
class TodoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Todo
|
||||
fields = ('title',)
|
||||
|
||||
|
||||
class TodoAnotherSerializer(serializers.ModelSerializer):
|
||||
todo = TodoSerializer()
|
||||
|
||||
class Meta:
|
||||
model = TodoAnother
|
||||
fields = ('title', 'todo')
|
||||
|
||||
|
||||
class TodoYetAnotherSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = TodoYetAnother
|
||||
fields = ('title', 'todo')
|
||||
depth = 2
|
||||
@@ -0,0 +1,10 @@
|
||||
from rest_framework import routers
|
||||
|
||||
from todo import views
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'', views.TodoViewSet)
|
||||
router.register(r'another', views.TodoAnotherViewSet)
|
||||
router.register(r'yetanother', views.TodoYetAnotherViewSet)
|
||||
|
||||
urlpatterns = router.urls
|
||||
@@ -0,0 +1,22 @@
|
||||
from rest_framework import viewsets
|
||||
|
||||
from .models import Todo, TodoAnother, TodoYetAnother
|
||||
from .serializer import TodoAnotherSerializer, TodoSerializer, TodoYetAnotherSerializer
|
||||
|
||||
|
||||
class TodoViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = Todo.objects.all()
|
||||
serializer_class = TodoSerializer
|
||||
|
||||
lookup_field = 'id'
|
||||
lookup_value_regex = '[0-9]+'
|
||||
|
||||
|
||||
class TodoAnotherViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = TodoAnother.objects.all()
|
||||
serializer_class = TodoAnotherSerializer
|
||||
|
||||
|
||||
class TodoYetAnotherViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = TodoYetAnother.objects.all()
|
||||
serializer_class = TodoYetAnotherSerializer
|
||||
+181
-9
@@ -198,9 +198,10 @@ paths:
|
||||
- name: what_am_i_doing
|
||||
in: formData
|
||||
description: test
|
||||
required: true
|
||||
required: false
|
||||
type: string
|
||||
pattern: ^69$
|
||||
default: '69'
|
||||
- name: image_styles
|
||||
in: formData
|
||||
description: Parameter with Items
|
||||
@@ -330,7 +331,7 @@ paths:
|
||||
type: integer
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
description: This should not crash
|
||||
tags:
|
||||
- snippets
|
||||
parameters:
|
||||
@@ -339,6 +340,105 @@ paths:
|
||||
description: A unique integer value identifying this snippet.
|
||||
required: true
|
||||
type: integer
|
||||
/todo/:
|
||||
get:
|
||||
operationId: todo_list
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Todo'
|
||||
tags:
|
||||
- todo
|
||||
parameters: []
|
||||
/todo/another/:
|
||||
get:
|
||||
operationId: todo_another_list
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TodoAnother'
|
||||
tags:
|
||||
- todo
|
||||
parameters: []
|
||||
/todo/another/{id}/:
|
||||
get:
|
||||
operationId: todo_another_read
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/TodoAnother'
|
||||
tags:
|
||||
- todo
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
description: A unique integer value identifying this todo another.
|
||||
required: true
|
||||
type: integer
|
||||
/todo/yetanother/:
|
||||
get:
|
||||
operationId: todo_yetanother_list
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TodoYetAnother'
|
||||
tags:
|
||||
- todo
|
||||
parameters: []
|
||||
/todo/yetanother/{id}/:
|
||||
get:
|
||||
operationId: todo_yetanother_read
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/TodoYetAnother'
|
||||
tags:
|
||||
- todo
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
description: A unique integer value identifying this todo yet another.
|
||||
required: true
|
||||
type: integer
|
||||
/todo/{id}/:
|
||||
get:
|
||||
operationId: todo_read
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/Todo'
|
||||
tags:
|
||||
- todo
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
description: A unique integer value identifying this todo.
|
||||
required: true
|
||||
type: integer
|
||||
/users/:
|
||||
get:
|
||||
operationId: users_list
|
||||
@@ -491,6 +591,16 @@ definitions:
|
||||
cover_name:
|
||||
type: string
|
||||
readOnly: true
|
||||
article_type:
|
||||
description: IntegerField declared on model with choices=(...) and exposed
|
||||
via ModelSerializer
|
||||
type: integer
|
||||
enum:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 7
|
||||
- 8
|
||||
Project:
|
||||
required:
|
||||
- projectName
|
||||
@@ -538,7 +648,6 @@ definitions:
|
||||
title: Linenos
|
||||
type: boolean
|
||||
language:
|
||||
title: Language
|
||||
description: Sample help text for language
|
||||
type: object
|
||||
properties:
|
||||
@@ -983,7 +1092,6 @@ definitions:
|
||||
- zephir
|
||||
default: python
|
||||
styles:
|
||||
title: Styles
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
@@ -1020,12 +1128,10 @@ definitions:
|
||||
default:
|
||||
- friendly
|
||||
lines:
|
||||
title: Lines
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
exampleProjects:
|
||||
title: Example projects
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Project'
|
||||
@@ -1036,6 +1142,75 @@ definitions:
|
||||
type: number
|
||||
readOnly: true
|
||||
default: 6.9
|
||||
rateAsString:
|
||||
title: Rate as string
|
||||
type: string
|
||||
format: decimal
|
||||
default: '0.000'
|
||||
rate:
|
||||
title: Rate
|
||||
type: number
|
||||
format: decimal
|
||||
default: 0.0
|
||||
minimum: 0.0
|
||||
Todo:
|
||||
required:
|
||||
- title
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
TodoAnother:
|
||||
required:
|
||||
- title
|
||||
- todo
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
todo:
|
||||
$ref: '#/definitions/Todo'
|
||||
TodoYetAnother:
|
||||
required:
|
||||
- title
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
todo:
|
||||
required:
|
||||
- title
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
title:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
todo:
|
||||
required:
|
||||
- title
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
title:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
readOnly: true
|
||||
readOnly: true
|
||||
UserSerializerrr:
|
||||
required:
|
||||
- username
|
||||
@@ -1060,13 +1235,11 @@ definitions:
|
||||
format: email
|
||||
maxLength: 254
|
||||
articles:
|
||||
title: Articles
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
uniqueItems: true
|
||||
snippets:
|
||||
title: Snippets
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
@@ -1084,7 +1257,6 @@ definitions:
|
||||
format: date
|
||||
readOnly: true
|
||||
article_slugs:
|
||||
title: Article slugs
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
@@ -46,3 +46,9 @@ def test_noop_inspectors(swagger_settings, mock_schema_request, codec_json, refe
|
||||
json_bytes = codec_json.encode(swagger)
|
||||
swagger_dict = json.loads(json_bytes.decode('utf-8'), object_pairs_hook=OrderedDict)
|
||||
compare_schemas(swagger_dict, reference_schema)
|
||||
|
||||
|
||||
def test_no_nested_model(swagger_dict):
|
||||
# ForeignKey models in deep ModelViewSets might wrongly be labeled as 'Nested' in the definitions section
|
||||
# see https://github.com/axnsan12/drf-yasg/issues/59
|
||||
assert 'Nested' not in swagger_dict['definitions']
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
envlist =
|
||||
py27-django111-drf37,
|
||||
py{34,35,36}-django{111,20}-drf37,
|
||||
py36-drfmaster,
|
||||
py36-django20-drfmaster,
|
||||
lint, docs
|
||||
|
||||
[travis:env]
|
||||
@@ -17,9 +17,8 @@ deps =
|
||||
|
||||
drf37: djangorestframework>=3.7.7,<3.8
|
||||
|
||||
# test with the latest build of Django and django-rest-framework to get early warning of compatibility issues
|
||||
# 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
|
||||
drfmaster: https://github.com/django/django/archive/master.tar.gz
|
||||
|
||||
# other dependencies
|
||||
-rrequirements/validation.txt
|
||||
@@ -28,9 +27,6 @@ deps =
|
||||
commands =
|
||||
pytest --cov --cov-config .coveragerc --cov-append --cov-report="" {posargs}
|
||||
|
||||
[testenv:py36-drfmaster]
|
||||
pip_pre = True
|
||||
|
||||
[testenv:lint]
|
||||
skip_install = true
|
||||
deps =
|
||||
|
||||
Reference in New Issue
Block a user