Compare commits
30 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
728c02356c | |
|
|
9ccf24c27a | |
|
|
8aa255cf56 | |
|
|
7491d330a8 | |
|
|
ebe21b77c6 | |
|
|
17da098940 | |
|
|
a872eb66d6 | |
|
|
6a1166deb5 | |
|
|
b700191f46 | |
|
|
5c25ecd8f2 | |
|
|
8fd27664f1 | |
|
|
456b697ca2 | |
|
|
9966297f87 | |
|
|
27007a9cf4 | |
|
|
a72e5b2899 | |
|
|
13311582ea | |
|
|
9a89d8ccb0 | |
|
|
16f67cd8c2 | |
|
|
99fa7c25ca | |
|
|
97e70d9d16 | |
|
|
ee086a6eec | |
|
|
4af38c970a | |
|
|
95337f85ad | |
|
|
1352c2a23b | |
|
|
8578b93eba | |
|
|
212891b1b8 | |
|
|
ab6444a32e | |
|
|
2e0f9a19a9 | |
|
|
cda808fe11 | |
|
|
e6219ab8b7 |
10
.travis.yml
10
.travis.yml
|
|
@ -1,15 +1,14 @@
|
|||
language: python
|
||||
python:
|
||||
- '2.7'
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
- '3.7'
|
||||
- '3.8'
|
||||
|
||||
dist: xenial
|
||||
|
||||
cache: pip
|
||||
|
||||
jobs:
|
||||
matrix:
|
||||
include:
|
||||
- python: '3.6'
|
||||
env: TOXENV=docs
|
||||
|
|
@ -59,11 +58,6 @@ after_success:
|
|||
codecov
|
||||
fi
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v?\d+\.\d+(\.\d+)?(-?\S+)?$/
|
||||
|
||||
stages:
|
||||
- test
|
||||
- name: publish
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
Contributing
|
||||
############
|
||||
|
||||
Contributions are always welcome and appreciated! Here are some ways you can contribut.
|
||||
Contributions are always welcome and appreciated! Here are some ways you can contribute.
|
||||
|
||||
******
|
||||
Issues
|
||||
|
|
@ -57,7 +57,7 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||
|
||||
.. code:: console
|
||||
|
||||
(venv) $ python testproj/manage.py generate_swagger ../tests/reference.yaml --overwrite --user admin --url http://test.local:8002/
|
||||
(venv) $ python testproj/manage.py generate_swagger tests/reference.yaml --overwrite --user admin --url http://test.local:8002/
|
||||
|
||||
After checking the git diff to verify that no unexpected changes appeared, you should commit the new
|
||||
``reference.yaml`` together with your changes.
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framewor
|
|||
|
||||
Compatible with
|
||||
|
||||
- **Django Rest Framework**: 3.8, 3.9
|
||||
- **Django**: 1.11, 2.1, 2.2
|
||||
- **Python**: 2.7, 3.5, 3.6, 3.7
|
||||
- **Django Rest Framework**: 3.8, 3.9, 3.10, 3.11
|
||||
- **Django**: 1.11, 2.2, 3.0
|
||||
- **Python**: 2.7, 3.6, 3.7, 3.8
|
||||
|
||||
Only the latest patch version of each ``major.minor`` series of Python, Django and Django REST Framework is supported.
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,37 @@ Changelog
|
|||
#########
|
||||
|
||||
|
||||
**********
|
||||
**1.17.1**
|
||||
**********
|
||||
|
||||
*Release date: Feb 17, 2020*
|
||||
|
||||
- **FIXED:** fixed compatibility issue with CurrentUserDefault in Django Rest Framework 3.11
|
||||
- **FIXED:** respect `USERNAME_FIELD` in `generate_swagger` command (:pr:`486`)
|
||||
|
||||
**Support was dropped for Python 3.5, Django 2.0, Django 2.1, DRF 3.7**
|
||||
|
||||
**********
|
||||
**1.17.0**
|
||||
**********
|
||||
|
||||
*Release date: Oct 03, 2019*
|
||||
|
||||
- **ADDED:** added `JSONFieldInspector` for `JSONField` support (:pr:`417`)
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.23.11
|
||||
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-rc.14 (:issue:`398`)
|
||||
- **FIXED:** fixed a type hint support issue (:pr:`428`, :issue:`450`)
|
||||
- **FIXED:** fixed packaging issue caused by a missing requirement (:issue:`412`)
|
||||
|
||||
**********
|
||||
**1.16.1**
|
||||
**********
|
||||
|
||||
*Release date: Jul 16, 2019*
|
||||
|
||||
- **IMPROVED:** better enum type detection for nested `ChoiceField`\ s (:pr:`400`)
|
||||
- **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**
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ some properties of the generated :class:`.Operation`. For example, in a ``ViewSe
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
|
||||
@swagger_auto_schema(operation_description="partial_update description override", responses={404: 'slug not found'})
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
"""partial_update method docstring"""
|
||||
|
|
@ -210,7 +212,8 @@ Schema generation of ``serializers.SerializerMethodField`` is supported in two w
|
|||
Serializer ``Meta`` nested class
|
||||
********************************
|
||||
|
||||
You can define some per-serializer options by adding a ``Meta`` class to your serializer, e.g.:
|
||||
You can define some per-serializer or per-field options by adding a ``Meta`` class to your ``Serializer`` or
|
||||
serializer ``Field``, e.g.:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -234,6 +237,64 @@ The available options are:
|
|||
which are converted to Swagger ``Schema`` attribute names according to :func:`.make_swagger_name`.
|
||||
Attribute names and values must conform to the `OpenAPI 2.0 specification <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schemaObject>`_.
|
||||
|
||||
Suppose you wanted to model an email using a `JSONField` to store the subject and body for performance reasons:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
|
||||
class Email(models.Model):
|
||||
# Store data as JSON, but the data should be made up of
|
||||
# an object that has two properties, "subject" and "body"
|
||||
# Example:
|
||||
# {
|
||||
# "subject": "My Title",
|
||||
# "body": "The body of the message.",
|
||||
# }
|
||||
message = JSONField()
|
||||
|
||||
To instruct ``drf-yasg`` to output an OpenAPI schema that matches this, create a custom ``JSONField``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class EmailMessageField(serializers.JSONField):
|
||||
class Meta:
|
||||
swagger_schema_fields = {
|
||||
"type": openapi.TYPE_OBJECT,
|
||||
"title": "Email",
|
||||
"properties": {
|
||||
"subject": openapi.Schema(
|
||||
title="Email subject",
|
||||
type=openapi.TYPE_STRING,
|
||||
),
|
||||
"body": openapi.Schema(
|
||||
title="Email body",
|
||||
type=openapi.TYPE_STRING,
|
||||
),
|
||||
},
|
||||
"required": ["subject", "body"],
|
||||
}
|
||||
|
||||
class EmailSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Email
|
||||
fields = "__all__"
|
||||
|
||||
message = EmailMessageField()
|
||||
|
||||
.. Warning::
|
||||
|
||||
Overriding a default ``Field`` generated by a ``ModelSerializer`` will also override automatically
|
||||
generated validators for that ``Field``. To add ``Serializer`` validation back in manually, see the relevant
|
||||
`DRF Validators`_ and `DRF Fields`_ documentation.
|
||||
|
||||
One example way to do this is to set the ``default_validators`` attribute on a field.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class EmailMessageField(serializers.JSONField):
|
||||
default_validators = [my_custom_email_validator]
|
||||
...
|
||||
|
||||
*************************
|
||||
Subclassing and extending
|
||||
|
|
@ -376,7 +437,7 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
|
|||
|
||||
|
||||
class AnotherSerializer(serializers.ModelSerializer):
|
||||
chilf = OneSerializer()
|
||||
child = OneSerializer()
|
||||
|
||||
class Meta:
|
||||
model = SomeParentModel
|
||||
|
|
@ -387,3 +448,5 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
|
|||
|
||||
|
||||
.. _Python 3 type hinting: https://docs.python.org/3/library/typing.html
|
||||
.. _DRF Validators: https://www.django-rest-framework.org/api-guide/validators/
|
||||
.. _DRF Fields: https://www.django-rest-framework.org/api-guide/fields/#validators
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ to this list.
|
|||
:class:`'drf_yasg.inspectors.ChoiceFieldInspector' <.inspectors.ChoiceFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.FileFieldInspector' <.inspectors.FileFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.JSONFieldInspector' <.inspectors.JSONFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.HiddenFieldInspector' <.inspectors.HiddenFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.RecursiveFieldInspector' <.inspectors.RecursiveFieldInspector>`, |br| \
|
||||
:class:`'drf_yasg.inspectors.SerializerMethodFieldInspector' <.inspectors.SerializerMethodFieldInspector>`, |br| \
|
||||
|
|
@ -454,7 +455,7 @@ FETCH_SCHEMA_WITH_QUERY
|
|||
|
||||
Fetch the OpenAPI document using the query parameters passed to the ReDoc page request.
|
||||
|
||||
**Default**: :python:`'True` |br|
|
||||
**Default**: :python:`True` |br|
|
||||
*Maps to parameter*: -
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"name": "drf-yasg",
|
||||
"dependencies": {
|
||||
"redoc": "^2.0.0-rc.8-1",
|
||||
"swagger-ui-dist": "^3.22.3"
|
||||
"redoc": "^2.0.0-rc.14",
|
||||
"swagger-ui-dist": "^3.23.11"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ ruamel.yaml>=0.15.34
|
|||
inflection>=0.3.1
|
||||
six>=1.10.0
|
||||
uritemplate>=3.0.0
|
||||
packaging
|
||||
|
||||
djangorestframework>=3.8
|
||||
Django>=1.11.7
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
-r lint.txt
|
||||
|
||||
tox-battery>=0.5
|
||||
django-oauth-toolkit
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
# test project requirements
|
||||
Pillow>=4.3.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.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-camel-case>=1.1.2
|
||||
djangorestframework-recursive>=0.1.2
|
||||
dj-database-url>=0.4.2
|
||||
user_agents>=1.1.0
|
||||
# django-oauth-toolkit 1.2 does not support Django 1.11
|
||||
django-oauth-toolkit>=1.1.0,<1.2.0
|
||||
django-cors-headers
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -19,7 +19,7 @@ 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)
|
||||
py3_supported_range = (5, 8)
|
||||
|
||||
# convert inclusive range to exclusive range
|
||||
py3_supported_range = (py3_supported_range[0], py3_supported_range[1] + 1)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ SWAGGER_DEFAULTS = {
|
|||
'drf_yasg.inspectors.ChoiceFieldInspector',
|
||||
'drf_yasg.inspectors.FileFieldInspector',
|
||||
'drf_yasg.inspectors.DictFieldInspector',
|
||||
'drf_yasg.inspectors.JSONFieldInspector',
|
||||
'drf_yasg.inspectors.HiddenFieldInspector',
|
||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||
'drf_yasg.inspectors.SerializerMethodFieldInspector',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from six import raise_from
|
||||
from six import binary_type, raise_from, text_type
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
|
@ -176,7 +176,14 @@ class SaneYamlDumper(yaml.SafeDumper):
|
|||
node.flow_style = best_style
|
||||
return node
|
||||
|
||||
def represent_text(self, text):
|
||||
if "\n" in text:
|
||||
return self.represent_scalar('tag:yaml.org,2002:str', text, style='|')
|
||||
return self.represent_scalar('tag:yaml.org,2002:str', text)
|
||||
|
||||
|
||||
SaneYamlDumper.add_representer(binary_type, SaneYamlDumper.represent_text)
|
||||
SaneYamlDumper.add_representer(text_type, SaneYamlDumper.represent_text)
|
||||
SaneYamlDumper.add_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
||||
SaneYamlDumper.add_multi_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,24 +3,16 @@ import logging
|
|||
import re
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import uritemplate
|
||||
import rest_framework
|
||||
import uritemplate
|
||||
from coreapi.compat import urlparse
|
||||
from packaging.version import Version
|
||||
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 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
|
||||
|
|
@ -28,6 +20,14 @@ from .inspectors.field import get_basic_type_info, get_queryset_field, get_query
|
|||
from .openapi import ReferenceResolver, SwaggerDict
|
||||
from .utils import force_real_str, get_consumes, get_produces
|
||||
|
||||
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
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PATH_PARAMETER_RE = re.compile(r'{(?P<parameter>\w+)}')
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from .base import (
|
|||
)
|
||||
from .field import (
|
||||
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
||||
InlineSerializerInspector, RecursiveFieldInspector, ReferencingSerializerInspector, RelatedFieldInspector,
|
||||
SerializerMethodFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector
|
||||
InlineSerializerInspector, JSONFieldInspector, RecursiveFieldInspector, ReferencingSerializerInspector,
|
||||
RelatedFieldInspector, SerializerMethodFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector
|
||||
)
|
||||
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
||||
from .view import SwaggerAutoSchema
|
||||
|
|
@ -24,7 +24,7 @@ __all__ = [
|
|||
|
||||
# field inspectors
|
||||
'InlineSerializerInspector', 'RecursiveFieldInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector',
|
||||
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector',
|
||||
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector', 'JSONFieldInspector',
|
||||
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector', 'SerializerMethodFieldInspector',
|
||||
|
||||
# view inspectors
|
||||
|
|
|
|||
|
|
@ -382,8 +382,21 @@ def find_limits(field):
|
|||
def decimal_field_type(field):
|
||||
return openapi.TYPE_NUMBER if decimal_as_float(field) else openapi.TYPE_STRING
|
||||
|
||||
def recurse_one_to_one(field, visited_set=None):
|
||||
if visited_set is None:
|
||||
visited_set = set()
|
||||
if field in visited_set:
|
||||
return None #cycle?
|
||||
if isinstance(field, models.OneToOneField):
|
||||
tgt = field.target_field
|
||||
visited_set.add(field)
|
||||
return recurse_one_to_one(tgt, visited_set=visited_set)
|
||||
else:
|
||||
tmp = get_basic_type_info(field)
|
||||
return tmp['type']
|
||||
|
||||
model_field_to_basic_type = [
|
||||
(models.OneToOneField, (recurse_one_to_one, None)),
|
||||
(models.AutoField, (openapi.TYPE_INTEGER, None)),
|
||||
(models.BinaryField, (openapi.TYPE_STRING, openapi.FORMAT_BINARY)),
|
||||
(models.BooleanField, (openapi.TYPE_BOOLEAN, None)),
|
||||
|
|
@ -610,6 +623,8 @@ 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)
|
||||
|
||||
|
|
@ -745,11 +760,23 @@ class HiddenFieldInspector(FieldInspector):
|
|||
return NotHandled
|
||||
|
||||
|
||||
class JSONFieldInspector(FieldInspector):
|
||||
"""Provides conversion for ``JSONField``."""
|
||||
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
||||
|
||||
if isinstance(field, serializers.JSONField) and swagger_object_type == openapi.Schema:
|
||||
return SwaggerType(type=openapi.TYPE_OBJECT)
|
||||
|
||||
return NotHandled
|
||||
|
||||
|
||||
class StringDefaultFieldInspector(FieldInspector):
|
||||
"""For otherwise unhandled fields, return them as plain :data:`.TYPE_STRING` objects."""
|
||||
|
||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs): # pragma: no cover
|
||||
# TODO unhandled fields: TimeField JSONField
|
||||
# TODO unhandled fields: TimeField
|
||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
||||
return SwaggerType(type=openapi.TYPE_STRING)
|
||||
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ class Command(BaseCommand):
|
|||
if user:
|
||||
# Only call get_user_model if --user was passed in order to
|
||||
# avoid crashing if auth is not configured in the project
|
||||
user = get_user_model().objects.get(username=user)
|
||||
user = get_user_model().objects.get(**{get_user_model().USERNAME_FIELD: user})
|
||||
|
||||
mock = mock or private or (user is not None) or (api_version is not None)
|
||||
if mock and not api_url:
|
||||
|
|
|
|||
|
|
@ -470,7 +470,7 @@ class Schema(SwaggerDict):
|
|||
:type properties: dict[str,Schema or SchemaRef]
|
||||
:param additional_properties: allow wildcard properties not listed in `properties`
|
||||
:type additional_properties: bool or Schema or SchemaRef
|
||||
:param list[str] required: list of requried property names
|
||||
:param list[str] required: list of required property names
|
||||
:param items: type of array items, only valid if `type` is ``array``
|
||||
:type items: Schema or SchemaRef
|
||||
:param default: only valid when insider another ``Schema``\\ 's ``properties``;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import six
|
|||
|
||||
from django.shortcuts import resolve_url
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.functional import Promise
|
||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, TemplateHTMLRenderer
|
||||
from rest_framework.utils import encoders, json
|
||||
|
|
@ -124,7 +124,7 @@ class SwaggerUIRenderer(_UIRenderer):
|
|||
swagger_ui_settings = self.get_swagger_ui_settings()
|
||||
|
||||
request = renderer_context.get('request', None)
|
||||
oauth_redirect_url = force_text(swagger_ui_settings.get('oauth2RedirectUrl', ''))
|
||||
oauth_redirect_url = force_str(swagger_ui_settings.get('oauth2RedirectUrl', ''))
|
||||
if request and oauth_redirect_url:
|
||||
swagger_ui_settings['oauth2RedirectUrl'] = request.build_absolute_uri(oauth_redirect_url)
|
||||
|
||||
|
|
|
|||
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
File diff suppressed because one or more lines are too long
|
|
@ -1,11 +1,12 @@
|
|||
import inspect
|
||||
import logging
|
||||
import sys
|
||||
import textwrap
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import models
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.mixins import DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from rest_framework.parsers import FileUploadParser
|
||||
|
|
@ -95,8 +96,8 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
|||
* a ``Serializer`` class or instance will be converted into a :class:`.Schema` and treated as above
|
||||
* a :class:`.Response` object will be used as-is; however if its ``schema`` attribute is a ``Serializer``,
|
||||
it will automatically be converted into a :class:`.Schema`
|
||||
:type responses: dict[str,(drf_yasg.openapi.Schema or drf_yasg.openapi.SchemaRef or drf_yasg.openapi.Response or
|
||||
str or rest_framework.serializers.Serializer)]
|
||||
:type responses: dict[int or str, (drf_yasg.openapi.Schema or drf_yasg.openapi.SchemaRef or
|
||||
drf_yasg.openapi.Response or str or rest_framework.serializers.Serializer)]
|
||||
|
||||
:param list[type[drf_yasg.inspectors.FieldInspector]] field_inspectors: extra serializer and field inspectors; these
|
||||
will be tried before :attr:`.ViewInspector.field_inspectors` on the :class:`.inspectors.SwaggerAutoSchema`
|
||||
|
|
@ -374,10 +375,16 @@ def get_consumes(parser_classes):
|
|||
parser_classes = [pc for pc in parser_classes if not issubclass(pc, FileUploadParser)]
|
||||
media_types = [parser.media_type for parser in parser_classes or []]
|
||||
non_form_media_types = [encoding for encoding in media_types if not is_form_media_type(encoding)]
|
||||
# Because swagger Parameter objects don't support complex data types (nested objects, arrays),
|
||||
# we can't use those unless we are sure the view *only* accepts form data
|
||||
# This means that a view won't support file upload in swagger unless it explicitly
|
||||
# sets its parser classes to include only form parsers
|
||||
if len(non_form_media_types) == 0:
|
||||
return media_types
|
||||
else:
|
||||
return non_form_media_types
|
||||
|
||||
# If the form accepts both form data and another type, like json (which is the default config),
|
||||
# we will render its input as a Schema and thus it file parameters will be read-only
|
||||
return non_form_media_types
|
||||
|
||||
|
||||
def get_produces(renderer_classes):
|
||||
|
|
@ -434,10 +441,13 @@ def force_real_str(s, encoding='utf-8', strings_only=False, errors='strict'):
|
|||
Fix for https://github.com/axnsan12/drf-yasg/issues/159
|
||||
"""
|
||||
if s is not None:
|
||||
s = force_text(s, encoding, strings_only, errors)
|
||||
s = force_str(s, encoding, strings_only, errors)
|
||||
if type(s) != str:
|
||||
s = '' + s
|
||||
|
||||
# Remove common indentation to get the correct Markdown rendering
|
||||
s = textwrap.dedent(s)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
|
|
@ -473,7 +483,10 @@ def get_field_default(field):
|
|||
try:
|
||||
if hasattr(default, 'set_context'):
|
||||
default.set_context(field)
|
||||
default = default()
|
||||
if getattr(default, 'requires_context', False):
|
||||
default = default(field)
|
||||
else:
|
||||
default = default()
|
||||
except Exception: # pragma: no cover
|
||||
logger.warning("default for %s is callable but it raised an exception when "
|
||||
"called; 'default' will not be set on schema", field, exc_info=True)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import warnings
|
||||
from functools import wraps
|
||||
from functools import WRAPPER_ASSIGNMENTS, wraps
|
||||
|
||||
from django.utils.cache import add_never_cache_headers
|
||||
from django.utils.decorators import available_attrs
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
from rest_framework import exceptions
|
||||
|
|
@ -30,7 +29,7 @@ def deferred_never_cache(view_func):
|
|||
never be cached.
|
||||
"""
|
||||
|
||||
@wraps(view_func, assigned=available_attrs(view_func))
|
||||
@wraps(view_func, assigned=WRAPPER_ASSIGNMENTS)
|
||||
def _wrapped_view_func(request, *args, **kwargs):
|
||||
response = view_func(request, *args, **kwargs)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from articles.models import Article, ArticleGroup
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import rest_framework
|
||||
from decimal import Decimal
|
||||
|
||||
import rest_framework
|
||||
from django.contrib.auth import get_user_model
|
||||
from packaging.version import Version
|
||||
from rest_framework import serializers
|
||||
|
||||
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet, SnippetViewer
|
||||
|
||||
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 snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet, SnippetViewer
|
||||
|
||||
|
||||
class LanguageSerializer(serializers.Serializer):
|
||||
|
|
|
|||
|
|
@ -193,16 +193,6 @@ LOGGING = {
|
|||
'propagate': False,
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['console_log'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
'django.db.backends': {
|
||||
'handlers': ['console_log'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'django.template': {
|
||||
'handlers': ['console_log'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.db import migrations, IntegrityError
|
||||
from django.db import migrations, IntegrityError, transaction
|
||||
|
||||
|
||||
def add_default_user(apps, schema_editor):
|
||||
|
|
@ -13,14 +13,15 @@ def add_default_user(apps, schema_editor):
|
|||
User = apps.get_model(settings.AUTH_USER_MODEL)
|
||||
|
||||
try:
|
||||
admin = User(
|
||||
username=username,
|
||||
email=email,
|
||||
password=make_password(password),
|
||||
is_superuser=True,
|
||||
is_staff=True
|
||||
)
|
||||
admin.save()
|
||||
with transaction.atomic():
|
||||
admin = User(
|
||||
username=username,
|
||||
email=email,
|
||||
password=make_password(password),
|
||||
is_superuser=True,
|
||||
is_staff=True
|
||||
)
|
||||
admin.save()
|
||||
except IntegrityError:
|
||||
sys.stdout.write(" User '%s <%s>' already exists..." % (username, email))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
swagger: '2.0'
|
||||
info:
|
||||
title: Snippets API
|
||||
description: "This is a demo project for the [drf-yasg](https://github.com/axnsan12/drf-yasg)\
|
||||
\ Django Rest Framework library.\n\nThe `swagger-ui` view can be found [here](/cached/swagger).\
|
||||
\ \nThe `ReDoc` view can be found [here](/cached/redoc). \nThe swagger YAML\
|
||||
\ document can be found [here](/cached/swagger.yaml). \n\nYou can log in using\
|
||||
\ the pre-existing `admin` user with password `passwordadmin`."
|
||||
description: |-
|
||||
This is a demo project for the [drf-yasg](https://github.com/axnsan12/drf-yasg) Django Rest Framework library.
|
||||
|
||||
The `swagger-ui` view can be found [here](/cached/swagger).
|
||||
The `ReDoc` view can be found [here](/cached/redoc).
|
||||
The swagger YAML document can be found [here](/cached/swagger.yaml).
|
||||
|
||||
You can log in using the pre-existing `admin` user with password `passwordadmin`.
|
||||
termsOfService: https://www.google.com/policies/terms/
|
||||
contact:
|
||||
email: contact@snippets.local
|
||||
|
|
@ -1502,7 +1505,9 @@ definitions:
|
|||
readOnly: true
|
||||
help_text_example_3:
|
||||
title: Help text example 3
|
||||
description: "\n docstring is set so should appear in swagger as fallback\n\
|
||||
\ :return:\n "
|
||||
description: |2
|
||||
|
||||
docstring is set so should appear in swagger as fallback
|
||||
:return:
|
||||
type: integer
|
||||
readOnly: true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
|
||||
import pytest
|
||||
|
|
@ -6,17 +7,22 @@ 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 django_fake_model import models as fake_models
|
||||
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
|
||||
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
|
||||
try:
|
||||
import typing
|
||||
except ImportError:
|
||||
typing = None
|
||||
|
||||
|
||||
def test_schema_is_valid(swagger, codec_yaml):
|
||||
codec_yaml.encode(swagger)
|
||||
|
|
@ -273,3 +279,76 @@ def test_nested_choice_in_array_field(choices, field, expected_type):
|
|||
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)
|
||||
|
||||
|
||||
def test_json_field():
|
||||
class TestJSONFieldSerializer(serializers.Serializer):
|
||||
json = serializers.JSONField()
|
||||
|
||||
class JSONViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = TestJSONFieldSerializer
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'jsons', JSONViewSet, **_basename_or_base_name('jsons'))
|
||||
|
||||
generator = OpenAPISchemaGenerator(
|
||||
info=openapi.Info(title='Test json field generator', default_version='v1'),
|
||||
patterns=router.urls
|
||||
)
|
||||
|
||||
swagger = generator.get_schema(None, True)
|
||||
property_schema = swagger["definitions"]["TestJSONField"]["properties"]["json"]
|
||||
assert property_schema == openapi.Schema(title='Json', type=openapi.TYPE_OBJECT)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('py_type, expected_type', [
|
||||
(str, openapi.TYPE_STRING),
|
||||
(int, openapi.TYPE_INTEGER),
|
||||
(float, openapi.TYPE_NUMBER),
|
||||
(bool, openapi.TYPE_BOOLEAN),
|
||||
])
|
||||
@pytest.mark.skipif(typing is None or sys.version_info.major < 3, reason="typing not supported")
|
||||
def test_optional_return_type(py_type, expected_type):
|
||||
|
||||
class OptionalMethodSerializer(serializers.Serializer):
|
||||
x = serializers.SerializerMethodField()
|
||||
|
||||
def get_x(self, instance):
|
||||
pass
|
||||
|
||||
# Add the type annotation here in order to avoid a SyntaxError in py27
|
||||
get_x.__annotations__["return"] = typing.Optional[py_type]
|
||||
|
||||
class OptionalMethodViewSet(viewsets.ViewSet):
|
||||
@swagger_auto_schema(responses={200: openapi.Response("OK", OptionalMethodSerializer)})
|
||||
def retrieve(self, request, pk=None):
|
||||
return Response({'optional': None})
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'optional', OptionalMethodViewSet, **_basename_or_base_name('optional'))
|
||||
|
||||
generator = OpenAPISchemaGenerator(
|
||||
info=openapi.Info(title='Test optional parameter', default_version='v1'),
|
||||
patterns=router.urls
|
||||
)
|
||||
swagger = generator.get_schema(None, True)
|
||||
property_schema = swagger["definitions"]["OptionalMethod"]["properties"]["x"]
|
||||
assert property_schema == openapi.Schema(title='X', type=expected_type, readOnly=True)
|
||||
|
||||
|
||||
EXPECTED_DESCRIPTION = """\
|
||||
description: |-
|
||||
This is a demo project for the [drf-yasg](https://github.com/axnsan12/drf-yasg) Django Rest Framework library.
|
||||
|
||||
The `swagger-ui` view can be found [here](/cached/swagger).
|
||||
The `ReDoc` view can be found [here](/cached/redoc).
|
||||
The swagger YAML document can be found [here](/cached/swagger.yaml).
|
||||
|
||||
You can log in using the pre-existing `admin` user with password `passwordadmin`.
|
||||
"""
|
||||
|
||||
|
||||
def test_multiline_strings(call_generate_swagger):
|
||||
output = call_generate_swagger(format='yaml')
|
||||
print("|\n|".join(output.splitlines()[:20]))
|
||||
assert EXPECTED_DESCRIPTION in output
|
||||
|
|
|
|||
29
tox.ini
29
tox.ini
|
|
@ -5,12 +5,11 @@ isolated_build_env = .package
|
|||
|
||||
# https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django
|
||||
envlist =
|
||||
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,
|
||||
py36-django{111,22}-drf{38,39},
|
||||
py37-django22-drf{38,39,310,311},
|
||||
py38-django{22,3}-drf{310,311},
|
||||
djmaster, lint, docs
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv:.package]
|
||||
# no additional dependencies besides PEP 517
|
||||
|
|
@ -19,18 +18,30 @@ deps =
|
|||
[testenv]
|
||||
deps =
|
||||
django111: Django>=1.11,<2.0
|
||||
django111: django-oauth-toolkit>=1.1.0,<1.2.0
|
||||
|
||||
django21: Django>=2.1,<2.2
|
||||
django21: django-oauth-toolkit>=1.2.0
|
||||
|
||||
django22: Django>=2.2,<2.3
|
||||
django22: django-oauth-toolkit>=1.2.0
|
||||
|
||||
django3: Django>=2.2,<2.3
|
||||
django3: django-oauth-toolkit>=1.2.0
|
||||
|
||||
drf38: djangorestframework>=3.8,<3.9
|
||||
drf39: djangorestframework>=3.9,<3.10
|
||||
drf310: djangorestframework>=3.10
|
||||
drf310: djangorestframework>=3.10,<3.11
|
||||
drf311: djangorestframework>=3.11,<3.12
|
||||
|
||||
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
|
||||
# test with the latest builds of Django and django-rest-framework
|
||||
# to get early warning of compatibility issues
|
||||
djmaster: https://github.com/django/django/archive/master.tar.gz
|
||||
djmaster: https://github.com/ottoyiu/django-cors-headers/archive/master.tar.gz
|
||||
djmaster: https://github.com/encode/django-rest-framework/archive/master.tar.gz
|
||||
djmaster: django-oauth-toolkit>=1.2.0
|
||||
|
||||
# other dependencies
|
||||
-r requirements/validation.txt
|
||||
|
|
@ -77,5 +88,5 @@ known_standard_library =
|
|||
known_third_party =
|
||||
coreapi,coreschema,datadiff,dj_database_url,django,django_filters,djangorestframework_camel_case,
|
||||
rest_framework_recursive,flex,gunicorn,inflection,pytest,rest_framework,ruamel,setuptools_scm,
|
||||
swagger_spec_validator,uritemplate,user_agents,whitenoise,oauth2_provider
|
||||
swagger_spec_validator,uritemplate,user_agents,whitenoise,oauth2_provider,packaging
|
||||
known_first_party = drf_yasg,testproj,articles,people,snippets,todo,users,urlconfs
|
||||
|
|
|
|||
Loading…
Reference in New Issue