Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b2ce7d0f9 | |||
| 256a052564 | |||
| cc90bc1544 | |||
| ecee6f8177 | |||
| 408b31fc4f | |||
| a4a11ad1ab |
+1
-1
@@ -36,7 +36,7 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||||||
$ virtualenv venv
|
$ virtualenv venv
|
||||||
$ source venv/bin/activate
|
$ source venv/bin/activate
|
||||||
(venv) $ pip install -e .[validation]
|
(venv) $ pip install -e .[validation]
|
||||||
(venv) $ pip install -rrequirements/dev.txt "Django>=1.11.7"
|
(venv) $ pip install -r requirements/dev.txt
|
||||||
|
|
||||||
#. **Make your changes and check them against the test project**
|
#. **Make your changes and check them against the test project**
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,18 @@ Changelog
|
|||||||
#########
|
#########
|
||||||
|
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.8.0**
|
||||||
|
*********
|
||||||
|
|
||||||
|
*Release date: Jun 01, 2018*
|
||||||
|
|
||||||
|
- **ADDED:** added a :ref:`swagger_schema_fields <swagger_schema_fields>` field on serializer ``Meta`` classes for
|
||||||
|
customizing schema generation (:issue:`132`, :pr:`134`)
|
||||||
|
- **FIXED:** error responses from schema views are now rendered with ``JSONRenderer`` instead of throwing
|
||||||
|
confusing errors (:pr:`130`, :issue:`58`)
|
||||||
|
- **FIXED:** ``readOnly`` schema fields will now no longer be marked as ``required`` (:pr:`133`)
|
||||||
|
|
||||||
*********
|
*********
|
||||||
**1.7.4**
|
**1.7.4**
|
||||||
*********
|
*********
|
||||||
|
|||||||
@@ -169,13 +169,19 @@ You can define some per-serializer options by adding a ``Meta`` class to your se
|
|||||||
class Meta:
|
class Meta:
|
||||||
... options here ...
|
... options here ...
|
||||||
|
|
||||||
Currently, the only option you can add here is
|
.. _swagger_schema_fields:
|
||||||
|
|
||||||
|
The available options are:
|
||||||
|
|
||||||
* ``ref_name`` - a string which will be used as the model definition name for this serializer class; setting it to
|
* ``ref_name`` - a string which will be used as the model definition name for this serializer class; setting it to
|
||||||
``None`` will force the serializer to be generated as an inline model everywhere it is used. If two serializers
|
``None`` will force the serializer to be generated as an inline model everywhere it is used. If two serializers
|
||||||
have the same ``ref_name``, both their usages will be replaced with a reference to the same definition.
|
have the same ``ref_name``, both their usages will be replaced with a reference to the same definition.
|
||||||
If this option is not specified, all serializers have an implicit name derived from their class name, minus any
|
If this option is not specified, all serializers have an implicit name derived from their class name, minus any
|
||||||
``Serializer`` suffix (e.g. ``UserSerializer`` -> ``User``, ``SerializerWithSuffix`` -> ``SerializerWithSuffix``)
|
``Serializer`` suffix (e.g. ``UserSerializer`` -> ``User``, ``SerializerWithSuffix`` -> ``SerializerWithSuffix``)
|
||||||
|
* ``swagger_schema_fields`` - a dictionary mapping :class:`.Schema` field names to values. These attributes
|
||||||
|
will be set on the :class:`.Schema` object generated from the ``Serializer``. Field names must be python values,
|
||||||
|
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>`_.
|
||||||
|
|
||||||
|
|
||||||
*************************
|
*************************
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class EndpointEnumerator(_EndpointEnumerator):
|
|||||||
for method in self.get_allowed_methods(callback):
|
for method in self.get_allowed_methods(callback):
|
||||||
endpoint = (path, method, callback)
|
endpoint = (path, method, callback)
|
||||||
api_endpoints.append(endpoint)
|
api_endpoints.append(endpoint)
|
||||||
except Exception:
|
except Exception: # pragma: no cover
|
||||||
logger.warning('failed to enumerate view', exc_info=True)
|
logger.warning('failed to enumerate view', exc_info=True)
|
||||||
|
|
||||||
elif isinstance(pattern, URLResolver):
|
elif isinstance(pattern, URLResolver):
|
||||||
@@ -367,6 +367,9 @@ class OpenAPISchemaGenerator(object):
|
|||||||
|
|
||||||
view_inspector = view_inspector_cls(view, path, method, components, request, overrides)
|
view_inspector = view_inspector_cls(view, path, method, components, request, overrides)
|
||||||
operation = view_inspector.get_operation(operation_keys)
|
operation = view_inspector.get_operation(operation_keys)
|
||||||
|
if operation is None:
|
||||||
|
return None
|
||||||
|
|
||||||
if 'consumes' in operation and set(operation.consumes) == set(self.consumes):
|
if 'consumes' in operation and set(operation.consumes) == set(self.consumes):
|
||||||
del operation.consumes
|
del operation.consumes
|
||||||
if 'produces' in operation and set(operation.produces) == set(self.produces):
|
if 'produces' in operation and set(operation.produces) == set(self.produces):
|
||||||
|
|||||||
@@ -22,12 +22,39 @@ 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)
|
result = self.probe_field_inspectors(serializer, openapi.Schema, self.use_definitions)
|
||||||
|
schema = openapi.resolve_ref(result, self.components)
|
||||||
|
self.add_manual_fields(serializer, schema)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def add_manual_parameters(self, serializer, parameters):
|
||||||
|
"""Add/replace parameters from the given list of automatically generated request parameters. This method
|
||||||
|
is called only when the serializer is converted into a list of parameters for use in a form data request.
|
||||||
|
|
||||||
|
:param serializer: serializer instance
|
||||||
|
:param list[openapi.Parameter] parameters: genereated parameters
|
||||||
|
:return: modified parameters
|
||||||
|
:rtype: list[openapi.Parameter]
|
||||||
|
"""
|
||||||
|
return parameters
|
||||||
|
|
||||||
def get_request_parameters(self, serializer, in_):
|
def get_request_parameters(self, serializer, in_):
|
||||||
fields = getattr(serializer, 'fields', {})
|
fields = getattr(serializer, 'fields', {})
|
||||||
return [
|
parameters = [
|
||||||
self.probe_field_inspectors(
|
self.probe_field_inspectors(
|
||||||
value, openapi.Parameter, self.use_definitions,
|
value, openapi.Parameter, self.use_definitions,
|
||||||
name=self.get_parameter_name(key), in_=in_
|
name=self.get_parameter_name(key), in_=in_
|
||||||
@@ -36,12 +63,17 @@ class InlineSerializerInspector(SerializerInspector):
|
|||||||
in fields.items()
|
in fields.items()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
return self.add_manual_parameters(serializer, parameters)
|
||||||
|
|
||||||
def get_property_name(self, field_name):
|
def get_property_name(self, field_name):
|
||||||
return field_name
|
return field_name
|
||||||
|
|
||||||
def get_parameter_name(self, field_name):
|
def get_parameter_name(self, field_name):
|
||||||
return field_name
|
return field_name
|
||||||
|
|
||||||
|
def get_serializer_ref_name(self, serializer):
|
||||||
|
return get_serializer_ref_name(serializer)
|
||||||
|
|
||||||
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):
|
||||||
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
|
||||||
|
|
||||||
@@ -55,7 +87,7 @@ class InlineSerializerInspector(SerializerInspector):
|
|||||||
if swagger_object_type != openapi.Schema:
|
if swagger_object_type != openapi.Schema:
|
||||||
raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)
|
raise SwaggerGenerationError("cannot instantiate nested serializer as " + swagger_object_type.__name__)
|
||||||
|
|
||||||
ref_name = get_serializer_ref_name(field)
|
ref_name = self.get_serializer_ref_name(field)
|
||||||
|
|
||||||
def make_schema_definition():
|
def make_schema_definition():
|
||||||
properties = OrderedDict()
|
properties = OrderedDict()
|
||||||
@@ -67,10 +99,12 @@ class InlineSerializerInspector(SerializerInspector):
|
|||||||
}
|
}
|
||||||
prop_kwargs = filter_none(prop_kwargs)
|
prop_kwargs = filter_none(prop_kwargs)
|
||||||
|
|
||||||
properties[property_name] = self.probe_field_inspectors(
|
child_schema = self.probe_field_inspectors(
|
||||||
child, ChildSwaggerType, use_references, **prop_kwargs
|
child, ChildSwaggerType, use_references, **prop_kwargs
|
||||||
)
|
)
|
||||||
if child.required:
|
properties[property_name] = child_schema
|
||||||
|
|
||||||
|
if child.required and not getattr(child_schema, 'read_only', False):
|
||||||
required.append(property_name)
|
required.append(property_name)
|
||||||
|
|
||||||
result = SwaggerType(
|
result = SwaggerType(
|
||||||
@@ -527,7 +561,7 @@ else:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from rest_framework_recursive.fields import RecursiveField
|
from rest_framework_recursive.fields import RecursiveField
|
||||||
except ImportError:
|
except ImportError: # pragma: no cover
|
||||||
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)"""
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.shortcuts import render, resolve_url
|
from django.shortcuts import render, resolve_url
|
||||||
from rest_framework.renderers import BaseRenderer, TemplateHTMLRenderer
|
from rest_framework.renderers import BaseRenderer, JSONRenderer, TemplateHTMLRenderer
|
||||||
from rest_framework.utils import json
|
from rest_framework.utils import json
|
||||||
|
|
||||||
from drf_yasg.openapi import Swagger
|
from drf_yasg.openapi import Swagger
|
||||||
@@ -10,7 +10,7 @@ from .codecs import VALIDATORS, OpenAPICodecJson, OpenAPICodecYaml
|
|||||||
|
|
||||||
class _SpecRenderer(BaseRenderer):
|
class _SpecRenderer(BaseRenderer):
|
||||||
"""Base class for text renderers. Handles encoding and validation."""
|
"""Base class for text renderers. Handles encoding and validation."""
|
||||||
charset = None
|
charset = 'utf-8'
|
||||||
validators = []
|
validators = []
|
||||||
codec_class = None
|
codec_class = None
|
||||||
|
|
||||||
@@ -22,6 +22,12 @@ class _SpecRenderer(BaseRenderer):
|
|||||||
def render(self, data, media_type=None, renderer_context=None):
|
def render(self, data, media_type=None, renderer_context=None):
|
||||||
assert self.codec_class, "must override codec_class"
|
assert self.codec_class, "must override codec_class"
|
||||||
codec = self.codec_class(self.validators)
|
codec = self.codec_class(self.validators)
|
||||||
|
|
||||||
|
if not isinstance(data, Swagger): # pragma: no cover
|
||||||
|
# if `swagger` is not a ``Swagger`` object, it means we somehow got a non-success ``Response``
|
||||||
|
# in that case, it's probably better to let the default ``TemplateHTMLRenderer`` render it
|
||||||
|
# see https://github.com/axnsan12/drf-yasg/issues/58
|
||||||
|
return JSONRenderer().render(data, media_type, renderer_context)
|
||||||
return codec.encode(data)
|
return codec.encode(data)
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +59,7 @@ class _UIRenderer(BaseRenderer):
|
|||||||
template = ''
|
template = ''
|
||||||
|
|
||||||
def render(self, swagger, accepted_media_type=None, renderer_context=None):
|
def render(self, swagger, accepted_media_type=None, renderer_context=None):
|
||||||
if not isinstance(swagger, Swagger):
|
if not isinstance(swagger, Swagger): # pragma: no cover
|
||||||
# if `swagger` is not a ``Swagger`` object, it means we somehow got a non-success ``Response``
|
# if `swagger` is not a ``Swagger`` object, it means we somehow got a non-success ``Response``
|
||||||
# in that case, it's probably better to let the default ``TemplateHTMLRenderer`` render it
|
# in that case, it's probably better to let the default ``TemplateHTMLRenderer`` render it
|
||||||
# see https://github.com/axnsan12/drf-yasg/issues/58
|
# see https://github.com/axnsan12/drf-yasg/issues/58
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class ArticleSerializer(serializers.ModelSerializer):
|
|||||||
read_only=True,
|
read_only=True,
|
||||||
)
|
)
|
||||||
uuid = serializers.UUIDField(help_text="should articles have UUIDs?", read_only=True)
|
uuid = serializers.UUIDField(help_text="should articles have UUIDs?", read_only=True)
|
||||||
cover_name = serializers.FileField(use_url=False, source='cover', read_only=True)
|
cover_name = serializers.FileField(use_url=False, source='cover', required=True)
|
||||||
group = serializers.SlugRelatedField(slug_field='uuid', queryset=ArticleGroup.objects.all())
|
group = serializers.SlugRelatedField(slug_field='uuid', queryset=ArticleGroup.objects.all())
|
||||||
original_group = serializers.SlugRelatedField(slug_field='uuid', read_only=True)
|
original_group = serializers.SlugRelatedField(slug_field='uuid', read_only=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework_recursive.fields import RecursiveField
|
from rest_framework_recursive.fields import RecursiveField
|
||||||
@@ -26,6 +28,15 @@ class TodoYetAnotherSerializer(serializers.ModelSerializer):
|
|||||||
model = TodoYetAnother
|
model = TodoYetAnother
|
||||||
fields = ('title', 'todo')
|
fields = ('title', 'todo')
|
||||||
depth = 2
|
depth = 2
|
||||||
|
swagger_schema_fields = {
|
||||||
|
'example': OrderedDict([
|
||||||
|
('title', 'parent'),
|
||||||
|
('todo', OrderedDict([
|
||||||
|
('title', 'child'),
|
||||||
|
('todo', None),
|
||||||
|
])),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TodoTreeSerializer(serializers.ModelSerializer):
|
class TodoTreeSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -1578,6 +1578,11 @@ definitions:
|
|||||||
minLength: 1
|
minLength: 1
|
||||||
readOnly: true
|
readOnly: true
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
example:
|
||||||
|
title: parent
|
||||||
|
todo:
|
||||||
|
title: child
|
||||||
|
todo: null
|
||||||
UserSerializerrr:
|
UserSerializerrr:
|
||||||
required:
|
required:
|
||||||
- username
|
- username
|
||||||
|
|||||||
Reference in New Issue
Block a user