Compare commits

..

34 Commits

Author SHA1 Message Date
Cristi Vijdea bc931677dc Add 1.16.1 changelog 2019-07-16 20:59:36 +03:00
tfranzel 1904b0499e Fix imports for DRF 3.10 (#408)
Fixes #410
Fixes #411
2019-07-16 20:39:44 +03:00
johnthagen 1e380fe68b Fix variable formatting (#406) 2019-07-15 12:14:37 +03:00
Étienne Noss 6417bb3770 Handle enum type for nested ChoiceFields (#400) 2019-07-15 12:14:23 +03:00
Cristi Vîjdea e9f27442fc Remove unused import 2019-06-13 16:58:41 +03:00
Cristi Vîjdea cf8b912c10 Align tox matrix to python version 2019-06-13 16:57:15 +03:00
Cristi Vîjdea 3a37c4a019 Drop Django 2.0 and DRF 3.7 support 2019-06-13 16:49:46 +03:00
Cristi Vîjdea 8acab171ea Fix isort
[ci skip]
2019-06-13 16:43:16 +03:00
Cristi Vîjdea acc204e4ea Add test for #382 2019-06-13 16:34:23 +03:00
Cristi Vîjdea 1635e5e095 Add 1.16.0 changelog 2019-06-13 13:48:45 +03:00
Cristi Vîjdea 753be1a8bd Fix potential issubclass crash 2019-06-13 13:43:16 +03:00
Pavel White 2656696a0f generators: support custom ReferenceResolver class (#350) 2019-06-13 03:38:17 +03:00
Hugo Duroux a083d3cf7c Add operation_keys to SwaggerAutoSchema constructor (#355) 2019-06-13 03:37:06 +03:00
Cristi Vîjdea eed8a8d3ec Add 1.15.1 changelog 2019-06-13 03:23:25 +03:00
Cristi Vîjdea d04f27f40f Add test for serializer field label
Closes #353.
2019-06-13 03:15:24 +03:00
Cristi Vîjdea db154d196a Drop Python 3.4 support 2019-06-13 02:37:04 +03:00
Cristi Vîjdea 60e1346150 Refactor setup.py python_requires 2019-06-13 02:36:43 +03:00
Cristi Vîjdea 69b628a7af Fix python 3.7 typing
Closes #371
2019-06-13 02:26:36 +03:00
Cristi Vîjdea 64d9d42aa9 Remove pygments test dependency 2019-06-13 01:54:22 +03:00
Cristi Vîjdea e9d5344de3 Update swagger-ui to 3.22.3 and ReDoc to 2.0.0-rc.8-1 2019-06-13 01:28:31 +03:00
elliott-omosheye b5aba7243d Fix unicode attribute error if typing installed on py2.7 (#363) 2019-06-13 00:51:47 +03:00
Terence Honles 91ef83e830 update reference.yaml based on pygments lexers & styles changes (#364)
Snippet model in testproj/snippets/models.py has field choices which depend
on ``pygments.lexers`` and ``pygments.styles``
2019-06-13 00:51:23 +03:00
raksa 0991c806c7 Fixed typo (#361) 2019-05-16 00:10:12 +03:00
johnthagen 59e86ff72f Support Django 2.2 (#346) 2019-04-05 23:32:33 +03:00
Cristi Vijdea 548489a539 Add missing migration for test 2019-04-01 03:29:10 +03:00
Cristi Vijdea 887b53300a Add 1.15.0 changelog 2019-04-01 03:29:03 +03:00
Joe Korbel f692fe7c98 Update openapi.rst (#327)
Minor typo on "because"
2019-04-01 03:21:16 +03:00
Cristi Vijdea 017ae3d240 Update swagger-ui to 3.22.0 and ReDoc to 2.0.0-rc.4 2019-04-01 03:19:26 +03:00
Cristi Vijdea b57413023b Fix isort 2019-04-01 02:58:42 +03:00
Cristi Vijdea 4014c69689 Add test for #340 2019-04-01 02:58:07 +03:00
Cristi Vijdea 7bb4700003 Apply to_representation on choices
Fixes #340
2019-04-01 02:58:07 +03:00
Dimas Ari 86c1675c58 make sure all ListModelMixin views considered as list view (#306)
* make sure all ListModelMixin views considered as list view
* test to make sure all ListModelMixin views considered as list view added
2019-04-01 02:57:14 +03:00
AndreaFox93 81f0b1a2ea Use getattr for 'help_text' (#342) 2019-04-01 02:24:00 +03:00
Cristi Vijdea 298a9745df Add is_list_view and has_list_response methods 2019-03-22 14:27:15 +02:00
44 changed files with 1480 additions and 755 deletions
+1
View File
@@ -3,6 +3,7 @@ testproj/db.sqlite3
testproj/staticfiles
\.pytest_cache/
docs/\.doctrees/
pip-wheel-metadata/
# Created by .ignore support plugin (hsz.mobi)
### Python template
+2 -1
View File
@@ -19,6 +19,7 @@
<excludeFolder url="file://$MODULE_DIR$/.cache" />
<excludeFolder url="file://$MODULE_DIR$/.eggs" />
<excludeFolder url="file://$MODULE_DIR$/.pytest_cache" />
<excludeFolder url="file://$MODULE_DIR$/.tox" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/docs/.doctrees" />
@@ -44,4 +45,4 @@
<option name="projectConfiguration" value="pytest" />
<option name="PROJECT_TEST_RUNNER" value="pytest" />
</component>
</module>
</module>
-1
View File
@@ -1,7 +1,6 @@
language: python
python:
- '2.7'
- '3.4'
- '3.5'
- '3.6'
- '3.7'
+1 -1
View File
@@ -95,7 +95,7 @@ You want to contribute some code? Great! Here are a few steps to get you started
#. **Your code must pass all the required travis jobs before it is merged**
As of now, this consists of running on Python 2.7, 3.4, 3.5 and 3.6, and building the docs succesfully.
As of now, this consists of running on Python 2.7, 3.5, 3.6 and 3.7, and building the docs succesfully.
******************
Maintainer's notes
+4 -4
View File
@@ -13,9 +13,9 @@ Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framewor
Compatible with
- **Django Rest Framework**: 3.7.7, 3.8, 3.9
- **Django**: 1.11, 2.0, 2.1
- **Python**: 2.7, 3.4, 3.5, 3.6, 3.7
- **Django Rest Framework**: 3.8, 3.9
- **Django**: 1.11, 2.1, 2.2
- **Python**: 2.7, 3.5, 3.6, 3.7
Only the latest patch version of each ``major.minor`` series of Python, Django and Django REST Framework is supported.
@@ -235,7 +235,7 @@ Offline
^^^^^^^
If your schema is not accessible from the internet, you can run a local copy of
`swagger-validator <https://hub.docker.com/r/swaggerapi/swagger-validator/>`_ and set the `VALIDATOR_URL` accordingly:
`swagger-validator <https://hub.docker.com/r/swaggerapi/swagger-validator/>`_ and set the ``VALIDATOR_URL`` accordingly:
.. code:: python
+45
View File
@@ -2,6 +2,51 @@
Changelog
#########
**********
**1.16.1**
**********
*Release date: Jul 16, 2019*
- **FIXED:** fixed DRF 3.10 compatibility (:pr:`408`, :issue:`410`, :issue:`411`)
- **IMPROVED:** better enum type detection for nested `ChoiceField`s (:pr:`400`)
**********
**1.16.0**
**********
*Release date: Jun 13, 2019*
- **ADDED:** added `reference_resolver_class` attribute hook to `SwaggerAutoSchema` (:pr:`350`)
- **ADDED:** added `operation_keys` attribute to `SwaggerAutoSchema`, along with `__init__` parameter (:pr:`355`)
- **FIXED:** fixed potential crash on `issubclass` check without `isclass` check
**********
**1.15.1**
**********
*Release date: Jun 13, 2019*
- **IMPROVED:** updated ``swagger-ui`` to version 3.22.3
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-rc.8-1
- **FIXED:** fixed an issue with inspection of typing hints on Python 2.7 (:issue:`363`)
- **FIXED:** fixed an issue with inspection of typing hints on Python 3.7 (:issue:`371`)
**Python 3.4 support has been dropped!**
**********
**1.15.0**
**********
*Release date: Apr 01, 2019*
- **ADDED:** added ``is_list_view`` and ``has_list_response`` extension points to ``SwaggerAutoSchema`` (:issue:`331`)
- **IMPROVED:** updated ``swagger-ui`` to version 3.22.0
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-rc.4
- **FIXED:** ``ListModelMixin`` will now always be treated as a list view (:issue:`306`)
- **FIXED:** non-primtive values in field ``choices`` will now be handled properly (:issue:`340`)
**********
**1.14.0**
**********
+3 -3
View File
@@ -87,14 +87,14 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
* for ``ViewSet``, ``GenericViewSet``, ``ModelViewSet``, because each viewset corresponds to multiple **paths**, you have
to decorate the *action methods*, i.e. ``list``, ``create``, ``retrieve``, etc. |br|
Additionally, ``@action``\ s, ``@list_route``\ s or ``@detail_route``\ s defined on the viewset, like function based
api views, can respond to multiple HTTP methods and thus have multiple operations that must be decorated separately:
Additionally, ``@action``\ s defined on the viewset, like function based api views, can respond to multiple HTTP
methods and thus have multiple operations that must be decorated separately:
.. code-block:: python
class ArticleViewSet(viewsets.ModelViewSet):
# method or 'methods' can be skipped because the list_route only handles a single method (GET)
# method or 'methods' can be skipped because the action only handles a single method (GET)
@swagger_auto_schema(operation_description='GET /articles/today/')
@action(detail=False, methods=['get'])
def today(self, request):
+1 -1
View File
@@ -14,7 +14,7 @@ This library generates OpenAPI 2.0 documents. The authoritative specification fo
be the official documentation over at `swagger.io <https://swagger.io/>`__ and the `OpenAPI 2.0 specification
page <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__.
Beause the above specifications are a bit heavy and convoluted, here is a general overview of how the specification
Because the above specifications are a bit heavy and convoluted, here is a general overview of how the specification
is structured, starting from the root ``Swagger`` object.
* :class:`.Swagger` object
+2 -2
View File
@@ -340,7 +340,7 @@ values for Parameters.
OAUTH2_REDIRECT_URL
-------------------
Used when OAuth2 authenitcation of API requests via swagger-ui is desired. If ``None`` is passed, the
Used when OAuth2 authentication of API requests via swagger-ui is desired. If ``None`` is passed, the
``oauth2RedirectUrl`` parameter will be set to ``{% static 'drf-yasg/swagger-ui-dist/oauth2-redirect.html' %}``. This
is the default `https://github.com/swagger-api/swagger-ui/blob/master/dist/oauth2-redirect.html <oauth2-redirect>`_
file provided by ``swagger-ui``.
@@ -351,7 +351,7 @@ file provided by ``swagger-ui``.
OAUTH2_CONFIG
-------------
Used when OAuth2 authenitcation of API requests via swagger-ui is desired. Provides OAuth2 configuration parameters
Used when OAuth2 authentication 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>`_.
+844 -132
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,8 +1,8 @@
{
"name": "drf-yasg",
"dependencies": {
"redoc": "^2.0.0-rc.2",
"swagger-ui-dist": "^3.21.0"
"redoc": "^2.0.0-rc.8-1",
"swagger-ui-dist": "^3.22.3"
},
"repository": {
"type": "git",
+1 -1
View File
@@ -5,5 +5,5 @@ inflection>=0.3.1
six>=1.10.0
uritemplate>=3.0.0
djangorestframework>=3.7.7
djangorestframework>=3.8
Django>=1.11.7
+2
View File
@@ -5,5 +5,7 @@ pytest-cov>=2.6.0
pytest-xdist>=1.25.0
pytest-django>=3.4.4
datadiff==2.0.0
psycopg2-binary==2.8.3
django-fake-model==0.1.4
-r testproj.txt
+4 -3
View File
@@ -1,10 +1,11 @@
# test project requirements
Pillow>=4.3.0
pygments>=2.2.0
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
django-filter>=1.1.0; python_version >= "3.5"
#djangorestframework-camel-case>=0.2.0
# tempory replacement of broken lib
-e git+https://github.com/tfranzel/djangorestframework-camel-case.git@bd556d38fa7382acadfe91d93d92d99c663248a9#egg=djangorestframework_camel_case
djangorestframework-recursive>=0.1.2
dj-database-url>=0.4.2
user_agents>=1.1.0
+1 -1
View File
@@ -1 +1 @@
python-3.7.1
python-3.7.3
+15 -10
View File
@@ -19,6 +19,18 @@ with io.open('README.rst', encoding='utf-8') as readme:
requirements = read_req('base.txt')
requirements_validation = read_req('validation.txt')
py3_supported_range = (5, 7)
# convert inclusive range to exclusive range
py3_supported_range = (py3_supported_range[0], py3_supported_range[1] + 1)
python_requires = ", ".join([">=2.7"] + ["!=3.{}.*".format(v) for v in range(0, py3_supported_range[0])])
python_classifiers = [
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
] + ['Programming Language :: Python :: 3.{}'.format(v) for v in range(*py3_supported_range)]
def drf_yasg_setup(**kwargs):
setup(
@@ -38,28 +50,21 @@ def drf_yasg_setup(**kwargs):
author_email='cristi@cvjd.me',
keywords='drf django django-rest-framework schema swagger openapi codegen swagger-codegen '
'documentation drf-yasg django-rest-swagger drf-openapi',
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
python_requires=python_requires,
classifiers=[
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Development Status :: 5 - Production/Stable',
'Operating System :: OS Independent',
'Environment :: Web Environment',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Framework :: Django',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.0',
'Framework :: Django :: 2.1',
'Framework :: Django :: 2.2',
'Topic :: Documentation',
'Topic :: Software Development :: Code Generators',
],
] + python_classifiers,
**kwargs
)
+14 -4
View File
@@ -4,14 +4,23 @@ import re
from collections import OrderedDict, defaultdict
import uritemplate
import rest_framework
from coreapi.compat import urlparse
from rest_framework import versioning
from rest_framework.compat import URLPattern, URLResolver, get_original_route
from rest_framework.schemas.generators import EndpointEnumerator as _EndpointEnumerator
from rest_framework.schemas.generators import SchemaGenerator, endpoint_ordering, get_pk_name
from rest_framework.schemas.inspectors import get_pk_description
from rest_framework.schemas.generators import endpoint_ordering, get_pk_name
from rest_framework.settings import api_settings
from packaging.version import Version
if Version(rest_framework.__version__) < Version('3.10'):
from rest_framework.schemas.generators import SchemaGenerator
from rest_framework.schemas.inspectors import get_pk_description
else:
from rest_framework.schemas import SchemaGenerator
from rest_framework.schemas.utils import get_pk_description
from . import openapi
from .app_settings import swagger_settings
from .errors import SwaggerGenerationError
@@ -160,6 +169,7 @@ class OpenAPISchemaGenerator(object):
Method implementations shamelessly stolen and adapted from rest-framework ``SchemaGenerator``.
"""
endpoint_enumerator_class = EndpointEnumerator
reference_resolver_class = ReferenceResolver
def __init__(self, info, version='', url=None, patterns=None, urlconf=None):
"""
@@ -238,7 +248,7 @@ class OpenAPISchemaGenerator(object):
:rtype: openapi.Swagger
"""
endpoints = self.get_endpoints(request)
components = ReferenceResolver(openapi.SCHEMA_DEFINITIONS, force_init=True)
components = self.reference_resolver_class(openapi.SCHEMA_DEFINITIONS, force_init=True)
self.consumes = get_consumes(api_settings.DEFAULT_PARSER_CLASSES)
self.produces = get_produces(api_settings.DEFAULT_RENDERER_CLASSES)
paths, prefix = self.get_paths(endpoints, components, request, public)
@@ -440,7 +450,7 @@ class OpenAPISchemaGenerator(object):
if view_inspector_cls is None:
return None
view_inspector = view_inspector_cls(view, path, method, components, request, overrides)
view_inspector = view_inspector_cls(view, path, method, components, request, overrides, operation_keys)
operation = view_inspector.get_operation(operation_keys)
if operation is None:
return None
+24 -16
View File
@@ -272,7 +272,8 @@ class FieldInspector(BaseInspector):
assert not isinstance(field, openapi.SwaggerDict), "passed field is already a SwaggerDict object"
title = force_real_str(field.label) if field.label else None
title = title if swagger_object_type == openapi.Schema else None # only Schema has title
description = force_real_str(field.help_text) if field.help_text else None
help_text = getattr(field, 'help_text', None)
description = force_real_str(help_text) if help_text else None
description = description if swagger_object_type != openapi.Items else None # Items has no description either
def SwaggerType(existing_object=None, **instance_kwargs):
@@ -341,6 +342,9 @@ class ViewInspector(BaseInspector):
#: methods that are assumed to require a request body determined by the view's ``serializer_class``
implicit_body_methods = ('PUT', 'PATCH', 'POST')
#: methods which are assumed to return a list of objects when present on non-detail endpoints
implicit_list_response_methods = ('GET',)
# real values set in __init__ to prevent import errors
field_inspectors = [] #:
filter_inspectors = [] #:
@@ -374,20 +378,30 @@ class ViewInspector(BaseInspector):
"""
raise NotImplementedError("ViewInspector must implement get_operation()!")
# methods below provided as default implementations for probing inspectors
def is_list_view(self):
"""Determine whether this view is a list or a detail view. The difference between the two is that
detail views depend on a pk/id path parameter. Note that a non-detail view does not necessarily imply a list
reponse (:meth:`.has_list_response`), nor are list responses limited to non-detail views.
For example, one might have a `/topic/<pk>/posts` endpoint which is a detail view that has a list response.
:rtype: bool"""
return is_list_view(self.path, self.method, self.view)
def has_list_response(self):
"""Determine whether this view returns multiple objects. By default this is any non-detail view
(see :meth:`.is_list_view`) whose request method is one of :attr:`.implicit_list_response_methods`.
:rtype: bool
"""
return self.is_list_view() and (self.method.upper() in self.implicit_list_response_methods)
def should_filter(self):
"""Determine whether filter backend parameters should be included for this request.
:rtype: bool
"""
if not getattr(self.view, 'filter_backends', None):
return False
if self.method.lower() not in ["get", "delete"]:
return False
return is_list_view(self.path, self.method, self.view)
return getattr(self.view, 'filter_backends', None) and self.has_list_response()
def get_filter_parameters(self):
"""Return the parameters added to the view by its filter backends.
@@ -408,13 +422,7 @@ class ViewInspector(BaseInspector):
:rtype: bool
"""
if not getattr(self.view, 'paginator', None):
return False
if self.method.lower() != 'get':
return False
return is_list_view(self.path, self.method, self.view)
return getattr(self.view, 'paginator', None) and self.has_list_response()
def get_pagination_parameters(self):
"""Return the parameters added to the view by its paginator.
+25 -6
View File
@@ -2,6 +2,7 @@ import datetime
import inspect
import logging
import operator
import sys
import uuid
from collections import OrderedDict
from decimal import Decimal
@@ -13,7 +14,9 @@ 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, get_serializer_class, get_serializer_ref_name
from ..utils import (
decimal_as_float, field_value_to_representation, filter_none, get_serializer_class, get_serializer_ref_name
)
from .base import FieldInspector, NotHandled, SerializerInspector, call_view_method
try:
@@ -487,6 +490,10 @@ hinting_type_info = [
(datetime.date, (openapi.TYPE_STRING, openapi.FORMAT_DATE)),
]
if sys.version_info < (3, 0):
# noinspection PyUnresolvedReferences
hinting_type_info.append((unicode, (openapi.TYPE_STRING, None))) # noqa: F821
if typing:
def inspect_collection_hint_class(hint_class):
args = hint_class.__args__
@@ -525,11 +532,14 @@ def get_basic_type_info_from_hint(hint_class):
:rtype: OrderedDict
"""
union_types = _get_union_types(hint_class)
if typing and union_types:
# Optional is implemented as Union[T, None]
if len(union_types) == 2 and isinstance(None, union_types[1]):
result = get_basic_type_info_from_hint(union_types[0])
result['x-nullable'] = True
if result:
result['x-nullable'] = True
return result
return None
@@ -600,8 +610,6 @@ class SerializerMethodFieldInspector(FieldInspector):
# look for Python 3.5+ style type hinting of the return value
hint_class = inspect_signature(method).return_annotation
if not inspect.isclass(hint_class) and hasattr(hint_class, '__args__'):
hint_class = hint_class.__args__[0]
if inspect.isclass(hint_class) and not issubclass(hint_class, inspect._empty):
type_info = get_basic_type_info_from_hint(hint_class)
@@ -635,13 +643,24 @@ class ChoiceFieldInspector(FieldInspector):
if isinstance(field, serializers.ChoiceField):
enum_type = openapi.TYPE_STRING
enum_values = list(field.choices.keys())
enum_values = []
for choice in field.choices.keys():
if isinstance(field, serializers.MultipleChoiceField):
choice = field_value_to_representation(field, [choice])[0]
else:
choice = field_value_to_representation(field, choice)
enum_values.append(choice)
# for ModelSerializer, try to infer the type from the associated model field
serializer = get_parent_serializer(field)
if isinstance(serializer, serializers.ModelSerializer):
model = getattr(getattr(serializer, 'Meta'), 'model')
model_field = get_model_field(model, field.source)
# Use the parent source for nested fields
model_field = get_model_field(model, field.source or field.parent.source)
# If the field has a base_field its type must be used
if getattr(model_field, "base_field", None):
model_field = model_field.base_field
if model_field:
model_type = get_basic_type_info(model_field)
if model_type:
+13 -6
View File
@@ -9,7 +9,7 @@ from .. import openapi
from ..errors import SwaggerGenerationError
from ..utils import (
filter_none, force_real_str, force_serializer_instance, get_consumes, get_produces, guess_response_status,
is_list_view, merge_params, no_body, param_list_to_odict
merge_params, no_body, param_list_to_odict
)
from .base import ViewInspector, call_view_method
@@ -17,12 +17,15 @@ logger = logging.getLogger(__name__)
class SwaggerAutoSchema(ViewInspector):
def __init__(self, view, path, method, components, request, overrides):
def __init__(self, view, path, method, components, request, overrides, operation_keys=None):
super(SwaggerAutoSchema, self).__init__(view, path, method, components, request, overrides)
self._sch = AutoSchema()
self._sch.view = view
self.operation_keys = operation_keys
def get_operation(self, operation_keys=None):
operation_keys = operation_keys or self.operation_keys
def get_operation(self, operation_keys):
consumes = self.get_consumes()
produces = self.get_produces()
@@ -208,7 +211,7 @@ class SwaggerAutoSchema(ViewInspector):
default_schema = self.serializer_to_schema(default_schema) or ''
if default_schema:
if is_list_view(self.path, self.method, self.view) and self.method.lower() == 'get':
if self.has_list_response():
default_schema = openapi.Schema(type=openapi.TYPE_ARRAY, items=default_schema)
if self.should_page():
default_schema = self.get_paginated_response(default_schema) or default_schema
@@ -300,7 +303,7 @@ class SwaggerAutoSchema(ViewInspector):
return natural_parameters + serializer_parameters
def get_operation_id(self, operation_keys):
def get_operation_id(self, operation_keys=None):
"""Return an unique ID for this operation. The ID must be unique across
all :class:`.Operation` objects in the API.
@@ -308,6 +311,8 @@ class SwaggerAutoSchema(ViewInspector):
of this view in the API; e.g. ``('snippets', 'list')``, ``('snippets', 'retrieve')``, etc.
:rtype: str
"""
operation_keys = operation_keys or self.operation_keys
operation_id = self.overrides.get('operation_id', '')
if not operation_id:
operation_id = '_'.join(operation_keys)
@@ -369,7 +374,7 @@ class SwaggerAutoSchema(ViewInspector):
"""
return self.overrides.get('deprecated', None)
def get_tags(self, operation_keys):
def get_tags(self, operation_keys=None):
"""Get a list of tags for this operation. Tags determine how operations relate with each other, and in the UI
each tag will show as a group containing the operations that use it. If not provided in overrides,
tags will be inferred from the operation url.
@@ -378,6 +383,8 @@ class SwaggerAutoSchema(ViewInspector):
of this view in the API; e.g. ``('snippets', 'list')``, ``('snippets', 'retrieve')``, etc.
:rtype: list[str]
"""
operation_keys = operation_keys or self.operation_keys
tags = self.overrides.get('tags')
if not tags:
tags = [operation_keys[0]]
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
+29 -11
View File
@@ -2,11 +2,12 @@ import inspect
import logging
import sys
from collections import OrderedDict
from decimal import Decimal
from django.db import models
from django.utils.encoding import force_text
from rest_framework import serializers, status
from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.mixins import DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.parsers import FileUploadParser
from rest_framework.request import is_form_media_type
from rest_framework.settings import api_settings as rest_framework_settings
@@ -165,7 +166,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
if len(available_http_methods) > 1:
assert _methods, \
"on multi-method api_view, action, detail_route or list_route, you must specify " \
"on multi-method api_view or action, you must specify " \
"swagger_auto_schema on a per-method basis using one of the `method` or `methods` arguments"
else:
# for a single-method view we assume that single method as the decorator target
@@ -178,8 +179,8 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
view_method._swagger_auto_schema = existing_data
else:
assert not _methods, \
"the methods argument should only be specified when decorating an action, detail_route or " \
"list_route; you should also ensure that you put the swagger_auto_schema decorator " \
"the methods argument should only be specified when decorating an action; " \
"you should also ensure that you put the swagger_auto_schema decorator " \
"AFTER (above) the _route decorator"
assert not existing_data, "swagger_auto_schema applied twice to method"
view_method._swagger_auto_schema = data
@@ -214,7 +215,7 @@ def is_list_view(path, method, view):
:param APIView view: target view
:rtype: bool
"""
# for ViewSets, it could be the default 'list' action, or a list_route
# for ViewSets, it could be the default 'list' action, or an @action(detail=False)
action = getattr(view, 'action', '')
method = getattr(view, action, None) or method
detail = getattr(method, 'detail', None)
@@ -226,6 +227,9 @@ def is_list_view(path, method, view):
# a detail action is surely not a list route
return False
if isinstance(view, ListModelMixin):
return True
# for GenericAPIView, if it's a detail view it can't also be a list view
if isinstance(view, (RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin)):
return False
@@ -437,6 +441,25 @@ def force_real_str(s, encoding='utf-8', strings_only=False, errors='strict'):
return s
def field_value_to_representation(field, value):
"""Convert a python value related to a field (default, choices, etc.) into its OpenAPI-compatible representation.
:param serializers.Field field: field associated with the value
:param object value: value
:return: the converted value
"""
value = field.to_representation(value)
if isinstance(value, Decimal):
if decimal_as_float(field):
value = float(value)
else:
value = str(value)
# JSON roundtrip ensures that the value is valid JSON;
# for example, sets and tuples get transformed into lists
return json.loads(json.dumps(value, cls=encoders.JSONEncoder))
def get_field_default(field):
"""
Get the default value for a field, converted to a JSON-compatible value while properly handling callables.
@@ -458,12 +481,7 @@ def get_field_default(field):
if default is not serializers.empty and default is not None:
try:
default = field.to_representation(default)
# JSON roundtrip ensures that the value is valid JSON;
# for example, sets and tuples get transformed into lists
default = json.loads(json.dumps(default, cls=encoders.JSONEncoder))
if decimal_as_float(field):
default = float(default)
default = field_value_to_representation(field, default)
except Exception: # pragma: no cover
logger.warning("'default' on schema for %s will not be set because "
"to_representation raised an exception", field, exc_info=True)
+4 -13
View File
@@ -1,13 +1,11 @@
import datetime
import functools
from django.utils.decorators import method_decorator
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
# noinspection PyDeprecation
from rest_framework.filters import OrderingFilter
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.parsers import MultiPartParser, FileUploadParser
from rest_framework.parsers import FileUploadParser, MultiPartParser
from rest_framework.response import Response
from articles import serializers
@@ -93,17 +91,10 @@ class ArticleViewSet(viewsets.ModelViewSet):
swagger_schema = NoTitleAutoSchema
try:
from rest_framework.decorators import action
list_route = functools.partial(action, detail=False)
detail_route = functools.partial(action, detail=True)
except ImportError:
# TODO: remove when dropping support for DRF 3.7
action = None
from rest_framework.decorators import list_route, detail_route
from rest_framework.decorators import action
@swagger_auto_schema(auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector])
@list_route(methods=['get'])
@action(detail=False, methods=['get'])
def today(self, request):
today_min = datetime.datetime.combine(datetime.date.today(), datetime.time.min)
today_max = datetime.datetime.combine(datetime.date.today(), datetime.time.max)
@@ -118,7 +109,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
type=openapi.TYPE_INTEGER,
description="this should not crash (form parameter on DELETE method)"
)])
@detail_route(methods=['get', 'post', 'delete'], parser_classes=(MultiPartParser, FileUploadParser))
@action(detail=True, methods=['get', 'post', 'delete'], parser_classes=(MultiPartParser, FileUploadParser))
def image(self, request, slug=None):
"""
image method docstring
+6
View File
@@ -1,13 +1,19 @@
from rest_framework import viewsets
from rest_framework.pagination import BasePagination
from .models import Identity, Person
from .serializers import IdentitySerializer, PersonSerializer
class UnknownPagination(BasePagination):
paginator_query_args = ['unknown_paginator']
class PersonViewSet(viewsets.ModelViewSet):
model = Person
queryset = Person.objects
serializer_class = PersonSerializer
pagination_class = UnknownPagination
class IdentityViewSet(viewsets.ModelViewSet):
@@ -0,0 +1,24 @@
# Generated by Django 2.1.7 on 2019-03-16 14:06
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('snippets', '0002_auto_20181219_1016'),
]
operations = [
migrations.CreateModel(
name='SnippetViewer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('snippet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewers', to='snippets.Snippet')),
('viewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='snippet_views', to=settings.AUTH_USER_MODEL)),
],
),
]
@@ -0,0 +1,23 @@
# Generated by Django 2.2.2 on 2019-06-12 22:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('snippets', '0003_snippetviewer'),
]
operations = [
migrations.AlterField(
model_name='snippet',
name='language',
field=models.CharField(choices=[('cpp', 'cpp'), ('js', 'js'), ('python', 'python')], default='python', max_length=100),
),
migrations.AlterField(
model_name='snippet',
name='style',
field=models.CharField(choices=[('monokai', 'monokai'), ('solarized-dark', 'solarized-dark'), ('vim', 'vim')], default='solarized-dark', max_length=100),
),
]
+8 -6
View File
@@ -1,10 +1,7 @@
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
LANGUAGE_CHOICES = sorted((item, item) for item in ('cpp', 'python', 'js'))
STYLE_CHOICES = sorted((item, item) for item in ('solarized-dark', 'monokai', 'vim'))
class Snippet(models.Model):
@@ -14,7 +11,12 @@ class Snippet(models.Model):
code = models.TextField(help_text="code model help text")
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='solarized-dark', max_length=100)
class Meta:
ordering = ('created',)
class SnippetViewer(models.Model):
snippet = models.ForeignKey(Snippet, on_delete=models.CASCADE, related_name='viewers')
viewer = models.ForeignKey('auth.User', related_name='snippet_views', on_delete=models.CASCADE)
+15 -4
View File
@@ -1,10 +1,15 @@
import rest_framework
from decimal import Decimal
from django.contrib.auth import get_user_model
from packaging.version import Version
if Version(rest_framework.__version__) < Version('3.10'):
from rest_framework.compat import MaxLengthValidator, MinValueValidator
else:
from django.core.validators import MaxLengthValidator, MinValueValidator
from rest_framework import serializers
from rest_framework.compat import MaxLengthValidator, MinValueValidator
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet, SnippetViewer
class LanguageSerializer(serializers.Serializer):
@@ -17,7 +22,7 @@ class LanguageSerializer(serializers.Serializer):
class ExampleProjectSerializer(serializers.Serializer):
project_name = serializers.CharField(help_text='Name of the project')
project_name = serializers.CharField(label='project name custom title', help_text='Name of the project')
github_repo = serializers.CharField(required=True, help_text='Github repository of the project')
class Meta:
@@ -69,7 +74,7 @@ class SnippetSerializer(serializers.Serializer):
tags = serializers.ListField(child=serializers.CharField(min_length=2), min_length=3, max_length=15)
linenos = serializers.BooleanField(required=False)
language = LanguageSerializer(help_text="Sample help text for language")
styles = serializers.MultipleChoiceField(choices=STYLE_CHOICES, default=['friendly'])
styles = serializers.MultipleChoiceField(choices=STYLE_CHOICES, default=['solarized-dark'])
lines = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, allow_null=True, required=False)
example_projects = serializers.ListSerializer(child=ExampleProjectSerializer(), read_only=True,
validators=[MaxLengthValidator(100)])
@@ -100,3 +105,9 @@ class SnippetSerializer(serializers.Serializer):
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
class SnippetViewerSerializer(serializers.ModelSerializer):
class Meta:
model = SnippetViewer
fields = '__all__'
+3
View File
@@ -8,10 +8,13 @@ if django.VERSION[:2] >= (2, 0):
urlpatterns = [
path('', views.SnippetList.as_view()),
path('<int:pk>/', views.SnippetDetail.as_view()),
path('views/<int:snippet_pk>/', views.SnippetViewerList.as_view()),
]
else:
from django.conf.urls import url
urlpatterns = [
url('^$', views.SnippetList.as_view()),
url(r'^(?P<pk>\d+)/$', views.SnippetDetail.as_view()),
url(r'^views/(?P<snippet_pk>\d+)/$', views.SnippetViewerList.as_view()),
]
+33 -3
View File
@@ -2,13 +2,15 @@ from djangorestframework_camel_case.parser import CamelCaseJSONParser
from djangorestframework_camel_case.render import CamelCaseJSONRenderer
from inflection import camelize
from rest_framework import generics, status
from rest_framework.parsers import FormParser, FileUploadParser
from rest_framework.generics import get_object_or_404
from rest_framework.pagination import PageNumberPagination
from rest_framework.parsers import FileUploadParser, FormParser
from drf_yasg import openapi
from drf_yasg.inspectors import SwaggerAutoSchema
from drf_yasg.utils import swagger_auto_schema
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from snippets.models import Snippet, SnippetViewer
from snippets.serializers import SnippetSerializer, SnippetViewerSerializer
class CamelCaseOperationIDAutoSchema(SwaggerAutoSchema):
@@ -93,3 +95,31 @@ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
def delete(self, request, *args, **kwargs):
"""delete method docstring"""
return super(SnippetDetail, self).patch(request, *args, **kwargs)
class SnippetViewerList(generics.ListAPIView):
"""SnippetViewerList classdoc"""
serializer_class = SnippetViewerSerializer
pagination_class = PageNumberPagination
parser_classes = (FormParser, CamelCaseJSONParser, FileUploadParser)
renderer_classes = (CamelCaseJSONRenderer,)
swagger_schema = CamelCaseOperationIDAutoSchema
lookup_url_kwarg = 'snippet_pk'
def get_object(self):
queryset = Snippet.objects.all()
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
def get_queryset(self):
return SnippetViewer.objects.filter(snippet=self.get_object())
+11
View File
@@ -0,0 +1,11 @@
from drf_yasg import openapi
from drf_yasg.inspectors import NotHandled, PaginatorInspector
class UnknownPaginatorInspector(PaginatorInspector):
def get_paginator_parameters(self, paginator):
if hasattr(paginator, 'paginator_query_args'):
return [openapi.Parameter(name=arg, in_=openapi.IN_QUERY, type=openapi.TYPE_STRING)
for arg in getattr(paginator, 'paginator_query_args')]
return NotHandled
+6 -1
View File
@@ -140,7 +140,12 @@ SWAGGER_SETTINGS = {
'clientId': OAUTH2_CLIENT_ID,
'clientSecret': OAUTH2_CLIENT_SECRET,
'appName': OAUTH2_APP_NAME,
}
},
"DEFAULT_PAGINATOR_INSPECTORS": [
'testproj.inspectors.UnknownPaginatorInspector',
'drf_yasg.inspectors.DjangoRestResponsePagination',
'drf_yasg.inspectors.CoreAPICompatInspector',
]
}
REDOC_SETTINGS = {
+21
View File
@@ -0,0 +1,21 @@
# Generated by Django 2.1.5 on 2019-04-01 00:28
from decimal import Decimal
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('todo', '0002_todotree'),
]
operations = [
migrations.CreateModel(
name='Pack',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('size_code', models.DecimalField(choices=[(Decimal('50'), '5x10'), (Decimal('100'), '10x10'), (Decimal('200'), '10x20')], decimal_places=3, default=Decimal('200'), max_digits=7)),
],
),
]
+18
View File
@@ -1,3 +1,5 @@
from decimal import Decimal
from django.db import models
@@ -18,3 +20,19 @@ class TodoYetAnother(models.Model):
class TodoTree(models.Model):
parent = models.ForeignKey('self', on_delete=models.CASCADE, related_name='children', null=True)
title = models.CharField(max_length=50)
class Pack(models.Model):
SIZE_10x20 = Decimal(200.000)
SIZE_10x10 = Decimal(100.000)
SIZE_5x10 = Decimal(50.000)
size_code_choices = (
(SIZE_5x10, '5x10'),
(SIZE_10x10, '10x10'),
(SIZE_10x20, '10x20'),
)
size_code = models.DecimalField(max_digits=7,
decimal_places=3,
choices=size_code_choices,
default=SIZE_10x20)
+12 -1
View File
@@ -4,7 +4,7 @@ from django.utils import timezone
from rest_framework import serializers
from rest_framework_recursive.fields import RecursiveField
from .models import Todo, TodoAnother, TodoTree, TodoYetAnother
from .models import Pack, Todo, TodoAnother, TodoTree, TodoYetAnother
class TodoSerializer(serializers.ModelSerializer):
@@ -57,3 +57,14 @@ class TodoRecursiveSerializer(serializers.ModelSerializer):
class Meta:
model = TodoTree
fields = ('id', 'title', 'parent', 'parent_id')
class HarvestSerializer(serializers.ModelSerializer):
class Meta:
model = Pack
fields = (
'size_code',
)
read_only_fields = (
'size_code',
)
+1
View File
@@ -9,6 +9,7 @@ router.register(r'another', views.TodoAnotherViewSet)
router.register(r'yetanother', views.TodoYetAnotherViewSet)
router.register(r'tree', views.TodoTreeView)
router.register(r'recursive', views.TodoRecursiveView)
router.register(r'harvest', views.HarvestViewSet)
urlpatterns = router.urls
+18 -3
View File
@@ -1,11 +1,13 @@
from rest_framework import viewsets
from rest_framework import mixins, permissions, viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.generics import RetrieveAPIView
from drf_yasg.utils import swagger_auto_schema
from .models import Todo, TodoAnother, TodoTree, TodoYetAnother
from .models import Pack, Todo, TodoAnother, TodoTree, TodoYetAnother
from .serializer import (
TodoAnotherSerializer, TodoRecursiveSerializer, TodoSerializer, TodoTreeSerializer, TodoYetAnotherSerializer
HarvestSerializer, TodoAnotherSerializer, TodoRecursiveSerializer, TodoSerializer, TodoTreeSerializer,
TodoYetAnotherSerializer
)
@@ -75,3 +77,16 @@ class TodoRecursiveView(viewsets.ModelViewSet):
@swagger_auto_schema(responses={200: TodoRecursiveSerializer(many=True)})
def list(self, request, *args, **kwargs):
return super(TodoRecursiveView, self).list(request, *args, **kwargs)
class HarvestViewSet(mixins.ListModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
queryset = Pack.objects.all()
serializer_class = HarvestSerializer
permission_classes = [permissions.IsAuthenticated]
authentication_classes = [TokenAuthentication]
def perform_update(self, serializer):
pass
+6 -1
View File
@@ -1,3 +1,5 @@
import sys
from django.contrib.auth.models import User
from rest_framework import serializers
@@ -6,7 +8,10 @@ from snippets.models import Snippet
try:
import typing # noqa: F401
from .method_serializers_with_typing import MethodFieldExampleSerializer
if sys.version_info >= (3, 4):
from .method_serializers_with_typing import MethodFieldExampleSerializer
else:
from .method_serializers_without_typing import MethodFieldExampleSerializer
except ImportError:
from .method_serializers_without_typing import MethodFieldExampleSerializer
+128 -464
View File
@@ -276,7 +276,10 @@ paths:
get:
operationId: people_list
description: ''
parameters: []
parameters:
- name: unknown_paginator
in: query
type: string
responses:
'200':
description: ''
@@ -440,6 +443,46 @@ paths:
tags:
- snippets
parameters: []
/snippets/views/{snippet_pk}/:
get:
operationId: snippetsViewsRead
description: SnippetViewerList classdoc
parameters:
- name: page
in: query
description: A page number within the paginated result set.
required: false
type: integer
responses:
'200':
description: ''
schema:
required:
- count
- results
type: object
properties:
count:
type: integer
next:
type: string
format: uri
x-nullable: true
previous:
type: string
format: uri
x-nullable: true
results:
type: array
items:
$ref: '#/definitions/SnippetViewer'
tags:
- snippets
parameters:
- name: snippet_pk
in: path
required: true
type: string
/snippets/{id}/:
get:
operationId: snippetsRead
@@ -552,6 +595,60 @@ paths:
description: A unique integer value identifying this todo another.
required: true
type: integer
/todo/harvest/:
get:
operationId: todo_harvest_list
description: ''
parameters: []
responses:
'200':
description: ''
schema:
type: array
items:
$ref: '#/definitions/Harvest'
tags:
- todo
parameters: []
/todo/harvest/{id}/:
put:
operationId: todo_harvest_update
description: ''
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Harvest'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Harvest'
tags:
- todo
patch:
operationId: todo_harvest_partial_update
description: ''
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Harvest'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Harvest'
tags:
- todo
parameters:
- name: id
in: path
description: A unique integer value identifying this pack.
required: true
type: integer
/todo/recursive/:
get:
operationId: todo_recursive_list
@@ -1016,7 +1113,7 @@ definitions:
type: object
properties:
projectName:
title: Project name
title: project name custom title
description: Name of the project
type: string
minLength: 1
@@ -1082,443 +1179,9 @@ definitions:
description: The name of the programming language
type: string
enum:
- abap
- abnf
- ada
- adl
- agda
- aheui
- ahk
- alloy
- ampl
- antlr
- antlr-as
- antlr-cpp
- antlr-csharp
- antlr-java
- antlr-objc
- antlr-perl
- antlr-python
- antlr-ruby
- apacheconf
- apl
- applescript
- arduino
- as
- as3
- aspectj
- aspx-cs
- aspx-vb
- asy
- at
- autoit
- awk
- basemake
- bash
- bat
- bbcode
- bc
- befunge
- bib
- blitzbasic
- blitzmax
- bnf
- boo
- boogie
- brainfuck
- bro
- bst
- bugs
- c
- c-objdump
- ca65
- cadl
- camkes
- capdl
- capnp
- cbmbas
- ceylon
- cfc
- cfengine3
- cfm
- cfs
- chai
- chapel
- cheetah
- cirru
- clay
- clean
- clojure
- clojurescript
- cmake
- cobol
- cobolfree
- coffee-script
- common-lisp
- componentpascal
- console
- control
- coq
- cpp
- cpp-objdump
- cpsa
- cr
- crmsh
- croc
- cryptol
- csharp
- csound
- csound-document
- csound-score
- css
- css+django
- css+erb
- css+genshitext
- css+lasso
- css+mako
- css+mozpreproc
- css+myghty
- css+php
- css+smarty
- cucumber
- cuda
- cypher
- cython
- d
- d-objdump
- dart
- delphi
- dg
- diff
- django
- docker
- doscon
- dpatch
- dtd
- duel
- dylan
- dylan-console
- dylan-lid
- earl-grey
- easytrieve
- ebnf
- ec
- ecl
- eiffel
- elixir
- elm
- emacs
- erb
- erl
- erlang
- evoque
- extempore
- ezhil
- factor
- fan
- fancy
- felix
- fennel
- fish
- flatline
- forth
- fortran
- fortranfixed
- foxpro
- fsharp
- gap
- gas
- genshi
- genshitext
- glsl
- gnuplot
- go
- golo
- gooddata-cl
- gosu
- groff
- groovy
- gst
- haml
- handlebars
- haskell
- haxeml
- hexdump
- hlsl
- hsail
- html
- html+cheetah
- html+django
- html+evoque
- html+genshi
- html+handlebars
- html+lasso
- html+mako
- html+myghty
- html+ng2
- html+php
- html+smarty
- html+twig
- html+velocity
- http
- hx
- hybris
- hylang
- i6t
- idl
- idris
- iex
- igor
- inform6
- inform7
- ini
- io
- ioke
- irc
- isabelle
- j
- jags
- jasmin
- java
- javascript+mozpreproc
- jcl
- jlcon
- js
- js+cheetah
- js+django
- js+erb
- js+genshitext
- js+lasso
- js+mako
- js+myghty
- js+php
- js+smarty
- jsgf
- json
- json-object
- jsonld
- jsp
- julia
- juttle
- kal
- kconfig
- koka
- kotlin
- lagda
- lasso
- lcry
- lean
- less
- lhs
- lidr
- lighty
- limbo
- liquid
- live-script
- llvm
- logos
- logtalk
- lsl
- lua
- make
- mako
- maql
- mask
- mason
- mathematica
- matlab
- matlabsession
- md
- minid
- modelica
- modula2
- monkey
- monte
- moocode
- moon
- mozhashpreproc
- mozpercentpreproc
- mql
- mscgen
- mupad
- mxml
- myghty
- mysql
- nasm
- ncl
- nemerle
- nesc
- newlisp
- newspeak
- ng2
- nginx
- nim
- nit
- nixos
- nsis
- numpy
- nusmv
- objdump
- objdump-nasm
- objective-c
- objective-c++
- objective-j
- ocaml
- octave
- odin
- ooc
- opa
- openedge
- pacmanconf
- pan
- parasail
- pawn
- perl
- perl6
- php
- pig
- pike
- pkgconfig
- plpgsql
- postgresql
- postscript
- pot
- pov
- powershell
- praat
- prolog
- properties
- protobuf
- ps1con
- psql
- pug
- puppet
- py3tb
- pycon
- pypylog
- pytb
- python
- python3
- qbasic
- qml
- qvto
- racket
- ragel
- ragel-c
- ragel-cpp
- ragel-d
- ragel-em
- ragel-java
- ragel-objc
- ragel-ruby
- raw
- rb
- rbcon
- rconsole
- rd
- rebol
- red
- redcode
- registry
- resource
- rexx
- rhtml
- rnc
- roboconf-graph
- roboconf-instances
- robotframework
- rql
- rsl
- rst
- rts
- rust
- sas
- sass
- sc
- scala
- scaml
- scheme
- scilab
- scss
- shen
- silver
- slim
- smali
- smalltalk
- smarty
- sml
- snobol
- snowball
- sourceslist
- sp
- sparql
- spec
- splus
- sql
- sqlite3
- squidconf
- ssp
- stan
- stata
- swift
- swig
- systemverilog
- tads3
- tap
- tasm
- tcl
- tcsh
- tcshcon
- tea
- termcap
- terminfo
- terraform
- tex
- text
- thrift
- todotxt
- trac-wiki
- treetop
- ts
- tsql
- turtle
- twig
- typoscript
- typoscriptcssdata
- typoscripthtmldata
- urbiscript
- vala
- vb.net
- vcl
- vclsnippets
- vctreestatus
- velocity
- verilog
- vgl
- vhdl
- vim
- wdiff
- whiley
- x10
- xml
- xml+cheetah
- xml+django
- xml+erb
- xml+evoque
- xml+lasso
- xml+mako
- xml+myghty
- xml+php
- xml+smarty
- xml+velocity
- xorg.conf
- xquery
- xslt
- xtend
- xul+mozpreproc
- yaml
- yaml+jinja
- zephir
default: python
readOnlyNullable:
title: Read only nullable
@@ -1531,37 +1194,11 @@ definitions:
items:
type: string
enum:
- abap
- algol
- algol_nu
- arduino
- autumn
- borland
- bw
- colorful
- default
- emacs
- friendly
- fruity
- igor
- lovelace
- manni
- monokai
- murphy
- native
- paraiso-dark
- paraiso-light
- pastie
- perldoc
- rainbow_dash
- rrt
- tango
- trac
- solarized-dark
- vim
- vs
- xcode
default:
- friendly
- solarized-dark
lines:
type: array
items:
@@ -1590,6 +1227,22 @@ definitions:
format: decimal
default: 0.0
minimum: 0.0
SnippetViewer:
required:
- snippet
- viewer
type: object
properties:
id:
title: ID
type: integer
readOnly: true
snippet:
title: Snippet
type: integer
viewer:
title: Viewer
type: integer
Todo:
required:
- title
@@ -1613,6 +1266,17 @@ definitions:
minLength: 1
todo:
$ref: '#/definitions/Todo'
Harvest:
type: object
properties:
size_code:
title: Size code
type: string
enum:
- '50'
- '100'
- '200'
readOnly: true
TodoRecursive:
required:
- title
+44
View File
@@ -3,11 +3,14 @@ from collections import OrderedDict
import pytest
from django.conf.urls import url
from django.contrib.postgres import fields as postgres_fields
from django.db import models
from django.utils.inspect import get_func_args
from rest_framework import routers, serializers, viewsets
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django_fake_model import models as fake_models
from drf_yasg import codecs, openapi
from drf_yasg.codecs import yaml_sane_load
from drf_yasg.errors import SwaggerGenerationError
@@ -204,6 +207,7 @@ def test_action_mapping():
@pytest.mark.parametrize('choices, expected_type', [
(['A', 'B'], openapi.TYPE_STRING),
([u'A', u'B'], openapi.TYPE_STRING),
([123, 456], openapi.TYPE_INTEGER),
([1.2, 3.4], openapi.TYPE_NUMBER),
(['A', 456], openapi.TYPE_STRING)
@@ -229,3 +233,43 @@ def test_choice_field(choices, expected_type):
property_schema = swagger['definitions']['Detail']['properties']['detail']
assert property_schema == openapi.Schema(title='Detail', type=expected_type, enum=choices)
@pytest.mark.parametrize('choices, field, expected_type', [
([1, 2, 3], models.IntegerField, openapi.TYPE_INTEGER),
(["A", "B"], models.CharField, openapi.TYPE_STRING),
])
def test_nested_choice_in_array_field(choices, field, expected_type):
# Create a model class on the fly to avoid warnings about using the several
# model class name several times
model_class = type(
"%sModel" % field.__name__,
(fake_models.FakeModel,),
{
"array": postgres_fields.ArrayField(
field(choices=((i, "choice %s" % i) for i in choices))
),
"__module__": "test_models",
}
)
class ArraySerializer(serializers.ModelSerializer):
class Meta:
model = model_class
fields = ("array",)
class ArrayViewSet(viewsets.ModelViewSet):
serializer_class = ArraySerializer
router = routers.DefaultRouter()
router.register(r'arrays', ArrayViewSet, **_basename_or_base_name('arrays'))
generator = OpenAPISchemaGenerator(
info=openapi.Info(title='Test array model generator', default_version='v1'),
patterns=router.urls
)
swagger = generator.get_schema(None, True)
property_schema = swagger['definitions']['Array']['properties']['array']['items']
assert property_schema == openapi.Schema(title='Array', type=expected_type, enum=choices)
+12 -8
View File
@@ -1,13 +1,15 @@
[tox]
minversion = 3.3.0
isolated_build = true
isolated_build_env=.package
isolated_build_env = .package
# https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django
envlist =
py{27,34,35,36}-django111-drf{37,38,39},
py{34,35,36,37}-django20-drf{37,38,39},
py{35,36,37}-django21-drf{37,38,39},
py27-django111-drf39-typing,
py27-django111-drf{38,39},
py{35,36}-django{111,21,22}-drf{38,39},
py37-django{21,22}-drf{38,39},
py37-django{21,22}-drf310,
djmaster, lint, docs
[testenv:.package]
@@ -17,12 +19,14 @@ deps =
[testenv]
deps =
django111: Django>=1.11,<2.0
django20: Django>=2.0,<2.1
django21: Django>=2.1,<2.2
django22: Django>=2.2,<2.3
drf37: djangorestframework>=3.7.7,<3.8
drf38: djangorestframework>=3.8.0,<3.9
drf38: djangorestframework>=3.8,<3.9
drf39: djangorestframework>=3.9,<3.10
drf310: djangorestframework>=3.10
typing: typing>=3.6.6
# test with the latest build of django-rest-framework to get early warning of compatibility issues
djmaster: https://github.com/encode/django-rest-framework/archive/master.tar.gz
@@ -72,6 +76,6 @@ known_standard_library =
types,warnings
known_third_party =
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,
rest_framework_recursive,flex,gunicorn,inflection,pytest,rest_framework,ruamel,setuptools_scm,
swagger_spec_validator,uritemplate,user_agents,whitenoise,oauth2_provider
known_first_party = drf_yasg,testproj,articles,people,snippets,todo,users,urlconfs