parent
1dd7cfe043
commit
748b5d3c2f
|
|
@ -4,9 +4,22 @@ Changelog
|
||||||
|
|
||||||
|
|
||||||
**********
|
**********
|
||||||
**1.9.2**
|
**1.10.0**
|
||||||
**********
|
**********
|
||||||
|
|
||||||
|
*Release date: TBD, 2018*
|
||||||
|
|
||||||
|
- **IMPROVED:** added support for ``SerializerMethodField``, via the ``swagger_serializer_method`` decorator for the
|
||||||
|
method field, and support for Python 3.5 style type hinting of the method field return type
|
||||||
|
(:issue:`137`, :pr:`175`, :pr:`179`)
|
||||||
|
|
||||||
|
*NOTE:* in order for this to work, you will have to add the new ``drf_yasg.inspectors.SerializerMethodFieldInspector``
|
||||||
|
to your ``DEFAULT_FIELD_INSPECTORS`` array if you changed it from the default value
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.9.2**
|
||||||
|
*********
|
||||||
|
|
||||||
*Release date: Aug 03, 2018*
|
*Release date: Aug 03, 2018*
|
||||||
|
|
||||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.17.6
|
- **IMPROVED:** updated ``swagger-ui`` to version 3.17.6
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,49 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
||||||
replacing/decorating methods on the base class itself.
|
replacing/decorating methods on the base class itself.
|
||||||
|
|
||||||
|
|
||||||
|
*********************************
|
||||||
|
Support for SerializerMethodField
|
||||||
|
*********************************
|
||||||
|
|
||||||
|
Schema generation of ``serializers.SerializerMethodField`` supported in two ways:
|
||||||
|
|
||||||
|
1) The decorator ``swagger_serializer_method(serializer)`` for the use case where the serializer method
|
||||||
|
is using a serializer. e.g.:
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from drf_yasg.utils import swagger_serializer_method
|
||||||
|
|
||||||
|
|
||||||
|
class OtherStuffSerializer(serializers.Serializer):
|
||||||
|
foo = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class ParentSerializer(serializers.Serializer):
|
||||||
|
|
||||||
|
other_stuff = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=OtherStuffSerializer)
|
||||||
|
def get_other_stuff(self, obj):
|
||||||
|
return OtherStuffSerializer().data
|
||||||
|
|
||||||
|
|
||||||
|
Note that the serializer parameter can be either be a serializer class or instance
|
||||||
|
|
||||||
|
|
||||||
|
2) For simple cases where the method is returning one of the supported types,
|
||||||
|
`Python 3 type hinting`_ of the serializer method return value can be used. e.g.:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class SomeSerializer(serializers.Serializer):
|
||||||
|
|
||||||
|
some_number = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_some_number(self, obj) -> float:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
********************************
|
********************************
|
||||||
Serializer ``Meta`` nested class
|
Serializer ``Meta`` nested class
|
||||||
********************************
|
********************************
|
||||||
|
|
@ -333,3 +376,6 @@ A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``t
|
||||||
|
|
||||||
Another caveat that stems from this is that any serializer named "``NestedSerializer``" will be forced inline
|
Another caveat that stems from this is that any serializer named "``NestedSerializer``" will be forced inline
|
||||||
unless it has a ``ref_name`` set explicitly.
|
unless it has a ``ref_name`` set explicitly.
|
||||||
|
|
||||||
|
|
||||||
|
.. _Python 3 type hinting: https://docs.python.org/3/library/typing.html
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ to this list.
|
||||||
:class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \
|
||||||
:class:`'drf_yasg.inspectors.HiddenFieldInspector' <.inspectors.HiddenFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.HiddenFieldInspector' <.inspectors.HiddenFieldInspector>`, |br| \
|
||||||
:class:`'drf_yasg.inspectors.RecursiveFieldInspector' <.inspectors.RecursiveFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.RecursiveFieldInspector' <.inspectors.RecursiveFieldInspector>`, |br| \
|
||||||
|
:class:`'drf_yasg.inspectors.SerializerMethodFieldInspector' <.inspectors.SerializerMethodFieldInspector>`, |br| \
|
||||||
:class:`'drf_yasg.inspectors.SimpleFieldInspector' <.inspectors.SimpleFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.SimpleFieldInspector' <.inspectors.SimpleFieldInspector>`, |br| \
|
||||||
:class:`'drf_yasg.inspectors.StringDefaultFieldInspector' <.inspectors.StringDefaultFieldInspector>`, |br| \
|
:class:`'drf_yasg.inspectors.StringDefaultFieldInspector' <.inspectors.StringDefaultFieldInspector>`, |br| \
|
||||||
``]``
|
``]``
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ SWAGGER_DEFAULTS = {
|
||||||
'drf_yasg.inspectors.DictFieldInspector',
|
'drf_yasg.inspectors.DictFieldInspector',
|
||||||
'drf_yasg.inspectors.HiddenFieldInspector',
|
'drf_yasg.inspectors.HiddenFieldInspector',
|
||||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||||
|
'drf_yasg.inspectors.SerializerMethodFieldInspector',
|
||||||
'drf_yasg.inspectors.SimpleFieldInspector',
|
'drf_yasg.inspectors.SimpleFieldInspector',
|
||||||
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from .base import (
|
||||||
from .field import (
|
from .field import (
|
||||||
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
|
||||||
InlineSerializerInspector, RecursiveFieldInspector, ReferencingSerializerInspector, RelatedFieldInspector,
|
InlineSerializerInspector, RecursiveFieldInspector, ReferencingSerializerInspector, RelatedFieldInspector,
|
||||||
SimpleFieldInspector, StringDefaultFieldInspector
|
SerializerMethodFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector
|
||||||
)
|
)
|
||||||
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
||||||
from .view import SwaggerAutoSchema
|
from .view import SwaggerAutoSchema
|
||||||
|
|
@ -25,7 +25,7 @@ __all__ = [
|
||||||
# field inspectors
|
# field inspectors
|
||||||
'InlineSerializerInspector', 'RecursiveFieldInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector',
|
'InlineSerializerInspector', 'RecursiveFieldInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector',
|
||||||
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector',
|
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector',
|
||||||
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector',
|
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector', 'SerializerMethodFieldInspector',
|
||||||
|
|
||||||
# view inspectors
|
# view inspectors
|
||||||
'SwaggerAutoSchema',
|
'SwaggerAutoSchema',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
import datetime
|
||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
|
import uuid
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
@ -13,6 +16,12 @@ from ..errors import SwaggerGenerationError
|
||||||
from ..utils import decimal_as_float, filter_none, get_serializer_ref_name
|
from ..utils import decimal_as_float, filter_none, get_serializer_ref_name
|
||||||
from .base import FieldInspector, NotHandled, SerializerInspector
|
from .base import FieldInspector, NotHandled, SerializerInspector
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Python>=3.5
|
||||||
|
import typing
|
||||||
|
except ImportError:
|
||||||
|
typing = None
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -409,6 +418,118 @@ def get_basic_type_info(field):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def decimal_return_type():
|
||||||
|
return openapi.TYPE_STRING if rest_framework_settings.COERCE_DECIMAL_TO_STRING else openapi.TYPE_NUMBER
|
||||||
|
|
||||||
|
|
||||||
|
raw_type_info = [
|
||||||
|
(bool, (openapi.TYPE_BOOLEAN, None)),
|
||||||
|
(int, (openapi.TYPE_INTEGER, None)),
|
||||||
|
(float, (openapi.TYPE_NUMBER, None)),
|
||||||
|
(Decimal, (decimal_return_type, openapi.FORMAT_DECIMAL)),
|
||||||
|
(uuid.UUID, (openapi.TYPE_STRING, openapi.FORMAT_UUID)),
|
||||||
|
(datetime.datetime, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)),
|
||||||
|
(datetime.date, (openapi.TYPE_STRING, openapi.FORMAT_DATE)),
|
||||||
|
# TODO - support typing.List etc
|
||||||
|
]
|
||||||
|
|
||||||
|
hinting_type_info = raw_type_info
|
||||||
|
|
||||||
|
|
||||||
|
def get_basic_type_info_from_hint(hint_class):
|
||||||
|
"""Given a class (eg from a SerializerMethodField's return type hint,
|
||||||
|
return its basic type information - ``type``, ``format``, ``pattern``,
|
||||||
|
and any applicable min/max limit values.
|
||||||
|
|
||||||
|
:param hint_class: the class
|
||||||
|
:return: the extracted attributes as a dictionary, or ``None`` if the field type is not known
|
||||||
|
:rtype: OrderedDict
|
||||||
|
"""
|
||||||
|
|
||||||
|
for check_class, type_format in hinting_type_info:
|
||||||
|
if issubclass(hint_class, check_class):
|
||||||
|
swagger_type, format = type_format
|
||||||
|
if callable(swagger_type):
|
||||||
|
swagger_type = swagger_type()
|
||||||
|
# if callable(format):
|
||||||
|
# format = format(klass)
|
||||||
|
break
|
||||||
|
else: # pragma: no cover
|
||||||
|
return None
|
||||||
|
|
||||||
|
pattern = None
|
||||||
|
|
||||||
|
result = OrderedDict([
|
||||||
|
('type', swagger_type),
|
||||||
|
('format', format),
|
||||||
|
('pattern', pattern)
|
||||||
|
])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class SerializerMethodFieldInspector(FieldInspector):
|
||||||
|
"""Provides conversion for SerializerMethodField, optionally using information from the swagger_method_field
|
||||||
|
decorator
|
||||||
|
"""
|
||||||
|
|
||||||
|
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||||
|
if not isinstance(field, serializers.SerializerMethodField):
|
||||||
|
return NotHandled
|
||||||
|
|
||||||
|
method = getattr(field.parent, field.method_name)
|
||||||
|
if method is None:
|
||||||
|
return NotHandled
|
||||||
|
|
||||||
|
serializer = getattr(method, "_swagger_serializer", None)
|
||||||
|
|
||||||
|
if serializer:
|
||||||
|
# attribute added by the swagger_serializer_method decorator
|
||||||
|
serializer = getattr(method, '_swagger_serializer', None)
|
||||||
|
|
||||||
|
# in order of preference for description, use:
|
||||||
|
# 1) field.help_text from SerializerMethodField(help_text)
|
||||||
|
# 2) serializer.help_text from swagger_serializer_method(serializer)
|
||||||
|
# 3) method's docstring
|
||||||
|
description = field.help_text
|
||||||
|
if description is None:
|
||||||
|
description = getattr(serializer, 'help_text', None)
|
||||||
|
if description is None:
|
||||||
|
description = method.__doc__
|
||||||
|
|
||||||
|
label = field.label
|
||||||
|
if label is None:
|
||||||
|
label = getattr(serializer, 'label', None)
|
||||||
|
|
||||||
|
if inspect.isclass(serializer):
|
||||||
|
serializer_kwargs = {
|
||||||
|
"help_text": description,
|
||||||
|
"label": label,
|
||||||
|
"read_only": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer = method._swagger_serializer(**serializer_kwargs)
|
||||||
|
else:
|
||||||
|
serializer.help_text = description
|
||||||
|
serializer.label = label
|
||||||
|
serializer.read_only = True
|
||||||
|
|
||||||
|
return self.probe_field_inspectors(serializer, swagger_object_type, use_references, read_only=True)
|
||||||
|
elif typing:
|
||||||
|
# look for Python 3.5+ style type hinting of the return value
|
||||||
|
hint_class = inspect.signature(method).return_annotation
|
||||||
|
|
||||||
|
if not issubclass(hint_class, inspect._empty):
|
||||||
|
type_info = get_basic_type_info_from_hint(hint_class)
|
||||||
|
|
||||||
|
if type_info is not None:
|
||||||
|
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type,
|
||||||
|
use_references, **kwargs)
|
||||||
|
return SwaggerType(**type_info)
|
||||||
|
|
||||||
|
return NotHandled
|
||||||
|
|
||||||
|
|
||||||
class SimpleFieldInspector(FieldInspector):
|
class SimpleFieldInspector(FieldInspector):
|
||||||
"""Provides conversions for fields which can be described using just ``type``, ``format``, ``pattern``
|
"""Provides conversions for fields which can be described using just ``type``, ``format``, ``pattern``
|
||||||
and min/max validators.
|
and min/max validators.
|
||||||
|
|
@ -531,6 +652,7 @@ else:
|
||||||
"""Hack to force ``djangorestframework_camel_case`` to camelize a plain string."""
|
"""Hack to force ``djangorestframework_camel_case`` to camelize a plain string."""
|
||||||
return next(iter(camelize({s: ''})))
|
return next(iter(camelize({s: ''})))
|
||||||
|
|
||||||
|
|
||||||
def camelize_schema(schema_or_ref, components):
|
def camelize_schema(schema_or_ref, components):
|
||||||
"""Recursively camelize property names for the given schema using ``djangorestframework_camel_case``."""
|
"""Recursively camelize property names for the given schema using ``djangorestframework_camel_case``."""
|
||||||
schema = openapi.resolve_ref(schema_or_ref, components)
|
schema = openapi.resolve_ref(schema_or_ref, components)
|
||||||
|
|
@ -545,6 +667,7 @@ else:
|
||||||
|
|
||||||
return schema_or_ref
|
return schema_or_ref
|
||||||
|
|
||||||
|
|
||||||
class CamelCaseJSONFilter(FieldInspector):
|
class CamelCaseJSONFilter(FieldInspector):
|
||||||
"""Converts property names to camelCase if ``CamelCaseJSONParser`` or ``CamelCaseJSONRenderer`` are used."""
|
"""Converts property names to camelCase if ``CamelCaseJSONParser`` or ``CamelCaseJSONRenderer`` are used."""
|
||||||
|
|
||||||
|
|
@ -569,6 +692,7 @@ except ImportError: # pragma: no cover
|
||||||
else:
|
else:
|
||||||
class RecursiveFieldInspector(FieldInspector):
|
class RecursiveFieldInspector(FieldInspector):
|
||||||
"""Provides conversion for RecursiveField (https://github.com/heywbj/django-rest-framework-recursive)"""
|
"""Provides conversion for RecursiveField (https://github.com/heywbj/django-rest-framework-recursive)"""
|
||||||
|
|
||||||
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
|
||||||
if isinstance(field, RecursiveField) and swagger_object_type == openapi.Schema:
|
if isinstance(field, RecursiveField) and swagger_object_type == openapi.Schema:
|
||||||
assert use_references is True, "Can not create schema for RecursiveField when use_references is False"
|
assert use_references is True, "Can not create schema for RecursiveField when use_references is False"
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,23 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def swagger_serializer_method(serializer):
|
||||||
|
"""
|
||||||
|
Decorates the method of a serializers.SerializerMethodField
|
||||||
|
to hint as to how Swagger should be generated for this field.
|
||||||
|
|
||||||
|
:param serializer: serializer class or instance
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(serializer_method):
|
||||||
|
# stash the serializer for SerializerMethodFieldInspector to find
|
||||||
|
serializer_method._swagger_serializer = serializer
|
||||||
|
return serializer_method
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def is_list_view(path, method, view):
|
def is_list_view(path, method, view):
|
||||||
"""Check if the given path/method appears to represent a list view (as opposed to a detail/instance view).
|
"""Check if the given path/method appears to represent a list view (as opposed to a detail/instance view).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class Unknown(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MethodFieldExampleSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Implementation of SerializerMethodField using type hinting for Python >= 3.5
|
||||||
|
"""
|
||||||
|
|
||||||
|
hinted_bool = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a bool")
|
||||||
|
|
||||||
|
def get_hinted_bool(self, obj) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
hinted_int = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be an integer")
|
||||||
|
|
||||||
|
def get_hinted_int(self, obj) -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
hinted_float = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a number")
|
||||||
|
|
||||||
|
def get_hinted_float(self, obj) -> float:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
hinted_decimal = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a decimal")
|
||||||
|
|
||||||
|
def get_hinted_decimal(self, obj) -> decimal.Decimal:
|
||||||
|
return decimal.Decimal(1)
|
||||||
|
|
||||||
|
hinted_datetime = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a datetime")
|
||||||
|
|
||||||
|
def get_hinted_datetime(self, obj) -> datetime.datetime:
|
||||||
|
return datetime.datetime.now()
|
||||||
|
|
||||||
|
hinted_date = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a date")
|
||||||
|
|
||||||
|
def get_hinted_date(self, obj) -> datetime.date:
|
||||||
|
return datetime.date.today()
|
||||||
|
|
||||||
|
hinted_uuid = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a uuid")
|
||||||
|
|
||||||
|
def get_hinted_uuid(self, obj) -> uuid.UUID:
|
||||||
|
return uuid.uuid4()
|
||||||
|
|
||||||
|
hinted_unknown = serializers.SerializerMethodField(
|
||||||
|
help_text="type hint is unknown, so is expected to fallback to string")
|
||||||
|
|
||||||
|
def get_hinted_unknown(self, obj) -> Unknown:
|
||||||
|
return Unknown()
|
||||||
|
|
||||||
|
non_hinted_number = serializers.SerializerMethodField(
|
||||||
|
help_text="No hint on the method, so this is expected to fallback to string")
|
||||||
|
|
||||||
|
def get_non_hinted_number(self, obj):
|
||||||
|
return 1.0
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from drf_yasg.utils import swagger_serializer_method
|
||||||
|
|
||||||
|
|
||||||
|
class Unknown(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MethodFieldExampleSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Fallback implementation of SerializerMethodField type hinting for Python < 3.5
|
||||||
|
|
||||||
|
`->` syntax isn't supported, instead decorate with a serializer that returns the same type
|
||||||
|
a bit of a hack, but it provides a cross-check between hinting and decorator functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
hinted_bool = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a bool")
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=serializers.BooleanField)
|
||||||
|
def get_hinted_bool(self, obj):
|
||||||
|
return True
|
||||||
|
|
||||||
|
hinted_int = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be an integer")
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=serializers.IntegerField)
|
||||||
|
def get_hinted_int(self, obj):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
hinted_float = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a number")
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=serializers.FloatField)
|
||||||
|
def get_hinted_float(self, obj):
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
hinted_decimal = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a decimal")
|
||||||
|
|
||||||
|
# note that in this case an instance is required since DecimalField has required arguments
|
||||||
|
@swagger_serializer_method(serializer=serializers.DecimalField(max_digits=6, decimal_places=4))
|
||||||
|
def get_hinted_decimal(self, obj):
|
||||||
|
return decimal.Decimal(1)
|
||||||
|
|
||||||
|
hinted_datetime = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a datetime")
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=serializers.DateTimeField)
|
||||||
|
def get_hinted_datetime(self, obj):
|
||||||
|
return datetime.datetime.now()
|
||||||
|
|
||||||
|
hinted_date = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a date")
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=serializers.DateField)
|
||||||
|
def get_hinted_date(self, obj):
|
||||||
|
return datetime.date.today()
|
||||||
|
|
||||||
|
hinted_uuid = serializers.SerializerMethodField(
|
||||||
|
help_text="the type hint on the method should determine this to be a uuid")
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=serializers.UUIDField)
|
||||||
|
def get_hinted_uuid(self, obj):
|
||||||
|
return uuid.uuid4()
|
||||||
|
|
||||||
|
hinted_unknown = serializers.SerializerMethodField(
|
||||||
|
help_text="type hint is unknown, so is expected to fallback to string")
|
||||||
|
|
||||||
|
def get_hinted_unknown(self, obj):
|
||||||
|
return Unknown()
|
||||||
|
|
||||||
|
non_hinted_number = serializers.SerializerMethodField(
|
||||||
|
help_text="No hint on the method, so this is expected to fallback to string")
|
||||||
|
|
||||||
|
def get_non_hinted_number(self, obj):
|
||||||
|
return 1.0
|
||||||
|
|
@ -1,8 +1,19 @@
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from drf_yasg.utils import swagger_serializer_method
|
||||||
from snippets.models import Snippet
|
from snippets.models import Snippet
|
||||||
|
|
||||||
|
try:
|
||||||
|
import typing
|
||||||
|
from .method_serializers_with_typing import MethodFieldExampleSerializer
|
||||||
|
except ImportError:
|
||||||
|
from .method_serializers_without_typing import MethodFieldExampleSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class OtherStuffSerializer(serializers.Serializer):
|
||||||
|
foo = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
class UserSerializerrr(serializers.ModelSerializer):
|
class UserSerializerrr(serializers.ModelSerializer):
|
||||||
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
|
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
|
||||||
|
|
@ -10,10 +21,61 @@ class UserSerializerrr(serializers.ModelSerializer):
|
||||||
last_connected_ip = serializers.IPAddressField(help_text="i'm out of ideas", protocol='ipv4', read_only=True)
|
last_connected_ip = serializers.IPAddressField(help_text="i'm out of ideas", protocol='ipv4', read_only=True)
|
||||||
last_connected_at = serializers.DateField(help_text="really?", read_only=True)
|
last_connected_at = serializers.DateField(help_text="really?", read_only=True)
|
||||||
|
|
||||||
|
other_stuff = serializers.SerializerMethodField(
|
||||||
|
help_text="the decorator should determine the serializer class for this")
|
||||||
|
|
||||||
|
hint_example = MethodFieldExampleSerializer()
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=OtherStuffSerializer)
|
||||||
|
def get_other_stuff(self, obj):
|
||||||
|
"""
|
||||||
|
method_field that uses a serializer internally.
|
||||||
|
|
||||||
|
By using the decorator, we can tell drf-yasg how to represent this in Swagger
|
||||||
|
:param obj:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return OtherStuffSerializer().data
|
||||||
|
|
||||||
|
help_text_example_1 = serializers.SerializerMethodField(
|
||||||
|
help_text="help text on field is set, so this should appear in swagger"
|
||||||
|
)
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=serializers.IntegerField(
|
||||||
|
help_text="decorated instance help_text shouldn't appear in swagger because field has priority"))
|
||||||
|
def get_help_text_example_1(self):
|
||||||
|
"""
|
||||||
|
method docstring shouldn't appear in swagger because field has priority
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return 1
|
||||||
|
|
||||||
|
help_text_example_2 = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=serializers.IntegerField(
|
||||||
|
help_text="instance help_text is set, so should appear in swagger"))
|
||||||
|
def get_help_text_example_2(self):
|
||||||
|
"""
|
||||||
|
method docstring shouldn't appear in swagger because decorator has priority
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return 1
|
||||||
|
|
||||||
|
help_text_example_3 = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@swagger_serializer_method(serializer=serializers.IntegerField())
|
||||||
|
def get_help_text_example_3(self):
|
||||||
|
"""
|
||||||
|
docstring is set so should appear in swagger as fallback
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return 1
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('id', 'username', 'email', 'articles', 'snippets',
|
fields = ('id', 'username', 'email', 'articles', 'snippets',
|
||||||
'last_connected_ip', 'last_connected_at', 'article_slugs')
|
'last_connected_ip', 'last_connected_at', 'article_slugs', 'other_stuff', 'hint_example',
|
||||||
|
'help_text_example_1', 'help_text_example_2', 'help_text_example_3')
|
||||||
|
|
||||||
|
|
||||||
class UserListQuerySerializer(serializers.Serializer):
|
class UserListQuerySerializer(serializers.Serializer):
|
||||||
|
|
|
||||||
|
|
@ -1582,11 +1582,77 @@ definitions:
|
||||||
todo:
|
todo:
|
||||||
title: child
|
title: child
|
||||||
todo: null
|
todo: null
|
||||||
|
OtherStuff:
|
||||||
|
title: Other stuff
|
||||||
|
description: the decorator should determine the serializer class for this
|
||||||
|
required:
|
||||||
|
- foo
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
foo:
|
||||||
|
title: Foo
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
readOnly: true
|
||||||
|
MethodFieldExample:
|
||||||
|
title: Hint example
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
hinted_bool:
|
||||||
|
title: Hinted bool
|
||||||
|
description: the type hint on the method should determine this to be a bool
|
||||||
|
type: boolean
|
||||||
|
readOnly: true
|
||||||
|
hinted_int:
|
||||||
|
title: Hinted int
|
||||||
|
description: the type hint on the method should determine this to be an integer
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
hinted_float:
|
||||||
|
title: Hinted float
|
||||||
|
description: the type hint on the method should determine this to be a number
|
||||||
|
type: number
|
||||||
|
readOnly: true
|
||||||
|
hinted_decimal:
|
||||||
|
title: Hinted decimal
|
||||||
|
description: the type hint on the method should determine this to be a decimal
|
||||||
|
type: string
|
||||||
|
format: decimal
|
||||||
|
readOnly: true
|
||||||
|
hinted_datetime:
|
||||||
|
title: Hinted datetime
|
||||||
|
description: the type hint on the method should determine this to be a datetime
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
readOnly: true
|
||||||
|
hinted_date:
|
||||||
|
title: Hinted date
|
||||||
|
description: the type hint on the method should determine this to be a date
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
readOnly: true
|
||||||
|
hinted_uuid:
|
||||||
|
title: Hinted uuid
|
||||||
|
description: the type hint on the method should determine this to be a uuid
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
hinted_unknown:
|
||||||
|
title: Hinted unknown
|
||||||
|
description: type hint is unknown, so is expected to fallback to string
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
non_hinted_number:
|
||||||
|
title: Non hinted number
|
||||||
|
description: No hint on the method, so this is expected to fallback to string
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
UserSerializerrr:
|
UserSerializerrr:
|
||||||
required:
|
required:
|
||||||
- username
|
- username
|
||||||
- articles
|
- articles
|
||||||
- snippets
|
- snippets
|
||||||
|
- hint_example
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
|
|
@ -1637,3 +1703,23 @@ definitions:
|
||||||
pattern: ^[-a-zA-Z0-9_]+$
|
pattern: ^[-a-zA-Z0-9_]+$
|
||||||
readOnly: true
|
readOnly: true
|
||||||
uniqueItems: true
|
uniqueItems: true
|
||||||
|
other_stuff:
|
||||||
|
$ref: '#/definitions/OtherStuff'
|
||||||
|
hint_example:
|
||||||
|
$ref: '#/definitions/MethodFieldExample'
|
||||||
|
help_text_example_1:
|
||||||
|
title: Help text example 1
|
||||||
|
description: help text on field is set, so this should appear in swagger
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
help_text_example_2:
|
||||||
|
title: Help text example 2
|
||||||
|
description: instance help_text is set, so should appear in swagger
|
||||||
|
type: integer
|
||||||
|
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 "
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
|
|
||||||
2
tox.ini
2
tox.ini
|
|
@ -33,12 +33,14 @@ commands =
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps =
|
deps =
|
||||||
|
-rrequirements/setup.txt
|
||||||
-rrequirements/lint.txt
|
-rrequirements/lint.txt
|
||||||
commands =
|
commands =
|
||||||
flake8 src/drf_yasg testproj tests setup.py
|
flake8 src/drf_yasg testproj tests setup.py
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
deps =
|
deps =
|
||||||
|
-rrequirements/setup.txt
|
||||||
-rrequirements/docs.txt
|
-rrequirements/docs.txt
|
||||||
commands =
|
commands =
|
||||||
python setup.py check --restructuredtext --metadata --strict
|
python setup.py check --restructuredtext --metadata --strict
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue