parent
65aac1da2c
commit
37c00ab3fb
|
|
@ -52,7 +52,7 @@ Changelog
|
||||||
- **ADDED:** added ``DEFAULT_GENERATOR_CLASS`` setting and ``--generator-class`` argument to the ``generate_swagger``
|
- **ADDED:** added ``DEFAULT_GENERATOR_CLASS`` setting and ``--generator-class`` argument to the ``generate_swagger``
|
||||||
management command (:issue:`140`)
|
management command (:issue:`140`)
|
||||||
- **FIXED:** fixed wrongly required ``'count'`` response field on ``CursorPagination`` (:issue:`141`)
|
- **FIXED:** fixed wrongly required ``'count'`` response field on ``CursorPagination`` (:issue:`141`)
|
||||||
- **FIXED:** fixed some cases where ``swagger_extra_fields`` would not be handlded (:pr:`142`)
|
- **FIXED:** fixed some cases where ``swagger_schema_fields`` would not be handlded (:pr:`142`)
|
||||||
- **FIXED:** fixed crash when encountering ``coreapi.Fields``\ s without a ``schema`` (:issue:`143`)
|
- **FIXED:** fixed crash when encountering ``coreapi.Fields``\ s without a ``schema`` (:issue:`143`)
|
||||||
|
|
||||||
*********
|
*********
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@ import inspect
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.utils import encoders, json
|
|
||||||
|
|
||||||
from .. import openapi
|
from .. import openapi
|
||||||
from ..utils import decimal_as_float, force_real_str, is_list_view
|
from ..utils import force_real_str, get_field_default, is_list_view
|
||||||
|
|
||||||
#: Sentinel value that inspectors must return to signal that they do not know how to handle an object
|
#: Sentinel value that inspectors must return to signal that they do not know how to handle an object
|
||||||
NotHandled = object()
|
NotHandled = object()
|
||||||
|
|
@ -135,6 +134,19 @@ class FieldInspector(BaseInspector):
|
||||||
super(FieldInspector, self).__init__(view, path, method, components, request)
|
super(FieldInspector, self).__init__(view, path, method, components, request)
|
||||||
self.field_inspectors = field_inspectors
|
self.field_inspectors = field_inspectors
|
||||||
|
|
||||||
|
def add_manual_fields(self, serializer_or_field, schema):
|
||||||
|
"""Set fields from the ``swagger_schem_fields`` attribute on the Meta class. This method is called
|
||||||
|
only for serializers or fields that are converted into ``openapi.Schema`` objects.
|
||||||
|
|
||||||
|
:param serializer_or_field: serializer or field instance
|
||||||
|
:param openapi.Schema schema: the schema object to be modified in-place
|
||||||
|
"""
|
||||||
|
meta = getattr(serializer_or_field, 'Meta', None)
|
||||||
|
swagger_schema_fields = getattr(meta, 'swagger_schema_fields', {})
|
||||||
|
if swagger_schema_fields:
|
||||||
|
for attr, val in swagger_schema_fields.items():
|
||||||
|
setattr(schema, attr, val)
|
||||||
|
|
||||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||||
"""Convert a drf Serializer or Field instance into a Swagger object.
|
"""Convert a drf Serializer or Field instance into a Swagger object.
|
||||||
|
|
||||||
|
|
@ -205,46 +217,29 @@ class FieldInspector(BaseInspector):
|
||||||
instance_kwargs['required'] = field.required
|
instance_kwargs['required'] = field.required
|
||||||
|
|
||||||
if 'default' not in instance_kwargs and swagger_object_type != openapi.Items:
|
if 'default' not in instance_kwargs and swagger_object_type != openapi.Items:
|
||||||
default = getattr(field, 'default', serializers.empty)
|
default = get_field_default(field)
|
||||||
if default is not serializers.empty:
|
if default not in (None, serializers.empty):
|
||||||
if callable(default):
|
instance_kwargs['default'] = default
|
||||||
try:
|
|
||||||
if hasattr(default, 'set_context'):
|
|
||||||
default.set_context(field)
|
|
||||||
default = default()
|
|
||||||
except Exception: # pragma: no cover
|
|
||||||
logger.warning("default for %s is callable but it raised an exception when "
|
|
||||||
"called; 'default' field will not be added to schema", field, exc_info=True)
|
|
||||||
default = None
|
|
||||||
|
|
||||||
if 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)
|
|
||||||
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)
|
|
||||||
default = None
|
|
||||||
|
|
||||||
if default is not None:
|
|
||||||
instance_kwargs['default'] = default
|
|
||||||
|
|
||||||
if instance_kwargs.get('type', None) != openapi.TYPE_ARRAY:
|
if instance_kwargs.get('type', None) != openapi.TYPE_ARRAY:
|
||||||
instance_kwargs.setdefault('title', title)
|
instance_kwargs.setdefault('title', title)
|
||||||
instance_kwargs.setdefault('description', description)
|
if description is not None:
|
||||||
|
instance_kwargs.setdefault('description', description)
|
||||||
instance_kwargs.update(kwargs)
|
instance_kwargs.update(kwargs)
|
||||||
|
|
||||||
if existing_object is not None:
|
if existing_object is not None:
|
||||||
assert isinstance(existing_object, swagger_object_type)
|
assert isinstance(existing_object, swagger_object_type)
|
||||||
for attr, val in sorted(instance_kwargs.items()):
|
for key, val in sorted(instance_kwargs.items()):
|
||||||
setattr(existing_object, attr, val)
|
setattr(existing_object, key, val)
|
||||||
return existing_object
|
result = existing_object
|
||||||
|
else:
|
||||||
|
result = swagger_object_type(**instance_kwargs)
|
||||||
|
|
||||||
return swagger_object_type(**instance_kwargs)
|
# Provide an option to add manual paremeters to a schema
|
||||||
|
# for example, to add examples
|
||||||
|
if swagger_object_type == openapi.Schema:
|
||||||
|
self.add_manual_fields(field, result)
|
||||||
|
return result
|
||||||
|
|
||||||
# arrays in Schema have Schema elements, arrays in Parameter and Items have Items elements
|
# arrays in Schema have Schema elements, arrays in Parameter and Items have Items elements
|
||||||
child_swagger_type = openapi.Schema if swagger_object_type == openapi.Schema else openapi.Items
|
child_swagger_type = openapi.Schema if swagger_object_type == openapi.Schema else openapi.Items
|
||||||
|
|
|
||||||
|
|
@ -31,19 +31,6 @@ class InlineSerializerInspector(SerializerInspector):
|
||||||
#: whether to output :class:`.Schema` definitions inline or into the ``definitions`` section
|
#: whether to output :class:`.Schema` definitions inline or into the ``definitions`` section
|
||||||
use_definitions = False
|
use_definitions = False
|
||||||
|
|
||||||
def add_manual_fields(self, serializer, schema):
|
|
||||||
"""Set fields from the ``swagger_schem_fields`` attribute on the serializer's Meta class. This method is called
|
|
||||||
only for serializers that are converted into ``openapi.Schema`` objects.
|
|
||||||
|
|
||||||
:param serializer: serializer instance
|
|
||||||
:param openapi.Schema schema: the schema object to be modified in-place
|
|
||||||
"""
|
|
||||||
serializer_meta = getattr(serializer, 'Meta', None)
|
|
||||||
swagger_schema_fields = getattr(serializer_meta, 'swagger_schema_fields', {})
|
|
||||||
if swagger_schema_fields:
|
|
||||||
for attr, val in swagger_schema_fields.items():
|
|
||||||
setattr(schema, attr, val)
|
|
||||||
|
|
||||||
def get_schema(self, serializer):
|
def get_schema(self, serializer):
|
||||||
return self.probe_field_inspectors(serializer, openapi.Schema, self.use_definitions)
|
return self.probe_field_inspectors(serializer, openapi.Schema, self.use_definitions)
|
||||||
|
|
||||||
|
|
@ -124,9 +111,6 @@ class InlineSerializerInspector(SerializerInspector):
|
||||||
# it is better to just remove title from inline models
|
# it is better to just remove title from inline models
|
||||||
del result.title
|
del result.title
|
||||||
|
|
||||||
# Provide an option to add manual paremeters to a schema
|
|
||||||
# for example, to add examples
|
|
||||||
self.add_manual_fields(field, result)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if not ref_name or not use_references:
|
if not ref_name or not use_references:
|
||||||
|
|
@ -696,9 +680,9 @@ else:
|
||||||
assert use_references is True, "Can not create schema for RecursiveField when use_references is False"
|
assert use_references is True, "Can not create schema for RecursiveField when use_references is False"
|
||||||
|
|
||||||
ref_name = get_serializer_ref_name(field.proxied)
|
ref_name = get_serializer_ref_name(field.proxied)
|
||||||
assert ref_name is not None, "Can not create RecursiveField schema for inline ModelSerializer"
|
assert ref_name is not None, "Can't create RecursiveField schema for inline " + str(type(field.proxied))
|
||||||
|
|
||||||
return openapi.SchemaRef(self.components.with_scope(openapi.SCHEMA_DEFINITIONS), ref_name,
|
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
|
||||||
ignore_unresolved=True)
|
return openapi.SchemaRef(definitions, ref_name, ignore_unresolved=True)
|
||||||
|
|
||||||
return NotHandled
|
return NotHandled
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from rest_framework import serializers, status
|
||||||
from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin
|
from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||||
from rest_framework.request import is_form_media_type
|
from rest_framework.request import is_form_media_type
|
||||||
from rest_framework.settings import api_settings as rest_framework_settings
|
from rest_framework.settings import api_settings as rest_framework_settings
|
||||||
|
from rest_framework.utils import encoders, json
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -354,3 +355,38 @@ def force_real_str(s, encoding='utf-8', strings_only=False, errors='strict'):
|
||||||
s = '' + s
|
s = '' + s
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def get_field_default(field):
|
||||||
|
"""
|
||||||
|
Get the default value for a field, converted to a JSON-compatible value while properly handling callables.
|
||||||
|
|
||||||
|
:param field: field instance
|
||||||
|
:return: default value
|
||||||
|
"""
|
||||||
|
default = getattr(field, 'default', serializers.empty)
|
||||||
|
if default is not serializers.empty:
|
||||||
|
if callable(default):
|
||||||
|
try:
|
||||||
|
if hasattr(default, 'set_context'):
|
||||||
|
default.set_context(field)
|
||||||
|
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)
|
||||||
|
default = serializers.empty
|
||||||
|
|
||||||
|
if default is not serializers.empty:
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
default = serializers.empty
|
||||||
|
|
||||||
|
return default
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,34 @@ class ExampleProjectSerializer(serializers.Serializer):
|
||||||
ref_name = 'Project'
|
ref_name = 'Project'
|
||||||
|
|
||||||
|
|
||||||
|
class UnixTimestampField(serializers.DateTimeField):
|
||||||
|
def to_representation(self, value):
|
||||||
|
""" Return epoch time for a datetime object or ``None``"""
|
||||||
|
from django.utils.dateformat import format
|
||||||
|
try:
|
||||||
|
return int(format(value, 'U'))
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def to_internal_value(self, value):
|
||||||
|
import datetime
|
||||||
|
return datetime.datetime.fromtimestamp(int(value))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
swagger_schema_fields = {
|
||||||
|
'format': 'integer',
|
||||||
|
'title': 'Client date time suu',
|
||||||
|
'description': 'Date time in unix timestamp format',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SnippetSerializer(serializers.Serializer):
|
class SnippetSerializer(serializers.Serializer):
|
||||||
"""SnippetSerializer classdoc
|
"""SnippetSerializer classdoc
|
||||||
|
|
||||||
create: docstring for create from serializer classdoc
|
create: docstring for create from serializer classdoc
|
||||||
"""
|
"""
|
||||||
id = serializers.IntegerField(read_only=True, help_text="id serializer help text")
|
id = serializers.IntegerField(read_only=True, help_text="id serializer help text")
|
||||||
|
created = UnixTimestampField(read_only=True)
|
||||||
owner = serializers.PrimaryKeyRelatedField(
|
owner = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=get_user_model().objects.all(),
|
queryset=get_user_model().objects.all(),
|
||||||
default=serializers.CurrentUserDefault(),
|
default=serializers.CurrentUserDefault(),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from drf_yasg.utils import swagger_serializer_method
|
||||||
from snippets.models import Snippet
|
from snippets.models import Snippet
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import typing
|
import typing # noqa: F401
|
||||||
from .method_serializers_with_typing import MethodFieldExampleSerializer
|
from .method_serializers_with_typing import MethodFieldExampleSerializer
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from .method_serializers_without_typing import MethodFieldExampleSerializer
|
from .method_serializers_without_typing import MethodFieldExampleSerializer
|
||||||
|
|
|
||||||
|
|
@ -947,6 +947,12 @@ definitions:
|
||||||
description: id serializer help text
|
description: id serializer help text
|
||||||
type: integer
|
type: integer
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
created:
|
||||||
|
title: Client date time suu
|
||||||
|
type: string
|
||||||
|
format: integer
|
||||||
|
readOnly: true
|
||||||
|
description: Date time in unix timestamp format
|
||||||
owner:
|
owner:
|
||||||
title: Owner
|
title: Owner
|
||||||
description: The ID of the user that created this snippet; if none is provided,
|
description: The ID of the user that created this snippet; if none is provided,
|
||||||
|
|
|
||||||
2
tox.ini
2
tox.ini
|
|
@ -57,7 +57,7 @@ exclude = **/migrations/*
|
||||||
ignore = F405
|
ignore = F405
|
||||||
|
|
||||||
[isort]
|
[isort]
|
||||||
skip = .eggs,.tox,docs,env,venv
|
skip = .eggs,.tox,docs,env,venv,node_modules
|
||||||
skip_glob = **/migrations/*
|
skip_glob = **/migrations/*
|
||||||
not_skip = __init__.py
|
not_skip = __init__.py
|
||||||
atomic = true
|
atomic = true
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue