Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 713c669e28 | |||
| 2545ae9657 | |||
| f03d9d71e9 | |||
| a993cba7aa | |||
| bfd88cbdb4 | |||
| 123a05c82c | |||
| 7065429d47 | |||
| bd727fbe88 | |||
| 698a175a1b | |||
| 5b225423ae | |||
| 209201b9a5 | |||
| ca00ed35be | |||
| 4e7fa28744 | |||
| 979ec84630 |
+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
|
||||
-
|
||||
|
||||
@@ -353,6 +353,12 @@ Integration with `djangorestframework-camel-case <https://github.com/vbabiy/djan
|
||||
provided out of the box - if you have ``djangorestframework-camel-case`` installed and your ``APIView`` uses
|
||||
``CamelCaseJSONParser`` or ``CamelCaseJSONRenderer``, all property names will be converted to *camelCase* by default.
|
||||
|
||||
djangorestframework-recursive
|
||||
===============================
|
||||
|
||||
Integration with `djangorestframework-recursive <https://github.com/heywbj/django-rest-framework-recursive>`_ is
|
||||
provided out of the box - if you have ``djangorestframework-recursive`` installed.
|
||||
|
||||
.. |travis| image:: https://img.shields.io/travis/axnsan12/drf-yasg/master.svg
|
||||
:target: https://travis-ci.org/axnsan12/drf-yasg
|
||||
:alt: Travis CI
|
||||
|
||||
@@ -3,6 +3,42 @@ Changelog
|
||||
#########
|
||||
|
||||
|
||||
*********
|
||||
**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**
|
||||
*********
|
||||
|
||||
*Release date: Apr 27, 2018*
|
||||
|
||||
- **ADDED:** added integration with `djangorestframework-recursive <https://github.com/heywbj/django-rest-framework-recursive>`_
|
||||
(:issue:`109`, :pr:`110`, thanks to :ghuser:`rsichny`)
|
||||
|
||||
*NOTE:* in order for this to work, you will have to add the new ``drf_yasg.inspectors.RecursiveFieldInspector`` to
|
||||
your ``DEFAULT_FIELD_INSPECTORS`` array if you changed it from the default value
|
||||
|
||||
- **FIXED:** ``SchemaRef`` now supports cyclical references via the ``ignore_unresolved`` argument
|
||||
|
||||
*********
|
||||
**1.6.2**
|
||||
*********
|
||||
|
||||
+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.
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ to this list.
|
||||
:class:`'drf_yasg.inspectors.FileFieldInspector' <.inspectors.FileFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.HiddenFieldInspector' <.inspectors.HiddenFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.RecursiveFieldInspector' <.inspectors.RecursiveFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.SimpleFieldInspector' <.inspectors.SimpleFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.StringDefaultFieldInspector' <.inspectors.StringDefaultFieldInspector>`, |br| \
|
||||
``]``
|
||||
@@ -254,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
+414
-414
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "drf-yasg",
|
||||
"dependencies": {
|
||||
"redoc": "^2.0.0-alpha.17",
|
||||
"swagger-ui-dist": "^3.13.6"
|
||||
"swagger-ui-dist": "^3.14.1"
|
||||
},
|
||||
"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
|
||||
|
||||
@@ -5,5 +5,6 @@ django-cors-headers>=2.1.0
|
||||
django-filter>=1.1.0,<2.0; python_version == "2.7"
|
||||
django-filter>=1.1.0; python_version >= "3.4"
|
||||
djangorestframework-camel-case>=0.2.0
|
||||
djangorestframework-recursive>=0.1.2
|
||||
dj-database-url>=0.4.2
|
||||
user_agents>=1.1.0
|
||||
|
||||
@@ -6,12 +6,13 @@ SWAGGER_DEFAULTS = {
|
||||
|
||||
'DEFAULT_FIELD_INSPECTORS': [
|
||||
'drf_yasg.inspectors.CamelCaseJSONFilter',
|
||||
'drf_yasg.inspectors.RecursiveFieldInspector',
|
||||
'drf_yasg.inspectors.ReferencingSerializerInspector',
|
||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||
'drf_yasg.inspectors.ChoiceFieldInspector',
|
||||
'drf_yasg.inspectors.FileFieldInspector',
|
||||
'drf_yasg.inspectors.DictFieldInspector',
|
||||
'drf_yasg.inspectors.HiddenFieldInspector',
|
||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||
'drf_yasg.inspectors.SimpleFieldInspector',
|
||||
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
||||
],
|
||||
@@ -44,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': [
|
||||
|
||||
@@ -204,9 +204,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:
|
||||
@@ -401,7 +407,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)
|
||||
|
||||
@@ -4,8 +4,8 @@ from .base import (
|
||||
)
|
||||
from .field import (
|
||||
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
||||
InlineSerializerInspector, ReferencingSerializerInspector, RelatedFieldInspector, SimpleFieldInspector,
|
||||
StringDefaultFieldInspector
|
||||
InlineSerializerInspector, RecursiveFieldInspector, ReferencingSerializerInspector, RelatedFieldInspector,
|
||||
SimpleFieldInspector, StringDefaultFieldInspector
|
||||
)
|
||||
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
||||
from .view import SwaggerAutoSchema
|
||||
@@ -23,9 +23,9 @@ __all__ = [
|
||||
'CoreAPICompatInspector', 'DjangoRestResponsePagination',
|
||||
|
||||
# field inspectors
|
||||
'InlineSerializerInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector', 'SimpleFieldInspector',
|
||||
'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector', 'StringDefaultFieldInspector',
|
||||
'CamelCaseJSONFilter', 'HiddenFieldInspector',
|
||||
'InlineSerializerInspector', 'RecursiveFieldInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector',
|
||||
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector',
|
||||
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector',
|
||||
|
||||
# view inspectors
|
||||
'SwaggerAutoSchema',
|
||||
|
||||
@@ -10,7 +10,7 @@ from rest_framework.settings import api_settings as rest_framework_settings
|
||||
|
||||
from .. import openapi
|
||||
from ..errors import SwaggerGenerationError
|
||||
from ..utils import decimal_as_float, filter_none
|
||||
from ..utils import decimal_as_float, filter_none, get_serializer_ref_name
|
||||
from .base import FieldInspector, NotHandled, SerializerInspector
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -55,23 +55,12 @@ class InlineSerializerInspector(SerializerInspector):
|
||||
if swagger_object_type != openapi.Schema:
|
||||
raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)
|
||||
|
||||
serializer = field
|
||||
serializer_meta = getattr(serializer, 'Meta', None)
|
||||
serializer_name = type(serializer).__name__
|
||||
if hasattr(serializer_meta, 'ref_name'):
|
||||
ref_name = serializer_meta.ref_name
|
||||
elif serializer_name == 'NestedSerializer' and isinstance(serializer, serializers.ModelSerializer):
|
||||
logger.debug("Forcing inline output for ModelSerializer named 'NestedSerializer': " + str(serializer))
|
||||
ref_name = None
|
||||
else:
|
||||
ref_name = serializer_name
|
||||
if ref_name.endswith('Serializer'):
|
||||
ref_name = ref_name[:-len('Serializer')]
|
||||
ref_name = get_serializer_ref_name(field)
|
||||
|
||||
def make_schema_definition():
|
||||
properties = OrderedDict()
|
||||
required = []
|
||||
for property_name, child in serializer.fields.items():
|
||||
for property_name, child in field.fields.items():
|
||||
property_name = self.get_property_name(property_name)
|
||||
prop_kwargs = {
|
||||
'read_only': child.read_only or None
|
||||
@@ -292,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()))
|
||||
|
||||
|
||||
@@ -531,3 +524,24 @@ else:
|
||||
return camelize_schema(result, self.components)
|
||||
|
||||
return result
|
||||
|
||||
try:
|
||||
from rest_framework_recursive.fields import RecursiveField
|
||||
except ImportError:
|
||||
class RecursiveFieldInspector(FieldInspector):
|
||||
"""Provides conversion for RecursiveField (https://github.com/heywbj/django-rest-framework-recursive)"""
|
||||
pass
|
||||
else:
|
||||
class RecursiveFieldInspector(FieldInspector):
|
||||
"""Provides conversion for RecursiveField (https://github.com/heywbj/django-rest-framework-recursive)"""
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||
if isinstance(field, RecursiveField) and swagger_object_type == openapi.Schema:
|
||||
assert use_references is True, "Can not create schema for RecursiveField when use_references is False"
|
||||
|
||||
ref_name = get_serializer_ref_name(field.proxied)
|
||||
assert ref_name is not None, "Can not create RecursiveField schema for inline ModelSerializer"
|
||||
|
||||
return openapi.SchemaRef(self.components.with_scope(openapi.SCHEMA_DEFINITIONS), ref_name,
|
||||
ignore_unresolved=True)
|
||||
|
||||
return NotHandled
|
||||
|
||||
+12
-9
@@ -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
|
||||
@@ -437,7 +437,7 @@ class Schema(SwaggerDict):
|
||||
super(Schema, self).__init__(**extra)
|
||||
if required is True or required is False:
|
||||
# common error
|
||||
raise AssertionError("the `requires` attribute of schema must be an "
|
||||
raise AssertionError("the `required` attribute of schema must be an "
|
||||
"array of required property names, not a boolean!")
|
||||
assert type, "type is required!"
|
||||
self.title = title
|
||||
@@ -466,7 +466,7 @@ class Schema(SwaggerDict):
|
||||
class _Ref(SwaggerDict):
|
||||
ref_name_re = re.compile(r"#/(?P<scope>.+)/(?P<name>[^/]+)$")
|
||||
|
||||
def __init__(self, resolver, name, scope, expected_type):
|
||||
def __init__(self, resolver, name, scope, expected_type, ignore_unresolved=False):
|
||||
"""Base class for all reference types. A reference object has only one property, ``$ref``, which must be a JSON
|
||||
reference to a valid object in the specification, e.g. ``#/definitions/Article`` to refer to an article model.
|
||||
|
||||
@@ -474,13 +474,15 @@ class _Ref(SwaggerDict):
|
||||
:param str name: referenced object name, e.g. "Article"
|
||||
:param str scope: reference scope, e.g. "definitions"
|
||||
:param type[.SwaggerDict] expected_type: the expected type that will be asserted on the object found in resolver
|
||||
:param bool ignore_unresolved: allow the reference to be not defined in resolver
|
||||
"""
|
||||
super(_Ref, self).__init__()
|
||||
assert not type(self) == _Ref, "do not instantiate _Ref directly"
|
||||
ref_name = "#/{scope}/{name}".format(scope=scope, name=name)
|
||||
obj = resolver.get(name, scope)
|
||||
assert isinstance(obj, expected_type), ref_name + " is a {actual}, not a {expected}" \
|
||||
.format(actual=type(obj).__name__, expected=expected_type.__name__)
|
||||
if not ignore_unresolved:
|
||||
obj = resolver.get(name, scope)
|
||||
assert isinstance(obj, expected_type), ref_name + " is a {actual}, not a {expected}" \
|
||||
.format(actual=type(obj).__name__, expected=expected_type.__name__)
|
||||
self.ref = ref_name
|
||||
|
||||
def resolve(self, resolver):
|
||||
@@ -502,14 +504,15 @@ class _Ref(SwaggerDict):
|
||||
|
||||
|
||||
class SchemaRef(_Ref):
|
||||
def __init__(self, resolver, schema_name):
|
||||
def __init__(self, resolver, schema_name, ignore_unresolved=False):
|
||||
"""Adds a reference to a named Schema defined in the ``#/definitions/`` object.
|
||||
|
||||
:param .ReferenceResolver resolver: component resolver which must contain the definition
|
||||
:param str schema_name: schema name
|
||||
:param bool ignore_unresolved: allow the reference to be not defined in resolver
|
||||
"""
|
||||
assert SCHEMA_DEFINITIONS in resolver.scopes
|
||||
super(SchemaRef, self).__init__(resolver, schema_name, SCHEMA_DEFINITIONS, Schema)
|
||||
super(SchemaRef, self).__init__(resolver, schema_name, SCHEMA_DEFINITIONS, Schema, ignore_unresolved)
|
||||
|
||||
|
||||
Schema.OR_REF = (Schema, SchemaRef)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
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
@@ -1,18 +0,0 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'drf-yasg/style.css' %}"/>
|
||||
</head>
|
||||
<body>
|
||||
<script id="redoc-settings" type="application/json">{{ redoc_settings | safe }}</script>
|
||||
|
||||
<script src="{% static 'drf-yasg/insQ.min.js' %}"></script>
|
||||
<script src="{% static 'drf-yasg/redoc-init.js' %}"> </script>
|
||||
<script src="{% static 'drf-yasg/redoc-alpha/redoc.standalone.js' %}"> </script>
|
||||
</body>
|
||||
</html>
|
||||
+23
-1
@@ -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
|
||||
@@ -295,3 +295,25 @@ def decimal_as_float(field):
|
||||
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
|
||||
|
||||
|
||||
def get_serializer_ref_name(serializer):
|
||||
"""
|
||||
Get serializer's ref_name (or None for ModelSerializer if it is named 'NestedSerializer')
|
||||
|
||||
:param serializer: Serializer instance
|
||||
:return: Serializer's ref_name or None for inline serializer
|
||||
:rtype: str or None
|
||||
"""
|
||||
serializer_meta = getattr(serializer, 'Meta', None)
|
||||
serializer_name = type(serializer).__name__
|
||||
if hasattr(serializer_meta, 'ref_name'):
|
||||
ref_name = serializer_meta.ref_name
|
||||
elif serializer_name == 'NestedSerializer' and isinstance(serializer, serializers.ModelSerializer):
|
||||
logger.debug("Forcing inline output for ModelSerializer named 'NestedSerializer': " + str(serializer))
|
||||
ref_name = None
|
||||
else:
|
||||
ref_name = serializer_name
|
||||
if ref_name.endswith('Serializer'):
|
||||
ref_name = ref_name[:-len('Serializer')]
|
||||
return ref_name
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 2.0.4 on 2018-04-26 13:06
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('todo', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TodoTree',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=50)),
|
||||
('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='children', to='todo.TodoTree')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -13,3 +13,8 @@ class TodoAnother(models.Model):
|
||||
class TodoYetAnother(models.Model):
|
||||
todo = models.ForeignKey(TodoAnother, on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=50)
|
||||
|
||||
|
||||
class TodoTree(models.Model):
|
||||
parent = models.ForeignKey('self', on_delete=models.CASCADE, related_name='children', null=True)
|
||||
title = models.CharField(max_length=50)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers
|
||||
from rest_framework_recursive.fields import RecursiveField
|
||||
|
||||
from .models import Todo, TodoAnother, TodoYetAnother
|
||||
from .models import Todo, TodoAnother, TodoTree, TodoYetAnother
|
||||
|
||||
|
||||
class TodoSerializer(serializers.ModelSerializer):
|
||||
@@ -25,3 +26,22 @@ class TodoYetAnotherSerializer(serializers.ModelSerializer):
|
||||
model = TodoYetAnother
|
||||
fields = ('title', 'todo')
|
||||
depth = 2
|
||||
|
||||
|
||||
class TodoTreeSerializer(serializers.ModelSerializer):
|
||||
children = serializers.ListField(child=RecursiveField(), source='children.all')
|
||||
|
||||
class Meta:
|
||||
model = TodoTree
|
||||
fields = ('id', 'title', 'children')
|
||||
|
||||
|
||||
class TodoRecursiveSerializer(serializers.ModelSerializer):
|
||||
parent = RecursiveField(read_only=True)
|
||||
parent_id = serializers.PrimaryKeyRelatedField(queryset=TodoTree.objects.all(), pk_field=serializers.IntegerField(),
|
||||
write_only=True, allow_null=True, required=False, default=None,
|
||||
source='parent')
|
||||
|
||||
class Meta:
|
||||
model = TodoTree
|
||||
fields = ('id', 'title', 'parent', 'parent_id')
|
||||
|
||||
@@ -7,10 +7,12 @@ router = routers.DefaultRouter()
|
||||
router.register(r'', views.TodoViewSet)
|
||||
router.register(r'another', views.TodoAnotherViewSet)
|
||||
router.register(r'yetanother', views.TodoYetAnotherViewSet)
|
||||
router.register(r'tree', views.TodoTreeView)
|
||||
router.register(r'recursive', views.TodoRecursiveView)
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^(?P<todo_id>\d+)/yetanother/(?P<yetanother_id>\d+)/$',
|
||||
views.NestedTodoView.as_view(),),
|
||||
views.NestedTodoView.as_view(), ),
|
||||
]
|
||||
|
||||
+14
-2
@@ -1,8 +1,10 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
|
||||
from .models import Todo, TodoAnother, TodoYetAnother
|
||||
from .serializer import TodoAnotherSerializer, TodoSerializer, TodoYetAnotherSerializer
|
||||
from .models import Todo, TodoAnother, TodoTree, TodoYetAnother
|
||||
from .serializer import (
|
||||
TodoAnotherSerializer, TodoRecursiveSerializer, TodoSerializer, TodoTreeSerializer, TodoYetAnotherSerializer
|
||||
)
|
||||
|
||||
|
||||
class TodoViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
@@ -25,3 +27,13 @@ class TodoYetAnotherViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
class NestedTodoView(RetrieveAPIView):
|
||||
serializer_class = TodoYetAnotherSerializer
|
||||
|
||||
|
||||
class TodoTreeView(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = TodoTree.objects.all()
|
||||
serializer_class = TodoTreeSerializer
|
||||
|
||||
|
||||
class TodoRecursiveView(viewsets.ModelViewSet):
|
||||
queryset = TodoTree.objects.all()
|
||||
serializer_class = TodoRecursiveSerializer
|
||||
|
||||
+186
-2
@@ -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
|
||||
@@ -495,6 +501,129 @@ paths:
|
||||
description: A unique integer value identifying this todo another.
|
||||
required: true
|
||||
type: integer
|
||||
/todo/recursive/:
|
||||
get:
|
||||
operationId: todo_recursive_list
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
tags:
|
||||
- todo
|
||||
post:
|
||||
operationId: todo_recursive_create
|
||||
description: ''
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
tags:
|
||||
- todo
|
||||
parameters: []
|
||||
/todo/recursive/{id}/:
|
||||
get:
|
||||
operationId: todo_recursive_read
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
tags:
|
||||
- todo
|
||||
put:
|
||||
operationId: todo_recursive_update
|
||||
description: ''
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
tags:
|
||||
- todo
|
||||
patch:
|
||||
operationId: todo_recursive_partial_update
|
||||
description: ''
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
tags:
|
||||
- todo
|
||||
delete:
|
||||
operationId: todo_recursive_delete
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
tags:
|
||||
- todo
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
description: A unique integer value identifying this todo tree.
|
||||
required: true
|
||||
type: integer
|
||||
/todo/tree/:
|
||||
get:
|
||||
operationId: todo_tree_list
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TodoTree'
|
||||
tags:
|
||||
- todo
|
||||
parameters: []
|
||||
/todo/tree/{id}/:
|
||||
get:
|
||||
operationId: todo_tree_read
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/TodoTree'
|
||||
tags:
|
||||
- todo
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
description: A unique integer value identifying this todo tree.
|
||||
required: true
|
||||
type: integer
|
||||
/todo/yetanother/:
|
||||
get:
|
||||
operationId: todo_yetanother_list
|
||||
@@ -577,6 +706,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!
|
||||
@@ -677,6 +807,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.
|
||||
@@ -686,6 +817,7 @@ definitions:
|
||||
description: body serializer help_text
|
||||
type: string
|
||||
maxLength: 5000
|
||||
minLength: 1
|
||||
slug:
|
||||
description: slug model help_text
|
||||
type: string
|
||||
@@ -707,6 +839,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?
|
||||
@@ -749,10 +882,12 @@ definitions:
|
||||
title: FirstName
|
||||
type: string
|
||||
maxLength: 30
|
||||
minLength: 1
|
||||
lastName:
|
||||
title: LastName
|
||||
type: string
|
||||
maxLength: 30
|
||||
minLength: 1
|
||||
Person:
|
||||
required:
|
||||
- identity
|
||||
@@ -774,10 +909,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
|
||||
@@ -799,6 +936,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
|
||||
@@ -807,6 +945,7 @@ definitions:
|
||||
code:
|
||||
title: Code
|
||||
type: string
|
||||
minLength: 1
|
||||
linenos:
|
||||
title: Linenos
|
||||
type: boolean
|
||||
@@ -1325,6 +1464,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
TodoAnother:
|
||||
required:
|
||||
- title
|
||||
@@ -1335,8 +1475,47 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
todo:
|
||||
$ref: '#/definitions/Todo'
|
||||
TodoRecursive:
|
||||
required:
|
||||
- title
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
title:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
parent:
|
||||
$ref: '#/definitions/TodoRecursive'
|
||||
parent_id:
|
||||
type: integer
|
||||
title: Parent id
|
||||
TodoTree:
|
||||
required:
|
||||
- title
|
||||
- children
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
title:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
children:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TodoTree'
|
||||
TodoYetAnother:
|
||||
required:
|
||||
- title
|
||||
@@ -1346,6 +1525,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
todo:
|
||||
required:
|
||||
- title
|
||||
@@ -1359,6 +1539,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
todo:
|
||||
required:
|
||||
- title
|
||||
@@ -1372,6 +1553,7 @@ definitions:
|
||||
title: Title
|
||||
type: string
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
readOnly: true
|
||||
readOnly: true
|
||||
UserSerializerrr:
|
||||
@@ -1392,6 +1574,7 @@ definitions:
|
||||
type: string
|
||||
pattern: ^[\w.@+-]+$
|
||||
maxLength: 150
|
||||
minLength: 1
|
||||
email:
|
||||
title: Email address
|
||||
type: string
|
||||
@@ -1413,6 +1596,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())
|
||||
|
||||
@@ -65,7 +65,7 @@ known_standard_library =
|
||||
collections,copy,distutils,functools,inspect,io,json,logging,operator,os,pkg_resources,re,setuptools,sys,
|
||||
types,warnings
|
||||
known_third_party =
|
||||
coreapi,coreschema,datadiff,dj_database_url,django,django_filters,djangorestframework_camel_case,flex,gunicorn,
|
||||
inflection,pygments,pytest,rest_framework,ruamel,setuptools_scm,swagger_spec_validator,uritemplate,user_agents,
|
||||
whitenoise
|
||||
coreapi,coreschema,datadiff,dj_database_url,django,django_filters,djangorestframework_camel_case,
|
||||
rest_framework_recursive,flex,gunicorn,inflection,pygments,pytest,rest_framework,ruamel,setuptools_scm,
|
||||
swagger_spec_validator,uritemplate,user_agents,whitenoise
|
||||
known_first_party = drf_yasg,testproj,articles,people,snippets,todo,users,urlconfs
|
||||
|
||||
Reference in New Issue
Block a user