Compare commits

..

14 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
36 changed files with 619 additions and 122 deletions
+3 -3
View File
@@ -13,9 +13,9 @@
</component> </component>
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <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$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/testproj" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/testproj" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.6 (drf-yasg)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.6 (drf-yasg)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
@@ -24,7 +24,7 @@
<option name="TEMPLATE_CONFIGURATION" value="Django" /> <option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS"> <option name="TEMPLATE_FOLDERS">
<list> <list>
<option value="$MODULE_DIR$/drf_yasg/templates" /> <option value="$MODULE_DIR$/src/drf_yasg/templates" />
</list> </list>
</option> </option>
</component> </component>
@@ -32,4 +32,4 @@
<option name="projectConfiguration" value="py.test" /> <option name="projectConfiguration" value="py.test" />
<option name="PROJECT_TEST_RUNNER" value="py.test" /> <option name="PROJECT_TEST_RUNNER" value="py.test" />
</component> </component>
</module> </module>
+1 -1
View File
@@ -362,7 +362,7 @@ provided out of the box - if you have ``djangorestframework-camel-case`` install
:alt: Codecov :alt: Codecov
.. |pypi-version| image:: https://img.shields.io/pypi/v/drf-yasg.svg .. |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 :alt: PyPI
.. |rtd-badge| image:: https://img.shields.io/readthedocs/drf-yasg.svg .. |rtd-badge| image:: https://img.shields.io/readthedocs/drf-yasg.svg
+75
View File
@@ -2,10 +2,57 @@
Changelog 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** **1.4.0**
********* *********
*Release date: Feb 04, 2018*
- **ADDED:** added settings for OAuth2 client configuration in ``swagger-ui`` (:issue:`53`) - **ADDED:** added settings for OAuth2 client configuration in ``swagger-ui`` (:issue:`53`)
- **IMPROVED:** updated ``swagger-ui`` to version 3.9.3 - **IMPROVED:** updated ``swagger-ui`` to version 3.9.3
@@ -13,6 +60,8 @@ Changelog
**1.3.1** **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`) - **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 ``produces`` based on renderer classes
- **IMPROVED:** added generation of top-level ``consumes`` and ``produces`` based on - **IMPROVED:** added generation of top-level ``consumes`` and ``produces`` based on
@@ -22,6 +71,8 @@ Changelog
**1.3.0** **1.3.0**
********* *********
*Release date: Jan 23, 2018*
- **ADDED:** security requirements are now correctly set and can be customized; this should fix problems related - **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`) to authentication in ``swagger-ui`` Try it out! (:issue:`50`, :pr:`54`)
- **IMPROVED:** updated ``swagger-ui`` to version 3.9.2 - **IMPROVED:** updated ``swagger-ui`` to version 3.9.2
@@ -32,6 +83,8 @@ Changelog
**1.2.2** **1.2.2**
********* *********
*Release date: Jan 12, 2018*
- **FIXED:** djangorestframework>=3.7.7 is now required because of breaking changes - **FIXED:** djangorestframework>=3.7.7 is now required because of breaking changes
(:issue:`44`, :pr:`45`, thanks to :ghuser:`h-hirokawa`) (:issue:`44`, :pr:`45`, thanks to :ghuser:`h-hirokawa`)
@@ -39,12 +92,16 @@ Changelog
**1.2.1** **1.2.1**
********* *********
*Release date: Jan 12, 2018*
- Fixed deployment issues - Fixed deployment issues
********* *********
**1.2.0** **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 - **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`) longest common prefix of API urls (:issue:`37`, :pr:`42`)
- **IMPROVED:** removed inline scripts and styles from bundled HTML templates to increase CSP compatibility - **IMPROVED:** removed inline scripts and styles from bundled HTML templates to increase CSP compatibility
@@ -56,6 +113,8 @@ Changelog
**1.1.3** **1.1.3**
********* *********
*Release date: Jan 02, 2018*
- **FIXED:** schema view cache will now always ``Vary`` on the ``Cookie`` and ``Authentication`` (the - **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 ``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 authentication in ``swagger-ui`` and ``CurrentUserDefault`` values in the schema
@@ -64,6 +123,8 @@ Changelog
**1.1.2** **1.1.2**
********* *********
*Release date: Jan 01, 2018*
- **IMPROVED:** updated ``swagger-ui`` to version 3.8.1 - **IMPROVED:** updated ``swagger-ui`` to version 3.8.1
- **IMPROVED:** removed some unneeded static files - **IMPROVED:** removed some unneeded static files
@@ -71,6 +132,8 @@ Changelog
**1.1.1** **1.1.1**
********* *********
*Release date: Dec 27, 2017*
- **ADDED:** :ref:`generate_swagger management command <management-command>` - **ADDED:** :ref:`generate_swagger management command <management-command>`
(:issue:`29`, :pr:`31`, thanks to :ghuser:`beaugunderson`) (:issue:`29`, :pr:`31`, thanks to :ghuser:`beaugunderson`)
- **FIXED:** fixed improper generation of ``\Z`` regex tokens - will now be repalced by ``$`` - **FIXED:** fixed improper generation of ``\Z`` regex tokens - will now be repalced by ``$``
@@ -79,6 +142,8 @@ Changelog
**1.1.0** **1.1.0**
********* *********
*Release date: Dec 27, 2017*
- **ADDED:** added support for APIs versioned with ``URLPathVersioning`` or ``NamespaceVersioning`` - **ADDED:** added support for APIs versioned with ``URLPathVersioning`` or ``NamespaceVersioning``
- **ADDED:** added ability to recursively customize schema generation - **ADDED:** added ability to recursively customize schema generation
:ref:`using pluggable inspector classes <custom-spec-inspectors>` :ref:`using pluggable inspector classes <custom-spec-inspectors>`
@@ -94,6 +159,8 @@ Changelog
**1.0.6** **1.0.6**
********* *********
*Release date: Dec 23, 2017*
- **FIXED:** Swagger UI "Try it out!" should now work with Django login - **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`) - **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 - **IMPROVED:** updated ``swagger-ui`` to version 3.8.0
@@ -105,6 +172,8 @@ Changelog
**1.0.5** **1.0.5**
********* *********
*Release date: Dec 18, 2017*
- **FIXED:** fixed a crash caused by having read-only Serializers nested by reference - **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 - **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>`_ `path() <https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.path>`_
@@ -116,6 +185,8 @@ Changelog
**1.0.4** **1.0.4**
********* *********
*Release date: Dec 16, 2017*
- **FIXED:** fixed improper generation of YAML references - **FIXED:** fixed improper generation of YAML references
- **ADDED:** added ``query_serializer`` parameter to - **ADDED:** added ``query_serializer`` parameter to
:func:`@swagger_auto_schema <.swagger_auto_schema>` (:issue:`16`, :pr:`17`) :func:`@swagger_auto_schema <.swagger_auto_schema>` (:issue:`16`, :pr:`17`)
@@ -124,6 +195,8 @@ Changelog
**1.0.3** **1.0.3**
********* *********
*Release date: Dec 15, 2017*
- **FIXED:** fixed bug that caused schema views returned from cache to fail (:issue:`14`) - **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 - **FIXED:** disabled automatic generation of response schemas for form operations to avoid confusing errors caused by
attempting to shove file parameters into Schema objects attempting to shove file parameters into Schema objects
@@ -132,4 +205,6 @@ Changelog
**1.0.2** **1.0.2**
********* *********
*Release date: Dec 13, 2017*
- First published version - First published version
+23 -23
View File
@@ -162,6 +162,18 @@ autodoc_mock_imports = []
nitpick_ignore = [ nitpick_ignore = [
('py:class', 'object'), ('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', 'Exception'),
('py:class', 'collections.OrderedDict'), ('py:class', 'collections.OrderedDict'),
@@ -174,29 +186,17 @@ nitpick_ignore = [
('py:class', 'OpenAPICodecJson'), ('py:class', 'OpenAPICodecJson'),
('py:class', 'OpenAPISchemaGenerator'), ('py:class', 'OpenAPISchemaGenerator'),
('py:obj', 'bool'), ('py:class', 'coreapi.Field'),
('py:obj', 'dict'), ('py:class', 'BaseFilterBackend'),
('py:obj', 'list'), ('py:class', 'BasePagination'),
('py:obj', 'str'), ('py:class', 'Request'),
('py:obj', 'int'), ('py:class', 'rest_framework.request.Request'),
('py:obj', 'bytes'), ('py:class', 'rest_framework.serializers.Field'),
('py:obj', 'tuple'), ('py:class', 'serializers.Field'),
('py:obj', 'callable'), ('py:class', 'serializers.BaseSerializer'),
('py:obj', 'type'), ('py:class', 'Serializer'),
('py:obj', 'OrderedDict'), ('py:class', 'BaseSerializer'),
('py:obj', 'None'), ('py:class', 'APIView'),
('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'),
] ]
# even though the package should be already installed, the sphinx build on RTD # even though the package should be already installed, the sphinx build on RTD
+27 -1
View File
@@ -172,7 +172,11 @@ You can define some per-serializer options by adding a ``Meta`` class to your se
Currently, the only option you can add here is 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 * ``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 Subclassing and extending
@@ -301,3 +305,25 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
This means that you should generally avoid view or method-specific ``FieldInspector``\ s if you are dealing with This means that you should generally avoid view or method-specific ``FieldInspector``\ s if you are dealing with
references (a.k.a named models), because you can never know which view will be the first to generate the schema 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. for a given serializer.
**IMPORTANT:** nested fields on ``ModelSerializer``\ s that are generated from model ``ForeignKeys`` will always be
output by value. If you want the by-reference behaviour you have to explictly set the serializer class of nested
fields instead of letting ``ModelSerializer`` generate one automatically; for example:
.. code-block:: python
class OneSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ('id',)
class AnotherSerializer(serializers.ModelSerializer):
chilf = OneSerializer()
class Meta:
model = SomeParentModel
fields = ('id', 'child')
Another caveat that stems from this is that any serializer named "``NestedSerializer``" will be forced inline
unless it has a ``ref_name`` set explicitly.
+45 -42
View File
@@ -4,9 +4,9 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"dependencies": { "dependencies": {
"argparse": { "argparse": {
"version": "1.0.9", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": { "requires": {
"sprintf-js": "1.0.3" "sprintf-js": "1.0.3"
} }
@@ -38,9 +38,9 @@
} }
}, },
"commander": { "commander": {
"version": "2.13.0", "version": "2.14.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz",
"integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==",
"optional": true "optional": true
}, },
"core-js": { "core-js": {
@@ -73,9 +73,9 @@
"integrity": "sha1-8TyUAhQdoJ50rfTmN5jXkiBEOPI=" "integrity": "sha1-8TyUAhQdoJ50rfTmN5jXkiBEOPI="
}, },
"es6-promise": { "es6-promise": {
"version": "4.2.2", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.2.tgz", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
"integrity": "sha512-LSas5vsuA6Q4nEdf9wokY5/AJYXry98i0IzXsv49rYsgDGDNDPbqAYR1Pe23iFxygfbGZNR/5VrHXBCh2BhvUQ==" "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ=="
}, },
"esprima": { "esprima": {
"version": "4.0.0", "version": "4.0.0",
@@ -126,7 +126,7 @@
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
"integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
"requires": { "requires": {
"argparse": "1.0.9", "argparse": "1.0.10",
"esprima": "4.0.0" "esprima": "4.0.0"
} }
}, },
@@ -145,10 +145,10 @@
"requires": { "requires": {
"call-me-maybe": "1.0.1", "call-me-maybe": "1.0.1",
"debug": "3.1.0", "debug": "3.1.0",
"es6-promise": "4.2.2", "es6-promise": "4.2.4",
"js-yaml": "3.10.0", "js-yaml": "3.10.0",
"ono": "4.0.3", "ono": "4.0.3",
"z-schema": "3.19.0" "z-schema": "3.19.1"
} }
}, },
"lodash.get": { "lodash.get": {
@@ -185,9 +185,12 @@
} }
}, },
"openapi-sampler": { "openapi-sampler": {
"version": "0.4.3", "version": "1.0.0-beta.8",
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-0.4.3.tgz", "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.0.0-beta.8.tgz",
"integrity": "sha512-Ml6o1gt++ZQ4JKL344YRo/fX05yuM6C+l/mGVX2yjhu1BRKyrRK4Z46uBTKSVaag1xINBFwYG7dZdz/10AmPzA==" "integrity": "sha1-v0P/R3N/xOH5iNDiCC1JeI9B3q0=",
"requires": {
"json-pointer": "0.6.0"
}
}, },
"perfect-scrollbar": { "perfect-scrollbar": {
"version": "0.8.1", "version": "0.8.1",
@@ -195,36 +198,36 @@
"integrity": "sha512-RNC5tX/JMRYR+qVdJTEAWnRxw0Yf9lvbO8lTuAOvgDODkiA8lveTSkvrNMhmaGKEyimJpJl+myb/syVS9YyPuw==" "integrity": "sha512-RNC5tX/JMRYR+qVdJTEAWnRxw0Yf9lvbO8lTuAOvgDODkiA8lveTSkvrNMhmaGKEyimJpJl+myb/syVS9YyPuw=="
}, },
"prismjs": { "prismjs": {
"version": "1.10.0", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.10.0.tgz", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.11.0.tgz",
"integrity": "sha1-d+UYfCrmsyU/zDEwKc8l/lN3hyE=", "integrity": "sha1-KXrvM+t5Qhv9sZJzpQkspRWXDSk=",
"requires": { "requires": {
"clipboard": "1.7.1" "clipboard": "1.7.1"
} }
}, },
"process-nextick-args": { "process-nextick-args": {
"version": "1.0.7", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
}, },
"readable-stream": { "readable-stream": {
"version": "2.3.3", "version": "2.3.4",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==",
"requires": { "requires": {
"core-util-is": "1.0.2", "core-util-is": "1.0.2",
"inherits": "2.0.3", "inherits": "2.0.3",
"isarray": "1.0.0", "isarray": "1.0.0",
"process-nextick-args": "1.0.7", "process-nextick-args": "2.0.0",
"safe-buffer": "5.1.1", "safe-buffer": "5.1.1",
"string_decoder": "1.0.3", "string_decoder": "1.0.3",
"util-deprecate": "1.0.2" "util-deprecate": "1.0.2"
} }
}, },
"redoc": { "redoc": {
"version": "1.20.0", "version": "1.21.0",
"resolved": "https://registry.npmjs.org/redoc/-/redoc-1.20.0.tgz", "resolved": "https://registry.npmjs.org/redoc/-/redoc-1.21.0.tgz",
"integrity": "sha1-1c16xoQKJ8/7RzvSiYAFUq+CHq8=", "integrity": "sha1-RY8E7b7MqyVbQORhZA0eRG949N0=",
"requires": { "requires": {
"core-js": "2.5.3", "core-js": "2.5.3",
"dropkickjs": "2.1.10", "dropkickjs": "2.1.10",
@@ -234,9 +237,9 @@
"json-schema-ref-parser": "3.3.1", "json-schema-ref-parser": "3.3.1",
"lunr": "1.0.0", "lunr": "1.0.0",
"mark.js": "8.11.1", "mark.js": "8.11.1",
"openapi-sampler": "0.4.3", "openapi-sampler": "1.0.0-beta.8",
"perfect-scrollbar": "0.8.1", "perfect-scrollbar": "0.8.1",
"prismjs": "1.10.0", "prismjs": "1.11.0",
"remarkable": "1.7.1", "remarkable": "1.7.1",
"scrollparent": "2.0.1", "scrollparent": "2.0.1",
"slugify": "1.2.9", "slugify": "1.2.9",
@@ -298,7 +301,7 @@
"requires": { "requires": {
"builtin-status-codes": "3.0.0", "builtin-status-codes": "3.0.0",
"inherits": "2.0.3", "inherits": "2.0.3",
"readable-stream": "2.3.3", "readable-stream": "2.3.4",
"to-arraybuffer": "1.0.1", "to-arraybuffer": "1.0.1",
"xtend": "4.0.1" "xtend": "4.0.1"
} }
@@ -312,9 +315,9 @@
} }
}, },
"swagger-ui-dist": { "swagger-ui-dist": {
"version": "3.9.3", "version": "3.10.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.9.3.tgz", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.10.0.tgz",
"integrity": "sha1-yrqR6FUNfSRkoIRWvZNtfEMPDdM=" "integrity": "sha1-ilrzP/ImPHFaFD9z8qjUfVOZQ+8="
}, },
"tiny-emitter": { "tiny-emitter": {
"version": "2.0.2", "version": "2.0.2",
@@ -348,9 +351,9 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
}, },
"validator": { "validator": {
"version": "9.2.0", "version": "9.4.1",
"resolved": "https://registry.npmjs.org/validator/-/validator-9.2.0.tgz", "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz",
"integrity": "sha512-6Ij4Eo0KM4LkR0d0IegOwluG5453uqT5QyF5SV5Ezvm8/zmkKI/L4eoraafZGlZPC9guLkwKzgypcw8VGWWnGA==" "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA=="
}, },
"xtend": { "xtend": {
"version": "4.0.1", "version": "4.0.1",
@@ -358,14 +361,14 @@
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
}, },
"z-schema": { "z-schema": {
"version": "3.19.0", "version": "3.19.1",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.19.0.tgz", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.19.1.tgz",
"integrity": "sha512-V94f3ODuluBS4kQLLjNhwoMek0dyIXCsvNu/A17dAyJ6sMhT5KkJQwSn07R0naByLIXJWMDk+ruMfI/3G3hS4Q==", "integrity": "sha512-jPNzqmOu3+AGbb4krDODqo4QBzwUGDVzyfGyy1HtWaUnafltQotatSpxxWd6Mp0iSZOUwHU5sqKYi+U8HsHMkg==",
"requires": { "requires": {
"commander": "2.13.0", "commander": "2.14.1",
"lodash.get": "4.4.2", "lodash.get": "4.4.2",
"lodash.isequal": "4.5.0", "lodash.isequal": "4.5.0",
"validator": "9.2.0" "validator": "9.4.1"
} }
}, },
"zone.js": { "zone.js": {
+2 -2
View File
@@ -1,8 +1,8 @@
{ {
"name": "drf-yasg", "name": "drf-yasg",
"dependencies": { "dependencies": {
"redoc": "^1.20.0", "redoc": "^1.21.0",
"swagger-ui-dist": "^3.9.3" "swagger-ui-dist": "^3.10.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
+1 -1
View File
@@ -4,7 +4,7 @@ openapi_codec>=1.3.2
ruamel.yaml>=0.15.34 ruamel.yaml>=0.15.34
inflection>=0.3.1 inflection>=0.3.1
future>=0.16.0 future>=0.16.0
six>=1.11.0 six>=1.10.0
uritemplate>=3.0.0 uritemplate>=3.0.0
djangorestframework>=3.7.7 djangorestframework>=3.7.7
+1 -1
View File
@@ -1,5 +1,5 @@
# used by the 'docs' tox env for building the documentation # used by the 'docs' tox env for building the documentation
Sphinx>=1.6.5 Sphinx>=1.7.0
sphinx_rtd_theme>=0.2.4 sphinx_rtd_theme>=0.2.4
Pillow>=4.3.0 Pillow>=4.3.0
readme_renderer>=17.2 readme_renderer>=17.2
+2 -2
View File
@@ -405,8 +405,8 @@ class OpenAPISchemaGenerator(object):
for variable in uritemplate.variables(path): for variable in uritemplate.variables(path):
model, model_field = get_queryset_field(queryset, variable) model, model_field = get_queryset_field(queryset, variable)
attrs = get_basic_type_info(model_field) or {'type': openapi.TYPE_STRING} 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: if getattr(view_cls, 'lookup_field', None) == variable and attrs['type'] == openapi.TYPE_STRING:
attrs['pattern'] = view_cls.lookup_value_regex attrs['pattern'] = getattr(view_cls, 'lookup_value_regex', attrs.get('pattern', None))
if model_field and model_field.help_text: if model_field and model_field.help_text:
description = force_text(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 rest_framework.utils import encoders, json
from .. import openapi 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 #: Sentinel value that inspectors must return to signal that they do not know how to handle an object
NotHandled = object() NotHandled = object()
@@ -224,6 +224,8 @@ class FieldInspector(BaseInspector):
# JSON roundtrip ensures that the value is valid JSON; # JSON roundtrip ensures that the value is valid JSON;
# for example, sets and tuples get transformed into lists # for example, sets and tuples get transformed into lists
default = json.loads(json.dumps(default, cls=encoders.JSONEncoder)) default = json.loads(json.dumps(default, cls=encoders.JSONEncoder))
if decimal_as_float(field):
default = float(default)
except Exception: # pragma: no cover except Exception: # pragma: no cover
logger.warning("'default' on schema for %s will not be set because " logger.warning("'default' on schema for %s will not be set because "
"to_representation raised an exception", field, exc_info=True) "to_representation raised an exception", field, exc_info=True)
@@ -232,7 +234,8 @@ class FieldInspector(BaseInspector):
if default is not None: if default is not None:
instance_kwargs['default'] = default 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.setdefault('description', description)
instance_kwargs.update(kwargs) instance_kwargs.update(kwargs)
+61 -18
View File
@@ -1,5 +1,7 @@
import logging
import operator import operator
from collections import OrderedDict from collections import OrderedDict
from decimal import Decimal
from django.core import validators from django.core import validators
from django.db import models from django.db import models
@@ -8,9 +10,11 @@ from rest_framework.settings import api_settings as rest_framework_settings
from .. import openapi from .. import openapi
from ..errors import SwaggerGenerationError from ..errors import SwaggerGenerationError
from ..utils import filter_none from ..utils import decimal_as_float, filter_none
from .base import FieldInspector, NotHandled, SerializerInspector from .base import FieldInspector, NotHandled, SerializerInspector
logger = logging.getLogger(__name__)
class InlineSerializerInspector(SerializerInspector): class InlineSerializerInspector(SerializerInspector):
"""Provides serializer conversions using :meth:`.FieldInspector.field_to_swagger_object`.""" """Provides serializer conversions using :meth:`.FieldInspector.field_to_swagger_object`."""
@@ -53,10 +57,14 @@ class InlineSerializerInspector(SerializerInspector):
serializer = field serializer = field
serializer_meta = getattr(serializer, 'Meta', None) serializer_meta = getattr(serializer, 'Meta', None)
serializer_name = type(serializer).__name__
if hasattr(serializer_meta, 'ref_name'): if hasattr(serializer_meta, 'ref_name'):
ref_name = 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: else:
ref_name = type(serializer).__name__ ref_name = serializer_name
if ref_name.endswith('Serializer'): if ref_name.endswith('Serializer'):
ref_name = ref_name[:-len('Serializer')] ref_name = ref_name[:-len('Serializer')]
@@ -76,11 +84,17 @@ class InlineSerializerInspector(SerializerInspector):
if child.required: if child.required:
required.append(property_name) required.append(property_name)
return SwaggerType( result = SwaggerType(
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
properties=properties, properties=properties,
required=required or None, 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: if not ref_name or not use_references:
return make_schema_definition() return make_schema_definition()
@@ -258,18 +272,29 @@ def find_limits(field):
if isinstance(field, field_class) if isinstance(field, field_class)
] ]
if isinstance(field, serializers.DecimalField) and not decimal_as_float(field):
return limits
for validator in field.validators: for validator in field.validators:
if not hasattr(validator, 'limit_value'): if not hasattr(validator, 'limit_value'):
continue 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: for validator_class, attr, improves in applicable_limits:
if isinstance(validator, validator_class): if isinstance(validator, validator_class):
if attr not in limits or improves(validator.limit_value, limits[attr]): if attr not in limits or improves(limit_value, limits[attr]):
limits[attr] = validator.limit_value limits[attr] = limit_value
return OrderedDict(sorted(limits.items())) 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 = [ model_field_to_basic_type = [
(models.AutoField, (openapi.TYPE_INTEGER, None)), (models.AutoField, (openapi.TYPE_INTEGER, None)),
(models.BinaryField, (openapi.TYPE_STRING, openapi.FORMAT_BINARY)), (models.BinaryField, (openapi.TYPE_STRING, openapi.FORMAT_BINARY)),
@@ -277,7 +302,7 @@ model_field_to_basic_type = [
(models.NullBooleanField, (openapi.TYPE_BOOLEAN, None)), (models.NullBooleanField, (openapi.TYPE_BOOLEAN, None)),
(models.DateTimeField, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)), (models.DateTimeField, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)),
(models.DateField, (openapi.TYPE_STRING, openapi.FORMAT_DATE)), (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.DurationField, (openapi.TYPE_INTEGER, None)),
(models.FloatField, (openapi.TYPE_NUMBER, None)), (models.FloatField, (openapi.TYPE_NUMBER, None)),
(models.IntegerField, (openapi.TYPE_INTEGER, 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.UUIDField, (openapi.TYPE_STRING, openapi.FORMAT_UUID)),
(serializers.RegexField, (openapi.TYPE_STRING, None)), (serializers.RegexField, (openapi.TYPE_STRING, None)),
(serializers.CharField, (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.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.DurationField, (openapi.TYPE_NUMBER, None)), # ?
(serializers.DateField, (openapi.TYPE_STRING, openapi.FORMAT_DATE)), (serializers.DateField, (openapi.TYPE_STRING, openapi.FORMAT_DATE)),
(serializers.DateTimeField, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)), (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: for field_class, type_format in basic_type_info:
if isinstance(field, field_class): if isinstance(field, field_class):
swagger_type, format = type_format swagger_type, format = type_format
if callable(swagger_type):
swagger_type = swagger_type(field)
if callable(format): if callable(format):
format = format(field) format = format(field)
break break
else: # pragma: no cover else: # pragma: no cover
return None 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) limits = find_limits(field)
result = OrderedDict([ result = OrderedDict([
@@ -365,16 +397,27 @@ class ChoiceFieldInspector(FieldInspector):
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs): 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) SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
if isinstance(field, serializers.MultipleChoiceField): if isinstance(field, serializers.ChoiceField):
return SwaggerType( enum_type = openapi.TYPE_STRING
type=openapi.TYPE_ARRAY,
items=ChildSwaggerType( # for ModelSerializer, try to infer the type from the associated model field
type=openapi.TYPE_STRING, serializer = get_parent_serializer(field)
enum=list(field.choices.keys()) 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=enum_type, enum=list(field.choices.keys()))
return SwaggerType(type=openapi.TYPE_STRING, enum=list(field.choices.keys()))
return NotHandled return NotHandled
+1 -1
View File
@@ -221,7 +221,7 @@ class SwaggerAutoSchema(ViewInspector):
) )
elif isinstance(serializer, openapi.Response): elif isinstance(serializer, openapi.Response):
response = serializer 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) serializer = force_serializer_instance(response.schema)
response.schema = self.serializer_to_schema(serializer) response.schema = self.serializer_to_schema(serializer)
elif isinstance(serializer, openapi.Schema.OR_REF): elif isinstance(serializer, openapi.Schema.OR_REF):
+2
View File
@@ -35,6 +35,7 @@ FORMAT_URI = "uri" #:
# pulled out of my ass # pulled out of my ass
FORMAT_UUID = "uuid" #: FORMAT_UUID = "uuid" #:
FORMAT_SLUG = "slug" #: FORMAT_SLUG = "slug" #:
FORMAT_DECIMAL = "decimal"
IN_BODY = 'body' #: IN_BODY = 'body' #:
IN_PATH = 'path' #: IN_PATH = 'path' #:
@@ -395,6 +396,7 @@ class Parameter(SwaggerDict):
self.enum = enum self.enum = enum
self.pattern = pattern self.pattern = pattern
self.items = items self.items = items
self.default = default
self._insert_extras__() self._insert_extras__()
if self['in'] == IN_PATH: if self['in'] == IN_PATH:
# path parameters must always be required # path parameters must always be required
+8 -1
View File
@@ -1,7 +1,9 @@
from django.shortcuts import render, resolve_url 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 rest_framework.utils import json
from drf_yasg.openapi import Swagger
from .app_settings import redoc_settings, swagger_settings from .app_settings import redoc_settings, swagger_settings
from .codecs import VALIDATORS, OpenAPICodecJson, OpenAPICodecYaml from .codecs import VALIDATORS, OpenAPICodecJson, OpenAPICodecYaml
@@ -51,6 +53,11 @@ class _UIRenderer(BaseRenderer):
template = '' template = ''
def render(self, swagger, accepted_media_type=None, renderer_context=None): 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) self.set_context(renderer_context, swagger)
return render( return render(
renderer_context['request'], renderer_context['request'],
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
+16
View File
@@ -2,9 +2,11 @@ import inspect
import logging import logging
from collections import OrderedDict from collections import OrderedDict
from django.db import models
from rest_framework import serializers, status from rest_framework import serializers, status
from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.request import is_form_media_type 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 from rest_framework.views import APIView
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -118,6 +120,8 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
_methods = methods _methods = methods
if methods or method: 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 bool(methods) != bool(method), "specify either method or methods"
assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \ assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \
" use `method` for a single argument" " use `method` for a single argument"
@@ -273,3 +277,15 @@ def get_produces(renderer_classes):
media_types = [renderer.media_type for renderer in renderer_classes or []] media_types = [renderer.media_type for renderer in renderer_classes or []]
media_types = [encoding for encoding in media_types if 'html' not in encoding] media_types = [encoding for encoding in media_types if 'html' not in encoding]
return media_types 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_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True) date_modified = models.DateTimeField(auto_now=True)
author = models.ForeignKey('auth.User', related_name='articles', on_delete=models.CASCADE) 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) cover = models.ImageField(upload_to='article/original/', blank=True)
+2 -2
View File
@@ -16,7 +16,7 @@ class ArticleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Article model = Article
fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified', fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified',
'references', 'uuid', 'cover', 'cover_name') 'references', 'uuid', 'cover', 'cover_name', 'article_type')
read_only_fields = ('date_created', 'date_modified', read_only_fields = ('date_created', 'date_modified',
'references', 'uuid', 'cover_name') 'references', 'uuid', 'cover_name')
lookup_field = 'slug' lookup_field = 'slug'
@@ -31,7 +31,7 @@ class ArticleSerializer(serializers.ModelSerializer):
class ImageUploadSerializer(serializers.Serializer): 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( image_styles = serializers.ListSerializer(
child=serializers.ChoiceField(choices=['wide', 'tall', 'thumb', 'social']), child=serializers.ChoiceField(choices=['wide', 'tall', 'thumb', 'social']),
help_text="Parameter with Items" 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 django.contrib.auth import get_user_model
from rest_framework import serializers from rest_framework import serializers
from rest_framework.compat import MinValueValidator
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet
class LanguageSerializer(serializers.Serializer): class LanguageSerializer(serializers.Serializer):
name = serializers.ChoiceField( name = serializers.ChoiceField(
choices=LANGUAGE_CHOICES, default='python', help_text='The name of the programming language') 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): class ExampleProjectSerializer(serializers.Serializer):
project_name = serializers.CharField(help_text='Name of the project') project_name = serializers.CharField(help_text='Name of the project')
github_repo = serializers.CharField(required=True, help_text='Github repository 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) example_projects = serializers.ListSerializer(child=ExampleProjectSerializer(), read_only=True)
difficulty_factor = serializers.FloatField(help_text="this is here just to test FloatField", difficulty_factor = serializers.FloatField(help_text="this is here just to test FloatField",
read_only=True, default=lambda: 6.9) 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): def create(self, validated_data):
""" """
+6 -2
View File
@@ -1,7 +1,7 @@
from djangorestframework_camel_case.parser import CamelCaseJSONParser from djangorestframework_camel_case.parser import CamelCaseJSONParser
from djangorestframework_camel_case.render import CamelCaseJSONRenderer from djangorestframework_camel_case.render import CamelCaseJSONRenderer
from inflection import camelize from inflection import camelize
from rest_framework import generics from rest_framework import generics, status
from rest_framework.parsers import FormParser from rest_framework.parsers import FormParser
from drf_yasg import openapi from drf_yasg import openapi
@@ -62,8 +62,12 @@ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
type=openapi.TYPE_INTEGER, type=openapi.TYPE_INTEGER,
description="path parameter override", description="path parameter override",
required=True required=True
),
], responses={
status.HTTP_204_NO_CONTENT: openapi.Response(
description="This should not crash"
) )
]) })
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
"""delete method docstring""" """delete method docstring"""
return super(SnippetDetail, self).patch(request, *args, **kwargs) return super(SnippetDetail, self).patch(request, *args, **kwargs)
+1
View File
@@ -26,6 +26,7 @@ INSTALLED_APPS = [
'snippets', 'snippets',
'users', 'users',
'articles', 'articles',
'todo',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
+1
View File
@@ -62,5 +62,6 @@ urlpatterns = [
url(r'^snippets/', include('snippets.urls')), url(r'^snippets/', include('snippets.urls')),
url(r'^articles/', include('articles.urls')), url(r'^articles/', include('articles.urls')),
url(r'^users/', include('users.urls')), url(r'^users/', include('users.urls')),
url(r'^todo/', include('todo.urls')),
url(r'^plain/', plain_view), 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
+181 -9
View File
@@ -198,9 +198,10 @@ paths:
- name: what_am_i_doing - name: what_am_i_doing
in: formData in: formData
description: test description: test
required: true required: false
type: string type: string
pattern: ^69$ pattern: ^69$
default: '69'
- name: image_styles - name: image_styles
in: formData in: formData
description: Parameter with Items description: Parameter with Items
@@ -330,7 +331,7 @@ paths:
type: integer type: integer
responses: responses:
'204': '204':
description: '' description: This should not crash
tags: tags:
- snippets - snippets
parameters: parameters:
@@ -339,6 +340,105 @@ paths:
description: A unique integer value identifying this snippet. description: A unique integer value identifying this snippet.
required: true required: true
type: integer 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/: /users/:
get: get:
operationId: users_list operationId: users_list
@@ -491,6 +591,16 @@ definitions:
cover_name: cover_name:
type: string type: string
readOnly: true readOnly: true
article_type:
description: IntegerField declared on model with choices=(...) and exposed
via ModelSerializer
type: integer
enum:
- 1
- 2
- 3
- 7
- 8
Project: Project:
required: required:
- projectName - projectName
@@ -538,7 +648,6 @@ definitions:
title: Linenos title: Linenos
type: boolean type: boolean
language: language:
title: Language
description: Sample help text for language description: Sample help text for language
type: object type: object
properties: properties:
@@ -983,7 +1092,6 @@ definitions:
- zephir - zephir
default: python default: python
styles: styles:
title: Styles
type: array type: array
items: items:
type: string type: string
@@ -1020,12 +1128,10 @@ definitions:
default: default:
- friendly - friendly
lines: lines:
title: Lines
type: array type: array
items: items:
type: integer type: integer
exampleProjects: exampleProjects:
title: Example projects
type: array type: array
items: items:
$ref: '#/definitions/Project' $ref: '#/definitions/Project'
@@ -1036,6 +1142,75 @@ definitions:
type: number type: number
readOnly: true readOnly: true
default: 6.9 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: UserSerializerrr:
required: required:
- username - username
@@ -1060,13 +1235,11 @@ definitions:
format: email format: email
maxLength: 254 maxLength: 254
articles: articles:
title: Articles
type: array type: array
items: items:
type: integer type: integer
uniqueItems: true uniqueItems: true
snippets: snippets:
title: Snippets
type: array type: array
items: items:
type: integer type: integer
@@ -1084,7 +1257,6 @@ definitions:
format: date format: date
readOnly: true readOnly: true
article_slugs: article_slugs:
title: Article slugs
type: array type: array
items: items:
type: string type: string
+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) json_bytes = codec_json.encode(swagger)
swagger_dict = json.loads(json_bytes.decode('utf-8'), object_pairs_hook=OrderedDict) swagger_dict = json.loads(json_bytes.decode('utf-8'), object_pairs_hook=OrderedDict)
compare_schemas(swagger_dict, reference_schema) 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']