Compare commits

...

10 Commits

Author SHA1 Message Date
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
23 changed files with 231 additions and 101 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ License
BSD 3-Clause 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 Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
+18
View File
@@ -2,6 +2,24 @@
Changelog Changelog
######### #########
*********
**1.4.1**
*********
- **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**
*********
- **ADDED:** added settings for OAuth2 client configuration in ``swagger-ui`` (:issue:`53`)
- **IMPROVED:** updated ``swagger-ui`` to version 3.9.3
********* *********
**1.3.1** **1.3.1**
********* *********
+24 -24
View File
@@ -39,7 +39,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = 'drf-yasg' project = 'drf-yasg'
copyright = '2017, Cristi V.' copyright = '2018, Cristi V.'
author = 'Cristi V.' author = 'Cristi V.'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
@@ -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
+16
View File
@@ -47,3 +47,19 @@ Operation-level overrides can be added using the ``security`` parameter of
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`. :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.
+19
View File
@@ -253,6 +253,25 @@ Controls how many levels are expaned by default when showing nested models.
**Default**: :python:`3` |br| **Default**: :python:`3` |br|
*Maps to parameter*: ``defaultModelExpandDepth`` *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`` ``REDOC_SETTINGS``
****************** ******************
+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.2", "version": "3.10.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.9.2.tgz", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.10.0.tgz",
"integrity": "sha1-XuK7G2IFPC3ydGnlVZDGX8AOidQ=" "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": {
+8 -3
View File
@@ -1,7 +1,12 @@
{ {
"name": "drf-yasg", "name": "drf-yasg",
"dependencies": { "dependencies": {
"redoc": "^1.20.0", "redoc": "^1.21.0",
"swagger-ui-dist": "^3.9.2" "swagger-ui-dist": "^3.10.0"
} },
"repository": {
"type": "git",
"url": "https://github.com/axnsan12/drf-yasg.git"
},
"private": true
} }
+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
View File
@@ -43,6 +43,8 @@ SWAGGER_DEFAULTS = {
'SHOW_EXTENSIONS': True, 'SHOW_EXTENSIONS': True,
'DEFAULT_MODEL_RENDERING': 'model', 'DEFAULT_MODEL_RENDERING': 'model',
'DEFAULT_MODEL_DEPTH': 3, 'DEFAULT_MODEL_DEPTH': 3,
'OAUTH2_REDIRECT_URL': None,
'OAUTH2_CONFIG': {},
} }
REDOC_DEFAULTS = { REDOC_DEFAULTS = {
+3 -1
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)
+22 -6
View File
@@ -1,5 +1,6 @@
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,7 +9,7 @@ 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
@@ -258,18 +259,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 +289,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 +312,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,6 +340,8 @@ 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
+1
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' #:
+15 -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'],
@@ -63,6 +70,7 @@ class _UIRenderer(BaseRenderer):
renderer_context['version'] = swagger.info.version renderer_context['version'] = swagger.info.version
renderer_context['swagger_settings'] = json.dumps(self.get_swagger_ui_settings()) renderer_context['swagger_settings'] = json.dumps(self.get_swagger_ui_settings())
renderer_context['redoc_settings'] = json.dumps(self.get_redoc_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['USE_SESSION_AUTH'] = swagger_settings.USE_SESSION_AUTH
renderer_context.update(self.get_auth_urls()) renderer_context.update(self.get_auth_urls())
@@ -85,6 +93,7 @@ class _UIRenderer(BaseRenderer):
'defaultModelRendering': swagger_settings.DEFAULT_MODEL_RENDERING, 'defaultModelRendering': swagger_settings.DEFAULT_MODEL_RENDERING,
'defaultModelExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH, 'defaultModelExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
'defaultModelsExpandDepth': 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} data = {k: v for k, v in data.items() if v is not None}
if swagger_settings.VALIDATOR_URL != '': if swagger_settings.VALIDATOR_URL != '':
@@ -102,6 +111,11 @@ class _UIRenderer(BaseRenderer):
return data 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): class SwaggerUIRenderer(_UIRenderer):
"""Renders a swagger-ui web interface for schema browisng. """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
@@ -49,6 +49,7 @@ function initSwaggerUi() {
}; };
var swaggerSettings = JSON.parse(document.getElementById('swagger-settings').innerHTML); var swaggerSettings = JSON.parse(document.getElementById('swagger-settings').innerHTML);
console.log(swaggerSettings);
for (var p in swaggerSettings) { for (var p in swaggerSettings) {
if (swaggerSettings.hasOwnProperty(p)) { if (swaggerSettings.hasOwnProperty(p)) {
@@ -56,6 +57,10 @@ function initSwaggerUi() {
} }
} }
window.ui = SwaggerUIBundle(swaggerConfig); window.ui = SwaggerUIBundle(swaggerConfig);
var oauth2Config = JSON.parse(document.getElementById('oauth2-config').innerHTML);
console.log(oauth2Config);
window.ui.initOAuth(oauth2Config);
} }
window.onload = function () { window.onload = function () {
@@ -60,6 +60,7 @@
<script id="swagger-settings" type="application/json">{{ swagger_settings | safe }}</script> <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-bundle.js' %}"></script>
<script src="{% static 'drf-yasg/swagger-ui-dist/swagger-ui-standalone-preset.js' %}"></script> <script src="{% static 'drf-yasg/swagger-ui-dist/swagger-ui-standalone-preset.js' %}"></script>
+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
+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):
""" """
+11
View File
@@ -1036,6 +1036,17 @@ 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
UserSerializerrr: UserSerializerrr:
required: required:
- username - username
+2 -6
View File
@@ -2,7 +2,7 @@
envlist = envlist =
py27-django111-drf37, py27-django111-drf37,
py{34,35,36}-django{111,20}-drf37, py{34,35,36}-django{111,20}-drf37,
py36-drfmaster, py36-django20-drfmaster,
lint, docs lint, docs
[travis:env] [travis:env]
@@ -17,9 +17,8 @@ deps =
drf37: djangorestframework>=3.7.7,<3.8 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/encode/django-rest-framework/archive/master.tar.gz
drfmaster: https://github.com/django/django/archive/master.tar.gz
# other dependencies # other dependencies
-rrequirements/validation.txt -rrequirements/validation.txt
@@ -28,9 +27,6 @@ deps =
commands = commands =
pytest --cov --cov-config .coveragerc --cov-append --cov-report="" {posargs} pytest --cov --cov-config .coveragerc --cov-append --cov-report="" {posargs}
[testenv:py36-drfmaster]
pip_pre = True
[testenv:lint] [testenv:lint]
skip_install = true skip_install = true
deps = deps =