Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aca0c4713e | |||
| 3077195396 | |||
| 94f6ca8c89 | |||
| ae5eeeb600 | |||
| 23ebe2ff3e | |||
| 713c669e28 | |||
| 2545ae9657 | |||
| f03d9d71e9 | |||
| a993cba7aa | |||
| bfd88cbdb4 | |||
| 123a05c82c | |||
| 7065429d47 | |||
| bd727fbe88 | |||
| 698a175a1b | |||
| 5b225423ae | |||
| 209201b9a5 | |||
| ca00ed35be |
+3
-1
@@ -2,7 +2,6 @@ language: python
|
||||
cache: pip
|
||||
|
||||
python:
|
||||
- '2.7'
|
||||
- '3.4'
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
@@ -14,6 +13,9 @@ env:
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
python: '2.7'
|
||||
env: DRF=3.7
|
||||
-
|
||||
python: '3.6'
|
||||
env: DRF=master
|
||||
-
|
||||
|
||||
@@ -3,6 +3,46 @@ Changelog
|
||||
#########
|
||||
|
||||
|
||||
*********
|
||||
**1.7.4**
|
||||
*********
|
||||
|
||||
*Release date: May 14, 2018*
|
||||
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.14.2
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-alpha.20
|
||||
- **FIXED:** ignore ``None`` return from ``get_operation`` to avoid empty ``Path`` objects in output
|
||||
- **FIXED:** request body is now allowed on ``DELETE`` endpoints (:issue:`118`)
|
||||
|
||||
*********
|
||||
**1.7.3**
|
||||
*********
|
||||
|
||||
*Release date: May 12, 2018*
|
||||
|
||||
- **FIXED:** views whose ``__init__`` methods throw exceptions will now be ignored during endpoint enumeration
|
||||
|
||||
*********
|
||||
**1.7.2**
|
||||
*********
|
||||
|
||||
*Release date: May 12, 2018*
|
||||
|
||||
- **FIXED:** fixed generation of default ``SECURITY_REQUIREMENTS`` to match documented behaviour
|
||||
- **FIXED:** ordering of ``SECURITY_REQUIREMENTS`` and ``SECURITY_DEFINITIONS`` is now stable
|
||||
|
||||
*********
|
||||
**1.7.1**
|
||||
*********
|
||||
|
||||
*Release date: May 05, 2018*
|
||||
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.14.1
|
||||
- **IMPROVED:** set ``swagger-ui`` ``showCommonExtensions`` to ``True`` by default and add
|
||||
``SHOW_COMMON_EXTENSIONS`` setting key
|
||||
- **IMPROVED:** set ``min_length=1`` when ``allow_blank=False`` (:pr:`112`, thanks to :ghuser:`elnappo`)
|
||||
- **FIXED:** made documentation ordering of ``SwaggerDict`` extra attributes stable
|
||||
|
||||
*********
|
||||
**1.7.0**
|
||||
*********
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ 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.
|
||||
By default, a top-level `security` that accepts any one of 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.
|
||||
|
||||
|
||||
@@ -255,6 +255,15 @@ Controls how many levels are expaned by default when showing nested models.
|
||||
**Default**: :python:`3` |br|
|
||||
*Maps to parameter*: ``defaultModelExpandDepth``
|
||||
|
||||
DEFAULT_MODEL_DEPTH
|
||||
-------------------
|
||||
|
||||
Controls the display of extensions (``pattern``, ``maxLength``, ``minLength``, ``maximum``, ```minimum``) fields and
|
||||
values for Parameters.
|
||||
|
||||
**Default**: :python:`True` |br|
|
||||
*Maps to parameter*: ``showCommonExtensions``
|
||||
|
||||
.. _oauth2-settings:
|
||||
|
||||
OAUTH2_REDIRECT_URL
|
||||
|
||||
Generated
+265
-1754
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "drf-yasg",
|
||||
"dependencies": {
|
||||
"redoc": "^2.0.0-alpha.17",
|
||||
"swagger-ui-dist": "^3.13.6"
|
||||
"redoc": "^2.0.0-alpha.20",
|
||||
"swagger-ui-dist": "^3.14.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -8,5 +8,4 @@ six>=1.10.0
|
||||
uritemplate>=3.0.0
|
||||
|
||||
djangorestframework>=3.7.7
|
||||
Django>=1.11.7,<2.0; python_version <= "2.7"
|
||||
Django>=1.11.7; python_version >= "3.4"
|
||||
Django>=1.11.7
|
||||
|
||||
@@ -3,8 +3,7 @@ pytest>=2.9
|
||||
pytest-pythonpath>=0.7.1
|
||||
pytest-cov>=2.5.1
|
||||
pytest-xdist>=1.22.0
|
||||
# latest pip version of pytest-django is more than a year old and does not support Django 2.0
|
||||
git+https://github.com/pytest-dev/pytest-django.git@94cccb956435dd7a719606744ee7608397e1eafb
|
||||
pytest-django>=3.2.0
|
||||
datadiff==2.0.0
|
||||
|
||||
-r testproj.txt
|
||||
|
||||
@@ -45,6 +45,7 @@ SWAGGER_DEFAULTS = {
|
||||
'SHOW_EXTENSIONS': True,
|
||||
'DEFAULT_MODEL_RENDERING': 'model',
|
||||
'DEFAULT_MODEL_DEPTH': 3,
|
||||
'SHOW_COMMON_EXTENSIONS': True,
|
||||
'OAUTH2_REDIRECT_URL': None,
|
||||
'OAUTH2_CONFIG': {},
|
||||
'SUPPORTED_SUBMIT_METHODS': [
|
||||
|
||||
+21
-11
@@ -86,14 +86,17 @@ class EndpointEnumerator(_EndpointEnumerator):
|
||||
for pattern in patterns:
|
||||
path_regex = prefix + get_original_route(pattern)
|
||||
if isinstance(pattern, URLPattern):
|
||||
path = self.get_path_from_regex(path_regex)
|
||||
callback = pattern.callback
|
||||
url_name = pattern.name
|
||||
if self.should_include_endpoint(path, callback, app_name or '', namespace or '', url_name):
|
||||
path = self.replace_version(path, callback)
|
||||
for method in self.get_allowed_methods(callback):
|
||||
endpoint = (path, method, callback)
|
||||
api_endpoints.append(endpoint)
|
||||
try:
|
||||
path = self.get_path_from_regex(path_regex)
|
||||
callback = pattern.callback
|
||||
url_name = pattern.name
|
||||
if self.should_include_endpoint(path, callback, app_name or '', namespace or '', url_name):
|
||||
path = self.replace_version(path, callback)
|
||||
for method in self.get_allowed_methods(callback):
|
||||
endpoint = (path, method, callback)
|
||||
api_endpoints.append(endpoint)
|
||||
except Exception:
|
||||
logger.warning('failed to enumerate view', exc_info=True)
|
||||
|
||||
elif isinstance(pattern, URLResolver):
|
||||
nested_endpoints = self.get_api_endpoints(
|
||||
@@ -204,9 +207,15 @@ class OpenAPISchemaGenerator(object):
|
||||
paths, prefix = self.get_paths(endpoints, components, request, public)
|
||||
|
||||
security_definitions = swagger_settings.SECURITY_DEFINITIONS
|
||||
if security_definitions is not None:
|
||||
security_definitions = OrderedDict(sorted([(key, OrderedDict(sorted(sd.items())))
|
||||
for key, sd in swagger_settings.SECURITY_DEFINITIONS.items()]))
|
||||
security_requirements = swagger_settings.SECURITY_REQUIREMENTS
|
||||
if security_requirements is None:
|
||||
security_requirements = [{security_scheme: [] for security_scheme in swagger_settings.SECURITY_DEFINITIONS}]
|
||||
security_requirements = [{security_scheme: []} for security_scheme in swagger_settings.SECURITY_DEFINITIONS]
|
||||
|
||||
security_requirements = sorted(security_requirements, key=lambda od: list(sorted(od)))
|
||||
security_requirements = [OrderedDict(sorted(sr.items())) for sr in security_requirements]
|
||||
|
||||
url = self.url
|
||||
if url is None and request is not None:
|
||||
@@ -315,7 +324,9 @@ class OpenAPISchemaGenerator(object):
|
||||
if not public and not self._gen.has_view_permissions(path, method, view):
|
||||
continue
|
||||
|
||||
operations[method.lower()] = self.get_operation(view, path, prefix, method, components, request)
|
||||
operation = self.get_operation(view, path, prefix, method, components, request)
|
||||
if operation is not None:
|
||||
operations[method.lower()] = operation
|
||||
|
||||
if operations:
|
||||
# since the common prefix is used as the API basePath, it must be stripped
|
||||
@@ -401,7 +412,6 @@ class OpenAPISchemaGenerator(object):
|
||||
"""
|
||||
parameters = []
|
||||
queryset = getattr(view_cls, 'queryset', None)
|
||||
model = getattr(getattr(view_cls, 'queryset', None), 'model', None)
|
||||
|
||||
for variable in sorted(uritemplate.variables(path)):
|
||||
model, model_field = get_queryset_field(queryset, variable)
|
||||
|
||||
@@ -276,7 +276,10 @@ class SerializerInspector(FieldInspector):
|
||||
|
||||
|
||||
class ViewInspector(BaseInspector):
|
||||
body_methods = ('PUT', 'PATCH', 'POST') #: methods that are allowed to have a request body
|
||||
body_methods = ('PUT', 'PATCH', 'POST', 'DELETE') #: methods that are allowed to have a request body
|
||||
|
||||
#: methods that are assumed to require a request body determined by the view's ``serializer_class``
|
||||
implicit_body_methods = ('PUT', 'PATCH', 'POST')
|
||||
|
||||
# real values set in __init__ to prevent import errors
|
||||
field_inspectors = [] #:
|
||||
|
||||
@@ -281,6 +281,10 @@ def find_limits(field):
|
||||
if attr not in limits or improves(limit_value, limits[attr]):
|
||||
limits[attr] = limit_value
|
||||
|
||||
if hasattr(field, "allow_blank") and not field.allow_blank:
|
||||
if limits.get('min_length', 0) < 1:
|
||||
limits['min_length'] = 1
|
||||
|
||||
return OrderedDict(sorted(limits.items()))
|
||||
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
if isinstance(body_override, openapi.Schema.OR_REF):
|
||||
return body_override
|
||||
return force_serializer_instance(body_override)
|
||||
elif self.method in self.body_methods:
|
||||
elif self.method in self.implicit_body_methods:
|
||||
return self.get_view_serializer()
|
||||
|
||||
return None
|
||||
@@ -144,8 +144,11 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
raise SwaggerGenerationError("specify the body parameter as a Schema or Serializer in request_body")
|
||||
if any(param.in_ == openapi.IN_FORM for param in manual_parameters): # pragma: no cover
|
||||
if any(param.in_ == openapi.IN_BODY for param in parameters.values()):
|
||||
raise SwaggerGenerationError("cannot add form parameters when the request has a request schema; "
|
||||
raise SwaggerGenerationError("cannot add form parameters when the request has a request body; "
|
||||
"did you forget to set an appropriate parser class on the view?")
|
||||
if self.method not in self.body_methods:
|
||||
raise SwaggerGenerationError("form parameters can only be applied to (" + ','.join(self.body_methods) +
|
||||
") HTTP methods")
|
||||
|
||||
parameters.update(param_list_to_odict(manual_parameters))
|
||||
return list(parameters.values())
|
||||
|
||||
@@ -116,7 +116,7 @@ class SwaggerDict(OrderedDict):
|
||||
which would result in the extra attributes being added first. For this reason, we defer the insertion of the
|
||||
attributes and require that subclasses call ._insert_extras__ at the end of their __init__ method.
|
||||
"""
|
||||
for attr, val in self._extras__.items():
|
||||
for attr, val in sorted(self._extras__.items()):
|
||||
setattr(self, attr, val)
|
||||
|
||||
@staticmethod
|
||||
@@ -221,7 +221,7 @@ 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 dict[str,dict[str,str]] 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
|
||||
|
||||
@@ -93,6 +93,7 @@ class _UIRenderer(BaseRenderer):
|
||||
'defaultModelRendering': swagger_settings.DEFAULT_MODEL_RENDERING,
|
||||
'defaultModelExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
|
||||
'defaultModelsExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
|
||||
'showCommonExtensions': swagger_settings.SHOW_COMMON_EXTENSIONS,
|
||||
'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
|
||||
'supportedSubmitMethods': swagger_settings.SUPPORTED_SUBMIT_METHODS,
|
||||
}
|
||||
|
||||
+38
-61
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
@@ -66,7 +66,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
||||
|
||||
: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
|
||||
:param list[dict] security: security requirements override; used to specify which authetication mechanism
|
||||
is requried to call this API; an empty list marks the endpoint as unauthenticated (i.e. removes all accepted
|
||||
authentication schemes), and ``None`` will inherit the top-level secuirty requirements
|
||||
:param dict[str,(.Schema,.SchemaRef,.Response,str,Serializer)] responses: a dict of documented manual responses
|
||||
|
||||
+35
-12
@@ -33,6 +33,21 @@ class SnippetList(generics.ListCreateAPIView):
|
||||
"""post method docstring"""
|
||||
return super(SnippetList, self).post(request, *args, **kwargs)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_id='snippets_delete_bulk',
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'body': openapi.Schema(
|
||||
type=openapi.TYPE_STRING,
|
||||
description='this should not crash (request body on DELETE method)'
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
def delete(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
@@ -56,18 +71,26 @@ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""patch method docstring"""
|
||||
return super(SnippetDetail, self).patch(request, *args, **kwargs)
|
||||
|
||||
@swagger_auto_schema(manual_parameters=[
|
||||
openapi.Parameter(
|
||||
name='id', in_=openapi.IN_PATH,
|
||||
type=openapi.TYPE_INTEGER,
|
||||
description="path parameter override",
|
||||
required=True
|
||||
),
|
||||
], responses={
|
||||
status.HTTP_204_NO_CONTENT: openapi.Response(
|
||||
description="This should not crash"
|
||||
)
|
||||
})
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
name='id', in_=openapi.IN_PATH,
|
||||
type=openapi.TYPE_INTEGER,
|
||||
description="path parameter override",
|
||||
required=True
|
||||
),
|
||||
openapi.Parameter(
|
||||
name='delete_form_param', in_=openapi.IN_FORM,
|
||||
type=openapi.TYPE_INTEGER,
|
||||
description="this should not crash (form parameter on DELETE method)"
|
||||
),
|
||||
],
|
||||
responses={
|
||||
status.HTTP_204_NO_CONTENT: openapi.Response(
|
||||
description="this should not crash (response object with no schema)"
|
||||
)
|
||||
}
|
||||
)
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""delete method docstring"""
|
||||
return super(SnippetDetail, self).patch(request, *args, **kwargs)
|
||||
|
||||
@@ -94,7 +94,18 @@ SWAGGER_SETTINGS = {
|
||||
'LOGIN_URL': '/admin/login',
|
||||
'LOGOUT_URL': '/admin/logout',
|
||||
|
||||
'DEFAULT_INFO': 'testproj.urls.swagger_info'
|
||||
'DEFAULT_INFO': 'testproj.urls.swagger_info',
|
||||
|
||||
'SECURITY_DEFINITIONS': {
|
||||
'Basic': {
|
||||
'type': 'basic'
|
||||
},
|
||||
'Bearer': {
|
||||
'type': 'apiKey',
|
||||
'name': 'Authorization',
|
||||
'in': 'header'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
|
||||
+50
-3
@@ -21,10 +21,15 @@ consumes:
|
||||
produces:
|
||||
- application/json
|
||||
securityDefinitions:
|
||||
basic:
|
||||
Basic:
|
||||
type: basic
|
||||
Bearer:
|
||||
in: header
|
||||
name: Authorization
|
||||
type: apiKey
|
||||
security:
|
||||
- basic: []
|
||||
- Basic: []
|
||||
- Bearer: []
|
||||
paths:
|
||||
/articles/:
|
||||
get:
|
||||
@@ -202,6 +207,7 @@ paths:
|
||||
type: string
|
||||
pattern: ^69$
|
||||
default: '69'
|
||||
minLength: 1
|
||||
- name: image_styles
|
||||
in: formData
|
||||
description: Parameter with Items
|
||||
@@ -382,6 +388,24 @@ paths:
|
||||
$ref: '#/definitions/Snippet'
|
||||
tags:
|
||||
- snippets
|
||||
delete:
|
||||
operationId: snippetsDeleteBulk
|
||||
description: SnippetList classdoc
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
body:
|
||||
description: this should not crash (request body on DELETE method)
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
tags:
|
||||
- snippets
|
||||
parameters: []
|
||||
/snippets/{id}/:
|
||||
get:
|
||||
@@ -436,9 +460,13 @@ paths:
|
||||
description: path parameter override
|
||||
required: true
|
||||
type: integer
|
||||
- name: delete_form_param
|
||||
in: formData
|
||||
description: this should not crash (form parameter on DELETE method)
|
||||
type: integer
|
||||
responses:
|
||||
'204':
|
||||
description: This should not crash
|
||||
description: this should not crash (response object with no schema)
|
||||
tags:
|
||||
- snippets
|
||||
parameters:
|
||||
@@ -700,6 +728,7 @@ paths:
|
||||
description: this field is generated from a query_serializer
|
||||
required: false
|
||||
type: string
|
||||
minLength: 1
|
||||
- name: is_staff
|
||||
in: query
|
||||
description: this one too!
|
||||
@@ -800,6 +829,7 @@ definitions:
|
||||
description: title model help_text
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
author:
|
||||
description: The ID of the user that created this article; if none is provided,
|
||||
defaults to the currently logged in user.
|
||||
@@ -809,6 +839,7 @@ definitions:
|
||||
description: body serializer help_text
|
||||
type: string
|
||||
maxLength: 5000
|
||||
minLength: 1
|
||||
slug:
|
||||
description: slug model help_text
|
||||
type: string
|
||||
@@ -830,6 +861,7 @@ definitions:
|
||||
description: but i needed to test these 2 fields somehow
|
||||
type: string
|
||||
format: uri
|
||||
minLength: 1
|
||||
readOnly: true
|
||||
uuid:
|
||||
description: should articles have UUIDs?
|
||||
@@ -872,10 +904,12 @@ definitions:
|
||||
title: FirstName
|
||||
type: string
|
||||
maxLength: 30
|
||||
minLength: 1
|
||||
lastName:
|
||||
title: LastName
|
||||
type: string
|
||||
maxLength: 30
|
||||
minLength: 1
|
||||
Person:
|
||||
required:
|
||||
- identity
|
||||
@@ -897,10 +931,12 @@ definitions:
|
||||
title: Project name
|
||||
description: Name of the project
|
||||
type: string
|
||||
minLength: 1
|
||||
githubRepo:
|
||||
title: Github repo
|
||||
description: Github repository of the project
|
||||
type: string
|
||||
minLength: 1
|
||||
Snippet:
|
||||
required:
|
||||
- code
|
||||
@@ -922,6 +958,7 @@ definitions:
|
||||
description: The ID of the user that created this snippet.
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
title: Owner as string
|
||||
title:
|
||||
title: Title
|
||||
@@ -930,6 +967,7 @@ definitions:
|
||||
code:
|
||||
title: Code
|
||||
type: string
|
||||
minLength: 1
|
||||
linenos:
|
||||
title: Linenos
|
||||
type: boolean
|
||||
@@ -1448,6 +1486,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
TodoAnother:
|
||||
required:
|
||||
- title
|
||||
@@ -1458,6 +1497,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
todo:
|
||||
$ref: '#/definitions/Todo'
|
||||
TodoRecursive:
|
||||
@@ -1473,6 +1513,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
parent:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
parent_id:
|
||||
@@ -1492,6 +1533,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
children:
|
||||
type: array
|
||||
items:
|
||||
@@ -1505,6 +1547,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
todo:
|
||||
required:
|
||||
- title
|
||||
@@ -1518,6 +1561,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
todo:
|
||||
required:
|
||||
- title
|
||||
@@ -1531,6 +1575,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
readOnly: true
|
||||
readOnly: true
|
||||
UserSerializerrr:
|
||||
@@ -1551,6 +1596,7 @@ definitions:
|
||||
type: string
|
||||
pattern: ^[\w.@+-]+$
|
||||
maxLength: 150
|
||||
minLength: 1
|
||||
email:
|
||||
title: Email address
|
||||
type: string
|
||||
@@ -1572,6 +1618,7 @@ definitions:
|
||||
type: string
|
||||
format: ipv4
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
last_connected_at:
|
||||
title: Last connected at
|
||||
description: really?
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from collections import OrderedDict
|
||||
from random import shuffle
|
||||
|
||||
from drf_yasg import openapi
|
||||
|
||||
|
||||
@@ -51,3 +54,15 @@ def test_trailing_underscore_stripped():
|
||||
del sd.in_
|
||||
assert 'in' not in sd
|
||||
assert not hasattr(sd, 'in__')
|
||||
|
||||
|
||||
def test_extra_ordering():
|
||||
"""Insertion order should also be consistent when setting undeclared parameters (kwargs) in SwaggerDict"""
|
||||
extras = [('beta', 1), ('alpha', 2), ('omega', 3), ('gamma', 4)]
|
||||
shuffled_extras = list(extras)
|
||||
shuffle(shuffled_extras)
|
||||
|
||||
s1 = openapi.SwaggerDict(**OrderedDict(extras))
|
||||
s2 = openapi.SwaggerDict(**OrderedDict(shuffled_extras))
|
||||
|
||||
assert list(s1.items()) == list(s2.items())
|
||||
|
||||
Reference in New Issue
Block a user