Compare commits

...

26 Commits

Author SHA1 Message Date
Cristi Vîjdea ee46f59fb1 Infer ChoiceField type from model field when in ModelSerializer
Fixes part of issue #69
2018-02-26 20:32:57 +02:00
Santiago Castro 3f7ad62950 Make 1.10.0 the min six version (#71) 2018-02-26 17:18:52 +02:00
Cristi Vîjdea b38d3e6805 Do not set pattern on non-string values
Fixes #68
2018-02-23 18:51:55 +02:00
Cristi Vîjdea 058fd7096d Change README link from pypi.python.org to pypi.org
Description rendering is prettier on the new version.
2018-02-22 20:46:26 +02:00
Cristi Vîjdea a32321d43a Add release dates to changelog 2018-02-22 20:46:22 +02:00
Cristi Vîjdea f15e70b7db Fix missing assignment for default in openapi.Parameter 2018-02-22 20:33:47 +02:00
Cristi Vîjdea 64c280e222 Allow Response objects with no schema
Fixes #66.
2018-02-22 03:46:44 +02:00
Cristi Vîjdea d5073081d9 Fix ModelViewSet Nested bug (#65)
* Add todo repro app
* Add explicit test against model named "Nested"
* Force serializers named NestedSerializer to be output as inline models
* Allow ref_name to rescue a NestedSerializer
* Add tests and documentation
2018-02-22 03:46:16 +02:00
Cristi Vîjdea 10c7e22940 Merge pull request #64 from axnsan12/release/1.4.1
Release version 1.4.1
2018-02-21 05:17:57 +02:00
Cristi Vîjdea 6c497b32b4 Use TemplateHTMLRenderer for error responses in UIRenderer
Closes #58.
2018-02-21 04:59:03 +02:00
Cristi Vîjdea 73c60a8fda Add assertion against mistaken swagger_auto_schema methods usage 2018-02-21 04:42:56 +02:00
Cristi Vîjdea 97082e8898 Update swagger-ui to 3.10.0 and ReDoc to 1.21.0 2018-02-21 04:42:54 +02:00
Cristi Vîjdea 743396617e Upgrade sphinx to 1.7.0 2018-02-21 04:42:53 +02:00
Cristi Vîjdea 97cd1b63d9 Take coerce_to_string into account when handling DecimalField
Closes #62.
2018-02-21 04:42:34 +02:00
Cristi Vîjdea 71dee6eb45 Add OAuth2 client configuration for swagger-ui (#57) 2018-02-04 14:36:54 +02:00
Cristi Vîjdea 7fa0cc0639 Make DRF master test run with stable Django 2018-02-04 14:36:16 +02:00
Cristi Vîjdea e8c688f981 Update swagger-ui to 3.9.3 2018-02-04 14:23:00 +02:00
Cristi Vîjdea c01a6ead26 Bring project into 2018 2018-01-29 00:18:53 +02:00
Cristi Vîjdea 05901e99aa Add 1.3.1 changelog 2018-01-24 17:43:38 +02:00
Cristi Vîjdea a211184478 Add documentation about excluding endpoints
Closes #50.
2018-01-24 17:38:10 +02:00
Cristi Vîjdea a3e81ef7f6 Improve handling of consumes and produces attributes (#55)
* Fix get_consumes
* Generate produces for Operation
* Set global consumes and produces from rest framework DEFAULT_ settings
2018-01-24 14:44:00 +02:00
Cristi Vîjdea a46b684fea Add 1.3.0 changelog 2018-01-23 12:44:16 +02:00
Cristi Vîjdea fc35d9043e Add security requirements handling (#54)
* Add security requirements handling
* Update swagger-ui to 3.9.2, ReDoc to 1.20.0

Closes #39.
2018-01-23 12:43:25 +02:00
Cristi Vîjdea f18ff60ae7 Update swagger-ui to 3.9.1 2018-01-19 18:37:44 +01:00
Daniel Hahler a82730d32b Fix logging message in get_path_from_regex (#49)
Adds the missing param.
2018-01-17 21:49:22 +01:00
Cristi Vîjdea e30af0287f Fix README and CONTRIBUTING 2018-01-12 15:18:18 +01:00
52 changed files with 1152 additions and 467 deletions
+3 -3
View File
@@ -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>
+4 -3
View File
@@ -103,16 +103,17 @@ Maintainer's notes
Release checklist
=================
* update ``docs/changelog.rst`` with changes since the latest tag
* commit & tag the release
* update ``docs/changelog.rst`` with changes since the last tagged version
* commit & tag the release - ``git tag x.x.x -m "Release version x.x.x"``
* push using ``git push --follow-tags``
* verify that `Travis`_ has built the tag and succesfully published the release to PyPI
* verify that `Travis`_ has built the tag and succesfully published the release to `PyPI`_
* publish release notes `on GitHub`_
* start the `ReadTheDocs build`_ if it has not already started
* deploy the live demo `on Heroku`_
.. _Travis: https://travis-ci.org/axnsan12/drf-yasg/builds
.. _PyPI: https://pypi.org/project/drf-yasg/
.. _on GitHub: https://github.com/axnsan12/drf-yasg/releases
.. _ReadTheDocs build: https://readthedocs.org/projects/drf-yasg/builds/
.. _on Heroku: https://dashboard.heroku.com/pipelines/412d1cae-6a95-4f5e-810b-94869133f36a
+1 -1
View File
@@ -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:
+4 -69
View File
@@ -42,10 +42,8 @@ Features
- generated Swagger schema can be automatically validated by
`swagger-spec-validator <https://github.com/Yelp/swagger_spec_validator>`_ or
`flex <https://github.com/pipermerriam/flex>`_
- supports Django REST Framework API versioning
+ ``URLPathVersioning`` and ``NamespaceVersioning`` are supported
+ ``HostnameVersioning``, ``AcceptHeaderVersioning`` and ``QueryParameterVersioning`` are not currently supported
- supports Django REST Framework API versioning with ``URLPathVersioning`` and ``NamespaceVersioning``; other DRF
or custom versioning schemes are not currently supported
.. figure:: https://raw.githubusercontent.com/axnsan12/drf-yasg/1.0.2/screenshots/redoc-nested-response.png
:width: 100%
@@ -187,71 +185,8 @@ c. ``SWAGGER_SETTINGS`` and ``REDOC_SETTINGS``
----------------------------------------------
Additionally, you can include some more settings in your ``settings.py`` file.
The possible settings and their default values are as follows:
See https://drf-yasg.readthedocs.io/en/stable/settings.html for details.
.. code:: python
SWAGGER_SETTINGS = {
# default inspector classes, see advanced documentation
'DEFAULT_AUTO_SCHEMA_CLASS': 'drf_yasg.inspectors.SwaggerAutoSchema',
'DEFAULT_FIELD_INSPECTORS': [
'drf_yasg.inspectors.CamelCaseJSONFilter',
'drf_yasg.inspectors.ReferencingSerializerInspector',
'drf_yasg.inspectors.RelatedFieldInspector',
'drf_yasg.inspectors.ChoiceFieldInspector',
'drf_yasg.inspectors.FileFieldInspector',
'drf_yasg.inspectors.DictFieldInspector',
'drf_yasg.inspectors.SimpleFieldInspector',
'drf_yasg.inspectors.StringDefaultFieldInspector',
],
'DEFAULT_FILTER_INSPECTORS': [
'drf_yasg.inspectors.CoreAPICompatInspector',
],
'DEFAULT_PAGINATOR_INSPECTORS': [
'drf_yasg.inspectors.DjangoRestResponsePagination',
'drf_yasg.inspectors.CoreAPICompatInspector',
],
# default api Info if none is otherwise given; should be an import string to an openapi.Info object
'DEFAULT_INFO': None,
# default API url if none is otherwise given
'DEFAULT_API_URL': None,
'USE_SESSION_AUTH': True, # add Django Login and Django Logout buttons, CSRF token to swagger UI page
'LOGIN_URL': getattr(django.conf.settings, 'LOGIN_URL', None), # URL for the login button
'LOGOUT_URL': getattr(django.conf.settings, 'LOGOUT_URL', None), # URL for the logout button
# Swagger security definitions to include in the schema;
# see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-definitions-object
'SECURITY_DEFINITIONS': {
'basic': {
'type': 'basic'
}
},
# url to an external Swagger validation service; defaults to 'http://online.swagger.io/validator/'
# set to None to disable the schema validation badge in the UI
'VALIDATOR_URL': '',
# swagger-ui configuration settings, see https://github.com/swagger-api/swagger-ui/blob/112bca906553a937ac67adc2e500bdeed96d067b/docs/usage/configuration.md#parameters
'OPERATIONS_SORTER': None,
'TAGS_SORTER': None,
'DOC_EXPANSION': 'list',
'DEEP_LINKING': False,
'SHOW_EXTENSIONS': True,
'DEFAULT_MODEL_RENDERING': 'model',
'DEFAULT_MODEL_DEPTH': 3,
}
.. code:: python
REDOC_SETTINGS = {
# ReDoc UI configuration settings, see https://github.com/Rebilly/ReDoc#redoc-tag-attributes
'LAZY_RENDERING': True,
'HIDE_HOSTNAME': False,
'EXPAND_RESPONSES': 'all',
'PATH_IN_MIDDLE': False,
}
3. Caching
==========
@@ -427,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
+101
View File
@@ -2,10 +2,89 @@
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
``DEFAULT_PARSER_CLASSES`` and ``DEFAULT_RENDERER_CLASSES`` (:issue:`48`)
*********
**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
- **IMPROVED:** updated ``ReDoc`` to version 1.20.0
- **FIXED:** fixed an exception caused by a warning in get_path_from_regex (:pr:`49`, thanks to :ghuser:`blueyed`)
*********
**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`)
@@ -13,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
@@ -30,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
@@ -38,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
@@ -45,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 ``$``
@@ -53,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>`
@@ -68,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
@@ -79,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>`_
@@ -90,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`)
@@ -98,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
@@ -106,4 +205,6 @@ Changelog
**1.0.2**
*********
*Release date: Dec 13, 2017*
- First published version
+24 -24
View File
@@ -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
+44 -150
View File
@@ -9,157 +9,30 @@ Custom schema generation
If the default spec generation does not quite match what you were hoping to achieve, ``drf-yasg`` provides some
custom behavior hooks by default.
*********************
Swagger spec overview
*********************
.. _custom-spec-excluding-endpoints:
This library generates OpenAPI 2.0 documents. The authoritative specification for this document's structure will always
be the official documentation over at `swagger.io <https://swagger.io/>`__ and the `OpenAPI 2.0 specification
page <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__.
*******************
Excluding endpoints
*******************
Beause the above specifications are a bit heavy and convoluted, here is a general overview of how the specification
is structured, starting from the root ``Swagger`` object.
You can prevent a view from being included in the Swagger view by setting its class-level ``swagger_schema``
attribute to ``None``, or you can prevent an operation from being included by setting its ``auto_schema`` override
to none in :ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`:
* :class:`.Swagger` object
+ ``info``, ``schemes``, ``securityDefinitions`` and other informative attributes
+ ``paths``: :class:`.Paths` object
A list of all the paths in the API in the form of a mapping
.. code-block:: python
- ``{path}``: :class:`.PathItem` - each :class:`.PathItem` has multiple operations keyed by method
* ``{http_method}``: :class:`.Operation`
Each operation is thus uniquely identified by its ``(path, http_method)`` combination,
e.g. ``GET /articles/``, ``POST /articles/``, etc.
* ``parameters``: [:class:`.Parameter`] - and a list of path parameters
+ ``definitions``: named Models
A list of all the named models in the API in the form of a mapping
class UserList(APIView):
swagger_schema = None
- ``{ModelName}``: :class:`.Schema`
# all methods of the UserList class will be excluded
...
* :class:`.Operation` contains the following information about each operation:
+ ``parameters``: [:class:`.Parameter`]
A list of all the *query*, *header* and *form* parameters accepted by the operation.
- there can also be **at most one** body parameter whose structure is represented by a
:class:`.Schema` or a reference to one (:class:`.SchemaRef`)
+ ``responses``: :class:`.Responses`
A list of all the possible responses the operation is expected to return. Each response can optionally have a
:class:`.Schema` which describes the structure of its body.
- ``{status_code}``: :class:`.Response` - mapping of status code to response definition
+ ``operationId`` - should be unique across all operations
+ ``tags`` - used to group operations in the listing
It is interesting to note the main differences between :class:`.Parameter` and :class:`.Schema` objects:
+----------------------------------------------------------+-----------------------------------------------------------+
| :class:`.Schema` | :class:`.Parameter` |
+==========================================================+===========================================================+
| Can nest other Schemas | Cannot nest other Parameters |br| |
| | Can only nest a Schema if the parameter is ``in: body`` |
+----------------------------------------------------------+-----------------------------------------------------------+
| Cannot describe file uploads |br| | Can describe file uploads via ``type`` = ``file``, |br| |
| - ``file`` is not permitted as a value for ``type`` | but only as part of a form :class:`.Operation` [#formop]_ |
+----------------------------------------------------------+-----------------------------------------------------------+
| Can be used in :class:`.Response`\ s | Cannot be used in :class:`.Response`\ s |
+----------------------------------------------------------+-----------------------------------------------------------+
| Cannot be used in form :class:`.Operation`\ s [#formop]_ | Can be used in form :class:`.Operation`\ s [#formop]_ |
+----------------------------------------------------------+-----------------------------------------------------------+
| Can only describe request or response bodies | Can describe ``query``, ``form``, ``header`` or ``path`` |
| | parameters |
+----------------------------------------------------------+-----------------------------------------------------------+
.. [#formop] a form Operation is an :class:`.Operation` that consumes ``multipart/form-data`` or
``application/x-www-form-urlencoded`` content
* a form Operation cannot have ``body`` parameters
* a non-form operation cannot have ``form`` parameters
****************
Default behavior
****************
This section describes where information is sourced from when using the default generation process.
* :class:`.Paths` are generated by exploring the patterns registered in your default ``urlconf``, or the ``patterns``
and ``urlconf`` you specified when constructing :class:`.OpenAPISchemaGenerator`; only views inheriting from Django
Rest Framework's ``APIView`` are looked at, all other views are ignored
* ``path`` :class:`.Parameter`\ s are generated by looking in the URL pattern for any template parameters; attempts are
made to guess their type from the views ``queryset`` and ``lookup_field``, if applicable. You can override path
parameters via ``manual_parameters`` in :ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`.
* ``query`` :class:`.Parameter`\ s - i.e. parameters specified in the URL as ``/path/?query1=value&query2=value`` -
are generated from your view's ``filter_backends`` and ``paginator``, if any are declared. Additional parameters can
be specified via the ``query_serializer`` and ``manual_parameters`` arguments of
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`
* The request body is only generated for the HTTP ``POST``, ``PUT`` and ``PATCH`` methods, and is sourced from the
view's ``serializer_class``. You can also override the request body using the ``request_body`` argument of
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`.
- if the view represents a form request (that is, all its parsers are of the ``multipart/form-data`` or
``application/x-www-form-urlencoded`` media types), the request body will be output as ``form``
:class:`.Parameter`\ s
- if it is not a form request, the request body will be output as a single ``body`` :class:`.Parameter` wrapped
around a :class:`.Schema`
* ``header`` :class:`.Parameter`\ s are supported by the OpenAPI specification but are never generated by this library;
you can still add them using ``manual_parameters``.
* :class:`.Responses` are generated as follows:
+ if ``responses`` is provided to :ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>` and contains at least
one success status code (i.e. any `2xx` status code), no automatic response is generated and the given response
is used as described in the :func:`@swagger_auto_schema documentation <.swagger_auto_schema>`
+ otherwise, an attempt is made to generate a default response:
- the success status code is assumed to be ``204` for ``DELETE`` requests, ``201`` for ``POST`` requests, and
``200`` for all other request methods
- if the view has a request body, the same ``Serializer`` or :class:`.Schema` as in the request body is used
in generating the :class:`.Response` schema; this is inline with the default ``GenericAPIView`` and
``GenericViewSet`` behavior
- if the view has no request body, its ``serializer_class`` is used to generate the :class:`.Response` schema
- if the view is a list view (as defined by :func:`.is_list_view`), the response schema is wrapped in an array
- if the view is also paginated, the response schema is then wrapped in the appropriate paging response structure
- the description of the response is left blank
* :class:`.Response` headers are supported by the OpenAPI specification but not currently supported by this library;
you can still add them manually by providing an `appropriately structured dictionary
<https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#headersObject>`_
to the ``headers`` property of a :class:`.Response` object
* *descriptions* for :class:`.Operation`\ s, :class:`.Parameter`\ s and :class:`.Schema`\ s are picked up from
docstrings and ``help_text`` attributes in the same manner as the `default DRF SchemaGenerator
<http://www.django-rest-framework.org/api-guide/schemas/#schemas-as-documentation>`_
* .. _custom-spec-base-url:
The base URL for the API consists of three values - the ``host``, ``schemes`` and ``basePath`` attributes
* The host name and scheme are determined, in descending order of priority:
+ from the ``url`` argument passed to :func:`.get_schema_view` (more specifically, to the underlying
:class:`.OpenAPISchemaGenerator`)
+ from the :ref:`DEFAULT_API_URL setting <default-swagger-settings>`
+ inferred from the request made to the schema endpoint
For example, an url of ``https://www.example.com:8080/some/path`` will populate the ``host`` and ``schemes``
attributes with ``www.example.com:8080`` and ``['https']``, respectively. The path component will be ignored.
* The base path is determined as the concatenation of two variables:
#. the `SCRIPT_NAME`_ wsgi environment variable; this is set, for example, when serving the site from a
sub-path using web server url rewriting
.. Tip::
The Django `FORCE_SCRIPT_NAME`_ setting can be used to override the `SCRIPT_NAME`_ or set it when it's
missing from the environment.
#. the longest common path prefix of all the urls in your API - see :meth:`.determine_path_prefix`
* When using API versioning with ``NamespaceVersioning`` or ``URLPathVersioning``, versioned endpoints that do not
match the version used to access the ``SchemaView`` will be excluded from the endpoint list - for example,
``/api/v1.0/endpoint`` will be shown when viewing ``/api/v1.0/swagger/``, while ``/api/v2.0/endpoint`` will not
Other versioning schemes are not presently supported.
.. versionadded:: 1.2
Base path and versioning support.
# only the GET method will be shown in Swagger
@swagger_auto_schema(method='put', auto_schema=None)
@swagger_auto_schema(methods=['get'], ...)
@api_view(['GET', 'PUT'])
def user_detail(request, pk):
pass
.. _custom-spec-swagger-auto-schema:
@@ -299,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
@@ -352,8 +229,6 @@ This custom generator can be put to use by setting it as the :attr:`.generator_c
``Inspector`` classes
---------------------
.. versionadded:: 1.1
For customizing behavior related to specific field, serializer, filter or paginator classes you can implement the
:class:`~.inspectors.FieldInspector`, :class:`~.inspectors.SerializerInspector`, :class:`~.inspectors.FilterInspector`,
:class:`~.inspectors.PaginatorInspector` classes and use them with
@@ -431,5 +306,24 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
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.
.. _SCRIPT_NAME: https://www.python.org/dev/peps/pep-0333/#environ-variables
.. _FORCE_SCRIPT_NAME: https://docs.djangoproject.com/en/2.0/ref/settings/#force-script-name
**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.
+2
View File
@@ -12,6 +12,8 @@ drf-yasg
readme.rst
rendering.rst
openapi.rst
security.rst
custom_spec.rst
custom_ui.rst
settings.rst
+179
View File
@@ -0,0 +1,179 @@
.. |br| raw:: html
<br />
**********************
Functional overview
**********************
------------------------------
OpenAPI specification overview
------------------------------
This library generates OpenAPI 2.0 documents. The authoritative specification for this document's structure will always
be the official documentation over at `swagger.io <https://swagger.io/>`__ and the `OpenAPI 2.0 specification
page <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__.
Beause the above specifications are a bit heavy and convoluted, here is a general overview of how the specification
is structured, starting from the root ``Swagger`` object.
* :class:`.Swagger` object
+ ``info``, ``schemes``, ``securityDefinitions`` and other informative attributes
+ ``paths``: :class:`.Paths` object
A list of all the paths in the API in the form of a mapping
- ``{path}``: :class:`.PathItem` - each :class:`.PathItem` has multiple operations keyed by method
* ``{http_method}``: :class:`.Operation`
Each operation is thus uniquely identified by its ``(path, http_method)`` combination,
e.g. ``GET /articles/``, ``POST /articles/``, etc.
* ``parameters``: [:class:`.Parameter`] - and a list of path parameters
+ ``definitions``: named Models
A list of all the named models in the API in the form of a mapping
- ``{ModelName}``: :class:`.Schema`
* :class:`.Operation` contains the following information about each operation:
+ ``parameters``: [:class:`.Parameter`]
A list of all the *query*, *header* and *form* parameters accepted by the operation.
- there can also be **at most one** body parameter whose structure is represented by a
:class:`.Schema` or a reference to one (:class:`.SchemaRef`)
+ ``responses``: :class:`.Responses`
A list of all the possible responses the operation is expected to return. Each response can optionally have a
:class:`.Schema` which describes the structure of its body.
- ``{status_code}``: :class:`.Response` - mapping of status code to response definition
+ ``operationId`` - should be unique across all operations
+ ``tags`` - used to group operations in the listing
It is interesting to note the main differences between :class:`.Parameter` and :class:`.Schema` objects:
+----------------------------------------------------------+-----------------------------------------------------------+
| :class:`.Schema` | :class:`.Parameter` |
+==========================================================+===========================================================+
| Can nest other Schemas | Cannot nest other Parameters |br| |
| | Can only nest a Schema if the parameter is ``in: body`` |
+----------------------------------------------------------+-----------------------------------------------------------+
| Cannot describe file uploads |br| | Can describe file uploads via ``type`` = ``file``, |br| |
| - ``file`` is not permitted as a value for ``type`` | but only as part of a form :class:`.Operation` [#formop]_ |
+----------------------------------------------------------+-----------------------------------------------------------+
| Can be used in :class:`.Response`\ s | Cannot be used in :class:`.Response`\ s |
+----------------------------------------------------------+-----------------------------------------------------------+
| Cannot be used in form :class:`.Operation`\ s [#formop]_ | Can be used in form :class:`.Operation`\ s [#formop]_ |
+----------------------------------------------------------+-----------------------------------------------------------+
| Can only describe request or response bodies | Can describe ``query``, ``form``, ``header`` or ``path`` |
| | parameters |
+----------------------------------------------------------+-----------------------------------------------------------+
.. [#formop] a form Operation is an :class:`.Operation` that consumes ``multipart/form-data`` or
``application/x-www-form-urlencoded`` content
* a form Operation cannot have ``body`` parameters
* a non-form operation cannot have ``form`` parameters
----------------
Default behavior
----------------
This section describes where information is sourced from when using the default generation process.
* :class:`.Paths` are generated by exploring the patterns registered in your default ``urlconf``, or the ``patterns``
and ``urlconf`` you specified when constructing :class:`.OpenAPISchemaGenerator`; only views inheriting from Django
Rest Framework's ``APIView`` are looked at, all other views are ignored
* ``path`` :class:`.Parameter`\ s are generated by looking in the URL pattern for any template parameters; attempts are
made to guess their type from the views ``queryset`` and ``lookup_field``, if applicable. You can override path
parameters via ``manual_parameters`` in :ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`.
* ``query`` :class:`.Parameter`\ s - i.e. parameters specified in the URL as ``/path/?query1=value&query2=value`` -
are generated from your view's ``filter_backends`` and ``paginator``, if any are declared. Additional parameters can
be specified via the ``query_serializer`` and ``manual_parameters`` arguments of
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`
* The request body is only generated for the HTTP ``POST``, ``PUT`` and ``PATCH`` methods, and is sourced from the
view's ``serializer_class``. You can also override the request body using the ``request_body`` argument of
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`.
- if the view represents a form request (that is, all its parsers are of the ``multipart/form-data`` or
``application/x-www-form-urlencoded`` media types), the request body will be output as ``form``
:class:`.Parameter`\ s
- if it is not a form request, the request body will be output as a single ``body`` :class:`.Parameter` wrapped
around a :class:`.Schema`
* ``header`` :class:`.Parameter`\ s are supported by the OpenAPI specification but are never generated by this library;
you can still add them using ``manual_parameters``.
* :class:`.Responses` are generated as follows:
+ if ``responses`` is provided to :ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>` and contains at least
one success status code (i.e. any `2xx` status code), no automatic response is generated and the given response
is used as described in the :func:`@swagger_auto_schema documentation <.swagger_auto_schema>`
+ otherwise, an attempt is made to generate a default response:
- the success status code is assumed to be ``204` for ``DELETE`` requests, ``201`` for ``POST`` requests, and
``200`` for all other request methods
- if the view has a request body, the same ``Serializer`` or :class:`.Schema` as in the request body is used
in generating the :class:`.Response` schema; this is inline with the default ``GenericAPIView`` and
``GenericViewSet`` behavior
- if the view has no request body, its ``serializer_class`` is used to generate the :class:`.Response` schema
- if the view is a list view (as defined by :func:`.is_list_view`), the response schema is wrapped in an array
- if the view is also paginated, the response schema is then wrapped in the appropriate paging response structure
- the description of the response is left blank
* :class:`.Response` headers are supported by the OpenAPI specification but not currently supported by this library;
you can still add them manually by providing an `appropriately structured dictionary
<https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#headersObject>`_
to the ``headers`` property of a :class:`.Response` object
* *descriptions* for :class:`.Operation`\ s, :class:`.Parameter`\ s and :class:`.Schema`\ s are picked up from
docstrings and ``help_text`` attributes in the same manner as the `default DRF SchemaGenerator
<http://www.django-rest-framework.org/api-guide/schemas/#schemas-as-documentation>`_
* .. _custom-spec-base-url:
The base URL for the API consists of three values - the ``host``, ``schemes`` and ``basePath`` attributes
* The host name and scheme are determined, in descending order of priority:
+ from the ``url`` argument passed to :func:`.get_schema_view` (more specifically, to the underlying
:class:`.OpenAPISchemaGenerator`)
+ from the :ref:`DEFAULT_API_URL setting <default-swagger-settings>`
+ inferred from the request made to the schema endpoint
For example, an url of ``https://www.example.com:8080/some/path`` will populate the ``host`` and ``schemes``
attributes with ``www.example.com:8080`` and ``['https']``, respectively. The path component will be ignored.
* The base path is determined as the concatenation of two variables:
#. the `SCRIPT_NAME`_ wsgi environment variable; this is set, for example, when serving the site from a
sub-path using web server url rewriting
.. Tip::
The Django `FORCE_SCRIPT_NAME`_ setting can be used to override the `SCRIPT_NAME`_ or set it when it's
missing from the environment.
#. the longest common path prefix of all the urls in your API - see :meth:`.determine_path_prefix`
* When using API versioning with ``NamespaceVersioning`` or ``URLPathVersioning``, versioned endpoints that do not
match the version used to access the ``SchemaView`` will be excluded from the endpoint list - for example,
``/api/v1.0/endpoint`` will be shown when viewing ``/api/v1.0/swagger/``, while ``/api/v2.0/endpoint`` will not
Other versioning schemes are not presently supported.
---------------------
A note on limitations
---------------------
When schema generation is requested, available endpoints are inspected by enumeration all the routes registered in
Django's urlconf. Each registered view is then artificially instantiated for introspection, and it is this step that
brings some limitations to what can be done:
* the ``request`` the view sees will always be the request made against the schema view endpoint
- e.g. ``GET /swagger.yaml``
* path parameters will not be filled
This means that you could get surprizing results if your ``get_serializer`` or ``get_serializer_class`` methods
depend on the incoming request, call ``get_object`` or in general depend on any stateful logic. You can prevent this
in a few ways:
* provide a fixed serializer for request and response body introspection using
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`, to prevent ``get_serializer`` from being called on
the view
* :ref:`exclude your endpoint from introspection <custom-spec-excluding-endpoints>`
.. _SCRIPT_NAME: https://www.python.org/dev/peps/pep-0333/#environ-variables
.. _FORCE_SCRIPT_NAME: https://docs.djangoproject.com/en/2.0/ref/settings/#force-script-name
-2
View File
@@ -41,8 +41,6 @@ You can use your custom renderer classes as kwargs to :meth:`.SchemaView.as_cach
Management command
******************
.. versionadded:: 1.1.1
If you only need a swagger spec file in YAML or JSON format, you can use the ``generate_swagger`` management command
to get it without having to start the web server:
+65
View File
@@ -0,0 +1,65 @@
*********************************
Describing authentication schemes
*********************************
When using the `swagger-ui` frontend, it is possible to interact with the API described by your Swagger document.
This interaction might require authentication, which you will have to describe in order to make `swagger-ui` work
with it.
--------------------
Security definitions
--------------------
The first step that you have to do is add a :ref:`SECURITY_DEFINITIONS <security-definitions-settings>` setting
to declare all authentication schemes supported by your API.
For example, the definition for a simple API accepting HTTP basic auth and `Authorization` header API tokens would be:
.. code-block:: python
SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': {
'Basic': {
'type': 'basic'
},
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
}
}
---------------------
Security requirements
---------------------
The second step is specifying, for each endpoint, which authentication mechanism can be used for interacting with it.
See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-requirement-object for details.
By default, a top-level `security` that accepts all the declared security definitions is generated.
For the example above, that would be :code:`[{'Basic': []}, {'Bearer': []}]`. This can be overriden using the
:ref:`SECURITY_REQUIREMENTS <security-definitions-settings>` setting.
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.
+30
View File
@@ -140,6 +140,9 @@ URL for the Django Logout action when using `USE_SESSION_AUTH`_.
**Default**: :python:`django.conf.settings.LOGOUT_URL`
.. _security-definitions-settings:
SECURITY_DEFINITIONS
--------------------
@@ -154,6 +157,14 @@ See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#sec
'type': 'basic'
}
SECURITY_REQUIREMENTS
---------------------
Global security requirements. If :python:`None`, all schemes in ``SECURITY_DEFINITIONS`` are accepted. |br|
See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#securityRequirementObject.
**Default**: :python:`None`
.. _swagger-ui-settings:
Swagger UI settings
@@ -242,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``
******************
+67 -64
View File
@@ -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.12.2",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz",
"integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==",
"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.1.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz",
"integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng=="
"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.1.1",
"es6-promise": "4.2.4",
"js-yaml": "3.10.0",
"ono": "4.0.2",
"z-schema": "3.19.0"
"ono": "4.0.3",
"z-schema": "3.19.1"
}
},
"lodash.get": {
@@ -166,23 +166,31 @@
"resolved": "https://registry.npmjs.org/lunr/-/lunr-1.0.0.tgz",
"integrity": "sha1-XJJ2ySyRrDWpJBtQGNRnI9kuL18="
},
"mark.js": {
"version": "8.11.1",
"resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz",
"integrity": "sha1-GA8fnr74sOY45BZq1S24eb6y/8U="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"ono": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.2.tgz",
"integrity": "sha512-EFXJFoeF+KkZW4lwmcPMKHp2ZU7o6CM+ccX2nPbEJKiJIdyqbIcS1v6pmNgeNJ6x4/vEYn0/8oz66qXSPnnmSQ==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.3.tgz",
"integrity": "sha512-7QIxG4UB00H7CR7fhXC/U7VhB5DK9wsYLwaYBui1JmQoXtLkhIBn3fbuk6FgAP+ctWeBsWVTM+R/bThvUZN+ww==",
"requires": {
"format-util": "1.0.3"
}
},
"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",
@@ -190,36 +198,36 @@
"integrity": "sha512-RNC5tX/JMRYR+qVdJTEAWnRxw0Yf9lvbO8lTuAOvgDODkiA8lveTSkvrNMhmaGKEyimJpJl+myb/syVS9YyPuw=="
},
"prismjs": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.9.0.tgz",
"integrity": "sha1-+j4tntw8OIfB8fMJXUHx+bQgDw8=",
"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.19.3",
"resolved": "https://registry.npmjs.org/redoc/-/redoc-1.19.3.tgz",
"integrity": "sha1-DfPx+6S92G/+nGIAEzBxUjytVec=",
"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",
@@ -228,21 +236,16 @@
"json-pointer": "0.6.0",
"json-schema-ref-parser": "3.3.1",
"lunr": "1.0.0",
"mark.js": "github:julmot/mark.js#714c9523feca999267f1758da8cfd92d077105d0",
"openapi-sampler": "0.4.3",
"mark.js": "8.11.1",
"openapi-sampler": "1.0.0-beta.8",
"perfect-scrollbar": "0.8.1",
"prismjs": "1.9.0",
"prismjs": "1.11.0",
"remarkable": "1.7.1",
"scrollparent": "2.0.1",
"slugify": "1.2.6",
"stream-http": "2.7.2",
"slugify": "1.2.9",
"stream-http": "2.8.0",
"ts-helpers": "1.1.2",
"zone.js": "0.8.18"
},
"dependencies": {
"mark.js": {
"version": "github:julmot/mark.js#714c9523feca999267f1758da8cfd92d077105d0"
}
"zone.js": "0.8.20"
}
},
"remarkable": {
@@ -282,9 +285,9 @@
"optional": true
},
"slugify": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.2.6.tgz",
"integrity": "sha512-796YAGnzEnLKQHAFf7H2q1nsjY/9qywSnF9ZkMUbs9he4aZaXO/zFUow0LZ95sBAiQjOX1EmGl23gTYaswiNaQ=="
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.2.9.tgz",
"integrity": "sha512-n0cdJ+kN3slJu8SbZXt/EHjljBqF6MxvMGSg/NPpBzoY7yyXoH38wp/ox20a1JaG1KgmdTN5Lf3aS9+xB2Y2aQ=="
},
"sprintf-js": {
"version": "1.0.3",
@@ -292,13 +295,13 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"stream-http": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz",
"integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.0.tgz",
"integrity": "sha512-sZOFxI/5xw058XIRHl4dU3dZ+TTOIGJR78Dvo0oEAejIt4ou27k+3ne1zYmCV+v7UucbxIFQuOgnkTVHh8YPnw==",
"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.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.9.0.tgz",
"integrity": "sha1-eWXZZ6ds74tFWo0M2ROA/2Ss2RM="
"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,20 +361,20 @@
"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.12.2",
"commander": "2.14.1",
"lodash.get": "4.4.2",
"lodash.isequal": "4.5.0",
"validator": "9.2.0"
"validator": "9.4.1"
}
},
"zone.js": {
"version": "0.8.18",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.18.tgz",
"integrity": "sha512-knKOBQM0oea3/x9pdyDuDi7RhxDlJhOIkeixXSiTKWLgs4LpK37iBc+1HaHwzlciHUKT172CymJFKo8Xgh+44Q=="
"version": "0.8.20",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.20.tgz",
"integrity": "sha512-FXlA37ErSXCMy5RNBcGFgCI/Zivqzr0D19GuvDxhcYIJc7xkFp6c29DKyODJu0Zo+EMyur/WPPgcBh1EHjB9jA=="
}
}
}
+8 -3
View File
@@ -1,7 +1,12 @@
{
"name": "drf-yasg",
"dependencies": {
"redoc": "^1.19.3",
"swagger-ui-dist": "^3.9.0"
}
"redoc": "^1.21.0",
"swagger-ui-dist": "^3.10.0"
},
"repository": {
"type": "git",
"url": "https://github.com/axnsan12/drf-yasg.git"
},
"private": true
}
+1 -1
View File
@@ -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 -1
View File
@@ -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
+3
View File
@@ -31,6 +31,7 @@ SWAGGER_DEFAULTS = {
'type': 'basic'
}
},
'SECURITY_REQUIREMENTS': None,
'LOGIN_URL': getattr(settings, 'LOGIN_URL', None),
'LOGOUT_URL': getattr(settings, 'LOGOUT_URL', None),
'VALIDATOR_URL': '',
@@ -42,6 +43,8 @@ SWAGGER_DEFAULTS = {
'SHOW_EXTENSIONS': True,
'DEFAULT_MODEL_RENDERING': 'model',
'DEFAULT_MODEL_DEPTH': 3,
'OAUTH2_REDIRECT_URL': None,
'OAUTH2_CONFIG': {},
}
REDOC_DEFAULTS = {
-2
View File
@@ -8,7 +8,6 @@ from coreapi.compat import force_bytes
from ruamel import yaml
from . import openapi
from .app_settings import swagger_settings
from .errors import SwaggerValidationError
@@ -94,7 +93,6 @@ class _OpenAPICodec(object):
:return: swagger spec as dict
:rtype: OrderedDict
"""
swagger.security_definitions = swagger_settings.SECURITY_DEFINITIONS
return swagger.as_odict()
+31 -8
View File
@@ -10,13 +10,14 @@ from rest_framework.compat import URLPattern, URLResolver, get_original_route
from rest_framework.schemas.generators import EndpointEnumerator as _EndpointEnumerator
from rest_framework.schemas.generators import SchemaGenerator, endpoint_ordering
from rest_framework.schemas.inspectors import get_pk_description
from drf_yasg.errors import SwaggerGenerationError
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 .openapi import ReferenceResolver
from .utils import get_consumes, get_produces
logger = logging.getLogger(__name__)
@@ -30,7 +31,8 @@ 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")
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):
@@ -43,6 +45,9 @@ class EndpointEnumerator(_EndpointEnumerator):
if version and version not in namespace.split(':'):
return False
if getattr(callback.cls, 'swagger_schema', object()) is None:
return False
return True
def replace_version(self, path, callback):
@@ -164,6 +169,9 @@ class OpenAPISchemaGenerator(object):
self._gen = SchemaGenerator(info.title, url, info.get('description', ''), patterns, urlconf)
self.info = info
self.version = version
self.consumes = []
self.produces = []
if url is None and swagger_settings.DEFAULT_API_URL is not None:
url = swagger_settings.DEFAULT_API_URL
@@ -190,14 +198,22 @@ class OpenAPISchemaGenerator(object):
"""
endpoints = self.get_endpoints(request)
components = ReferenceResolver(openapi.SCHEMA_DEFINITIONS)
self.consumes = get_consumes(rest_framework_settings.DEFAULT_PARSER_CLASSES)
self.produces = get_produces(rest_framework_settings.DEFAULT_RENDERER_CLASSES)
paths, prefix = self.get_paths(endpoints, components, request, public)
security_definitions = swagger_settings.SECURITY_DEFINITIONS
security_requirements = swagger_settings.SECURITY_REQUIREMENTS
if security_requirements is None:
security_requirements = [{security_scheme: [] for security_scheme in swagger_settings.SECURITY_DEFINITIONS}]
url = self.url
if url is None and request is not None:
url = request.build_absolute_uri()
return openapi.Swagger(
info=self.info, paths=paths,
info=self.info, paths=paths, consumes=self.consumes or None, produces=self.produces or None,
security_definitions=security_definitions, security=security_requirements,
_url=url, _prefix=prefix, _version=self.version, **dict(components)
)
@@ -323,7 +339,6 @@ class OpenAPISchemaGenerator(object):
:param Request request: the request made against the schema view; can be None
:rtype: openapi.Operation
"""
operation_keys = self.get_operation_keys(path[len(prefix):], method, view)
overrides = self.get_overrides(view, method)
@@ -335,8 +350,16 @@ class OpenAPISchemaGenerator(object):
# 3. on the swagger_auto_schema decorator
view_inspector_cls = overrides.get('auto_schema', view_inspector_cls)
if view_inspector_cls is None:
return None
view_inspector = view_inspector_cls(view, path, method, components, request, overrides)
return view_inspector.get_operation(operation_keys)
operation = view_inspector.get_operation(operation_keys)
if set(operation.consumes) == set(self.consumes):
del operation.consumes
if set(operation.produces) == set(self.produces):
del operation.produces
return operation
def get_path_item(self, path, view_cls, operations):
"""Get a :class:`.PathItem` object that describes the parameters and operations related to a single path in the
@@ -382,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)
+5 -2
View File
@@ -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)
+61 -18
View File
@@ -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
+30 -8
View File
@@ -6,7 +6,10 @@ from rest_framework.status import is_success
from .. import openapi
from ..errors import SwaggerGenerationError
from ..utils import force_serializer_instance, guess_response_status, is_list_view, no_body, param_list_to_odict
from ..utils import (
force_serializer_instance, get_consumes, get_produces, guess_response_status, is_list_view, no_body,
param_list_to_odict
)
from .base import ViewInspector
@@ -18,6 +21,7 @@ class SwaggerAutoSchema(ViewInspector):
def get_operation(self, operation_keys):
consumes = self.get_consumes()
produces = self.get_produces()
body = self.get_request_body_parameters(consumes)
query = self.get_query_parameters()
@@ -27,6 +31,8 @@ class SwaggerAutoSchema(ViewInspector):
operation_id = self.get_operation_id(operation_keys)
description = self.get_description()
security = self.get_security()
assert security is None or isinstance(security, list), "security must be a list of securiy requirement objects"
tags = self.get_tags(operation_keys)
responses = self.get_responses()
@@ -37,7 +43,9 @@ class SwaggerAutoSchema(ViewInspector):
responses=responses,
parameters=parameters,
consumes=consumes,
produces=produces,
tags=tags,
security=security
)
def get_request_body_parameters(self, consumes):
@@ -88,8 +96,8 @@ class SwaggerAutoSchema(ViewInspector):
if body_override is no_body:
return None
if self.method not in self.body_methods:
raise SwaggerGenerationError("request_body can only be applied to PUT, PATCH or POST views; "
"are you looking for query_serializer or manual_parameters?")
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)
@@ -213,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):
@@ -286,6 +294,16 @@ class SwaggerAutoSchema(ViewInspector):
description = self._sch.get_description(self.path, self.method)
return description
def get_security(self):
"""Return a list of security requirements for this operation.
Returning an empty list marks the endpoint as unauthenticated (i.e. removes all accepted
authentication schemes). Returning ``None`` will inherit the top-level secuirty requirements.
:return: security requirements
:rtype: list[dict[str,list[str]]]"""
return self.overrides.get('security', 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.
@@ -301,7 +319,11 @@ class SwaggerAutoSchema(ViewInspector):
:rtype: list[str]
"""
media_types = [parser.media_type for parser in getattr(self.view, 'parser_classes', [])]
if all(is_form_media_type(encoding) for encoding in media_types):
return media_types
return media_types[:1]
return get_consumes(getattr(self.view, 'parser_classes', []))
def get_produces(self):
"""Return the MIME types this endpoint can produce.
:rtype: list[str]
"""
return get_produces(getattr(self.view, 'renderer_classes', []))
+16 -3
View File
@@ -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' #:
@@ -211,7 +212,8 @@ class Info(SwaggerDict):
class Swagger(SwaggerDict):
def __init__(self, info=None, _url=None, _prefix=None, _version=None, paths=None, definitions=None, **extra):
def __init__(self, info=None, _url=None, _prefix=None, _version=None, consumes=None, produces=None,
security_definitions=None, security=None, paths=None, definitions=None, **extra):
"""Root Swagger object.
:param .Info info: info object
@@ -219,6 +221,10 @@ class Swagger(SwaggerDict):
:param str _prefix: api path prefix to use in setting basePath; this will be appended to the wsgi
SCRIPT_NAME prefix or Django's FORCE_SCRIPT_NAME if applicable
:param str _version: version string to override Info
:param list[dict] security_definitions: list of supported authentication mechanisms
:param list[dict] security: authentication mechanisms accepted by default; can be overriden in Operation
:param list[str] consumes: consumed MIME types; can be overriden in Operation
:param list[str] produces: produced MIME types; can be overriden in Operation
:param .Paths paths: paths object
:param dict[str,.Schema] definitions: named models
"""
@@ -234,6 +240,10 @@ class Swagger(SwaggerDict):
self.schemes = [url.scheme]
self.base_path = self.get_base_path(get_script_prefix(), _prefix)
self.consumes = consumes
self.produces = produces
self.security_definitions = filter_none(security_definitions)
self.security = filter_none(security)
self.paths = paths
self.definitions = filter_none(definitions)
self._insert_extras__()
@@ -304,8 +314,8 @@ class PathItem(SwaggerDict):
class Operation(SwaggerDict):
def __init__(self, operation_id, responses, parameters=None, consumes=None,
produces=None, summary=None, description=None, tags=None, **extra):
def __init__(self, operation_id, responses, parameters=None, consumes=None, produces=None, summary=None,
description=None, tags=None, security=None, **extra):
"""Information about an API operation (path + http method combination)
:param str operation_id: operation ID, should be unique across all operations
@@ -316,6 +326,7 @@ class Operation(SwaggerDict):
:param str summary: operation summary; should be < 120 characters
:param str description: operation description; can be of any length and supports markdown
:param list[str] tags: operation tags
:param list[dict[str,list[str]]] security: list of security requirements
"""
super(Operation, self).__init__(**extra)
self.operation_id = operation_id
@@ -326,6 +337,7 @@ class Operation(SwaggerDict):
self.consumes = filter_none(consumes)
self.produces = filter_none(produces)
self.tags = filter_none(tags)
self.security = filter_none(security)
self._insert_extras__()
@@ -384,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
+15 -1
View File
@@ -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.
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
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>
+56 -11
View File
@@ -2,8 +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__)
@@ -11,10 +14,12 @@ logger = logging.getLogger(__name__)
#: used to forcibly remove the body of a request via :func:`.swagger_auto_schema`
no_body = object()
unset = object()
def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_body=None, query_serializer=None,
manual_parameters=None, operation_id=None, operation_description=None, responses=None,
field_inspectors=None, filter_inspectors=None, paginator_inspectors=None,
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):
"""Decorate a view method to customize the :class:`.Operation` object generated from it.
@@ -23,17 +28,11 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
The `auto_schema` and `operation_description` arguments take precendence over view- or method-level values.
.. versionchanged:: 1.1
Added the ``extra_overrides`` and ``operatiod_id`` parameters.
.. versionchanged:: 1.1
Added the ``field_inspectors``, ``filter_inspectors`` and ``paginator_inspectors`` parameters.
:param str method: for multi-method views, the http method the options should apply to
:param list[str] methods: for multi-method views, the http methods the options should apply to
:param .inspectors.SwaggerAutoSchema auto_schema: custom class to use for generating the Operation object;
this overrides both the class-level ``swagger_schema`` attribute and the ``DEFAULT_AUTO_SCHEMA_CLASS``
setting
setting, and can be set to ``None`` to prevent this operation from being generated
:param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :data:`.no_body`. The value given here
will be used as the ``schema`` property of a :class:`.Parameter` with ``in: 'body'``.
@@ -62,6 +61,9 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
: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 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 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
@@ -88,18 +90,20 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
def decorator(view_method):
assert not any(hm in extra_overrides for hm in APIView.http_method_names), "HTTP method names not allowed here"
data = {
'auto_schema': auto_schema,
'request_body': request_body,
'query_serializer': query_serializer,
'manual_parameters': manual_parameters,
'operation_id': operation_id,
'operation_description': operation_description,
'security': security,
'responses': responses,
'filter_inspectors': list(filter_inspectors) if filter_inspectors else None,
'paginator_inspectors': list(paginator_inspectors) if paginator_inspectors else None,
'field_inspectors': list(field_inspectors) if field_inspectors else None,
}
data = filter_none(data)
if auto_schema is not unset:
data['auto_schema'] = auto_schema
data.update(extra_overrides)
if not data: # pragma: no cover
# no overrides to set, no use in doing more work
@@ -116,6 +120,8 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
_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"
@@ -244,3 +250,42 @@ def force_serializer_instance(serializer):
assert isinstance(serializer, serializers.BaseSerializer), \
"Serializer class or instance required, not %s" % type(serializer).__name__
return serializer
def get_consumes(parser_classes):
"""Extract ``consumes`` MIME types from a list of parser classes.
:param list parser_classes: parser classes
:return: MIME types for ``consumes``
:rtype: list[str]
"""
media_types = [parser.media_type for parser in parser_classes or []]
if all(is_form_media_type(encoding) for encoding in media_types):
return media_types
else:
media_types = [encoding for encoding in media_types if not is_form_media_type(encoding)]
return media_types
def get_produces(renderer_classes):
"""Extract ``produces`` MIME types from a list of renderer classes.
:param list renderer_classes: renderer classes
:return: MIME types for ``produces``
: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]
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),
),
]
+4
View File
@@ -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)
+2 -2
View File
@@ -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"
+7 -2
View File
@@ -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):
"""
+8 -3
View File
@@ -1,7 +1,8 @@
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
from drf_yasg.inspectors import SwaggerAutoSchema
@@ -21,7 +22,7 @@ class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
parser_classes = (CamelCaseJSONParser,)
parser_classes = (FormParser, CamelCaseJSONParser,)
renderer_classes = (CamelCaseJSONRenderer,)
swagger_schema = CamelCaseOperationIDAutoSchema
@@ -61,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)
+1
View File
@@ -26,6 +26,7 @@ INSTALLED_APPS = [
'snippets',
'users',
'articles',
'todo',
]
MIDDLEWARE = [
+1
View File
@@ -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),
]
View File
+40
View File
@@ -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')),
],
),
]
+15
View File
@@ -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)
+24
View File
@@ -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
+10
View File
@@ -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
+22
View File
@@ -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
+11 -7
View File
@@ -19,13 +19,17 @@ class UserList(APIView):
serializer = UserSerializerrr(queryset, many=True)
return Response(serializer.data)
@swagger_auto_schema(operation_description="apiview post description override", request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['username'],
properties={
'username': openapi.Schema(type=openapi.TYPE_STRING)
},
))
@swagger_auto_schema(
operation_description="apiview post description override",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['username'],
properties={
'username': openapi.Schema(type=openapi.TYPE_STRING)
},
),
security=[]
)
def post(self, request):
serializer = UserSerializerrr(request.data)
serializer.is_valid(raise_exception=True)
+191 -50
View File
@@ -16,6 +16,15 @@ host: test.local:8002
schemes:
- http
basePath: /
consumes:
- application/json
produces:
- application/json
securityDefinitions:
basic:
type: basic
security:
- basic: []
paths:
/articles/:
get:
@@ -63,8 +72,6 @@ paths:
type: array
items:
$ref: '#/definitions/Article'
consumes:
- application/json
tags:
- articles
post:
@@ -81,8 +88,6 @@ paths:
description: ''
schema:
$ref: '#/definitions/Article'
consumes:
- application/json
tags:
- articles
parameters: []
@@ -108,8 +113,6 @@ paths:
type: array
items:
$ref: '#/definitions/Article'
consumes:
- application/json
tags:
- articles
parameters: []
@@ -123,8 +126,6 @@ paths:
description: ''
schema:
$ref: '#/definitions/Article'
consumes:
- application/json
tags:
- articles
put:
@@ -141,8 +142,6 @@ paths:
description: ''
schema:
$ref: '#/definitions/Article'
consumes:
- application/json
tags:
- articles
patch:
@@ -161,8 +160,6 @@ paths:
$ref: '#/definitions/Article'
'404':
description: slug not found
consumes:
- application/json
tags:
- articles
delete:
@@ -172,8 +169,6 @@ paths:
responses:
'204':
description: ''
consumes:
- application/json
tags:
- articles
parameters:
@@ -203,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
@@ -246,8 +242,6 @@ paths:
responses:
'200':
description: ''
consumes:
- application/json
tags:
- plain
parameters: []
@@ -263,8 +257,6 @@ paths:
type: array
items:
$ref: '#/definitions/Snippet'
consumes:
- application/json
tags:
- snippets
post:
@@ -281,8 +273,6 @@ paths:
description: ''
schema:
$ref: '#/definitions/Snippet'
consumes:
- application/json
tags:
- snippets
parameters: []
@@ -296,8 +286,6 @@ paths:
description: ''
schema:
$ref: '#/definitions/Snippet'
consumes:
- application/json
tags:
- snippets
put:
@@ -314,8 +302,6 @@ paths:
description: ''
schema:
$ref: '#/definitions/Snippet'
consumes:
- application/json
tags:
- snippets
patch:
@@ -332,8 +318,6 @@ paths:
description: ''
schema:
$ref: '#/definitions/Snippet'
consumes:
- application/json
tags:
- snippets
delete:
@@ -347,9 +331,7 @@ paths:
type: integer
responses:
'204':
description: ''
consumes:
- application/json
description: This should not crash
tags:
- snippets
parameters:
@@ -358,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
@@ -380,8 +461,6 @@ paths:
type: array
items:
$ref: '#/definitions/UserSerializerrr'
consumes:
- application/json
tags:
- users
post:
@@ -408,10 +487,9 @@ paths:
properties:
username:
type: string
consumes:
- application/json
tags:
- users
security: []
patch:
operationId: users_dummy
description: dummy operation
@@ -419,8 +497,6 @@ paths:
responses:
'200':
description: ''
consumes:
- application/json
tags:
- users
parameters: []
@@ -438,8 +514,6 @@ paths:
description: response description
schema:
$ref: '#/definitions/UserSerializerrr'
consumes:
- application/json
tags:
- users
put:
@@ -456,8 +530,6 @@ paths:
description: ''
schema:
$ref: '#/definitions/UserSerializerrr'
consumes:
- application/json
tags:
- users
parameters:
@@ -519,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
@@ -566,7 +648,6 @@ definitions:
title: Linenos
type: boolean
language:
title: Language
description: Sample help text for language
type: object
properties:
@@ -1011,7 +1092,6 @@ definitions:
- zephir
default: python
styles:
title: Styles
type: array
items:
type: string
@@ -1048,12 +1128,10 @@ definitions:
default:
- friendly
lines:
title: Lines
type: array
items:
type: integer
exampleProjects:
title: Example projects
type: array
items:
$ref: '#/definitions/Project'
@@ -1064,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
@@ -1088,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
@@ -1112,7 +1257,6 @@ definitions:
format: date
readOnly: true
article_slugs:
title: Article slugs
type: array
items:
type: string
@@ -1120,6 +1264,3 @@ definitions:
pattern: ^[-a-zA-Z0-9_]+$
readOnly: true
uniqueItems: true
securityDefinitions:
basic:
type: basic
+6
View File
@@ -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']
+12
View File
@@ -67,3 +67,15 @@ def test_no_netloc(mock_schema_request):
swagger = generator.get_schema(mock_schema_request, public=True)
assert 'host' not in swagger and 'schemes' not in swagger
assert swagger['info']['version'] == 'v2'
def test_securiy_requirements(swagger_settings, mock_schema_request):
generator = OpenAPISchemaGenerator(
info=openapi.Info(title="Test generator", default_version="v1"),
version="v2",
url='',
)
swagger_settings['SECURITY_REQUIREMENTS'] = []
swagger = generator.get_schema(mock_schema_request, public=True)
assert swagger['security'] == []
+2 -6
View File
@@ -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 =
-2
View File
@@ -1,10 +1,8 @@
#!/bin/bash
set -ev
npm update
npm install -g --prefix ./node_modules/redoc-alpha redoc@latest
cp node_modules/redoc/dist/redoc.min.js src/drf_yasg/static/drf-yasg/redoc/
cp node_modules/redoc-alpha/node_modules/redoc/bundles/redoc.standalone.js src/drf_yasg/static/drf-yasg/redoc-alpha/
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