parent
65aac1da2c
commit
37c00ab3fb
|
|
@ -52,7 +52,7 @@ Changelog
|
|||
- **ADDED:** added ``DEFAULT_GENERATOR_CLASS`` setting and ``--generator-class`` argument to the ``generate_swagger``
|
||||
management command (:issue:`140`)
|
||||
- **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`)
|
||||
|
||||
*********
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ import inspect
|
|||
import logging
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.utils import encoders, json
|
||||
|
||||
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
|
||||
NotHandled = object()
|
||||
|
|
@ -135,6 +134,19 @@ class FieldInspector(BaseInspector):
|
|||
super(FieldInspector, self).__init__(view, path, method, components, request)
|
||||
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):
|
||||
"""Convert a drf Serializer or Field instance into a Swagger object.
|
||||
|
||||
|
|
@ -205,46 +217,29 @@ class FieldInspector(BaseInspector):
|
|||
instance_kwargs['required'] = field.required
|
||||
|
||||
if 'default' not in instance_kwargs and swagger_object_type != openapi.Items:
|
||||
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' 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:
|
||||
default = get_field_default(field)
|
||||
if default not in (None, serializers.empty):
|
||||
instance_kwargs['default'] = default
|
||||
|
||||
if instance_kwargs.get('type', None) != openapi.TYPE_ARRAY:
|
||||
instance_kwargs.setdefault('title', title)
|
||||
if description is not None:
|
||||
instance_kwargs.setdefault('description', description)
|
||||
instance_kwargs.update(kwargs)
|
||||
|
||||
if existing_object is not None:
|
||||
assert isinstance(existing_object, swagger_object_type)
|
||||
for attr, val in sorted(instance_kwargs.items()):
|
||||
setattr(existing_object, attr, val)
|
||||
return existing_object
|
||||
for key, val in sorted(instance_kwargs.items()):
|
||||
setattr(existing_object, key, val)
|
||||
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
|
||||
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
|
||||
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):
|
||||
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
|
||||
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
|
||||
|
||||
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"
|
||||
|
||||
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,
|
||||
ignore_unresolved=True)
|
||||
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
|
||||
return openapi.SchemaRef(definitions, ref_name, ignore_unresolved=True)
|
||||
|
||||
return NotHandled
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from rest_framework import serializers, status
|
|||
from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from rest_framework.request import is_form_media_type
|
||||
from rest_framework.settings import api_settings as rest_framework_settings
|
||||
from rest_framework.utils import encoders, json
|
||||
from rest_framework.views import APIView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -354,3 +355,38 @@ def force_real_str(s, encoding='utf-8', strings_only=False, errors='strict'):
|
|||
s = '' + 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'
|
||||
|
||||
|
||||
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):
|
||||
"""SnippetSerializer classdoc
|
||||
|
||||
create: docstring for create from serializer classdoc
|
||||
"""
|
||||
id = serializers.IntegerField(read_only=True, help_text="id serializer help text")
|
||||
created = UnixTimestampField(read_only=True)
|
||||
owner = serializers.PrimaryKeyRelatedField(
|
||||
queryset=get_user_model().objects.all(),
|
||||
default=serializers.CurrentUserDefault(),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from drf_yasg.utils import swagger_serializer_method
|
|||
from snippets.models import Snippet
|
||||
|
||||
try:
|
||||
import typing
|
||||
import typing # noqa: F401
|
||||
from .method_serializers_with_typing import MethodFieldExampleSerializer
|
||||
except ImportError:
|
||||
from .method_serializers_without_typing import MethodFieldExampleSerializer
|
||||
|
|
|
|||
|
|
@ -947,6 +947,12 @@ definitions:
|
|||
description: id serializer help text
|
||||
type: integer
|
||||
readOnly: true
|
||||
created:
|
||||
title: Client date time suu
|
||||
type: string
|
||||
format: integer
|
||||
readOnly: true
|
||||
description: Date time in unix timestamp format
|
||||
owner:
|
||||
title: Owner
|
||||
description: The ID of the user that created this snippet; if none is provided,
|
||||
|
|
|
|||
Loading…
Reference in New Issue