304 lines
11 KiB
ReStructuredText
304 lines
11 KiB
ReStructuredText
.. |br| raw:: html
|
|
|
|
<br />
|
|
|
|
########################
|
|
Custom schema generation
|
|
########################
|
|
|
|
If the default spec generation does not quite match what you were hoping to achieve, ``drf-yasg`` provides some
|
|
custom behavior hooks by default.
|
|
|
|
.. _custom-spec-excluding-endpoints:
|
|
|
|
*******************
|
|
Excluding endpoints
|
|
*******************
|
|
|
|
You can prevent a view from being included in the Swagger view by setting its class-level ``swagger_schema``
|
|
attribute to ``None``, or you can prevent an operation from being included by setting its ``auto_schema`` override
|
|
to none in :ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`:
|
|
|
|
.. code-block:: python
|
|
|
|
class UserList(APIView):
|
|
swagger_schema = None
|
|
|
|
# all methods of the UserList class will be excluded
|
|
...
|
|
|
|
# only the GET method will be shown in Swagger
|
|
@swagger_auto_schema(method='put', auto_schema=None)
|
|
@swagger_auto_schema(methods=['get'], ...)
|
|
@api_view(['GET', 'PUT'])
|
|
def user_detail(request, pk):
|
|
pass
|
|
|
|
.. _custom-spec-swagger-auto-schema:
|
|
|
|
**************************************
|
|
The ``@swagger_auto_schema`` decorator
|
|
**************************************
|
|
|
|
You can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decorator on view functions to override
|
|
some properties of the generated :class:`.Operation`. For example, in a ``ViewSet``,
|
|
|
|
.. code-block:: python
|
|
|
|
@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"""
|
|
...
|
|
|
|
will override the description of the ``PATCH /article/{id}/`` operation, and document a 404 response with no body and
|
|
the given description.
|
|
|
|
Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decorator depends on the type of your view:
|
|
|
|
* for function based ``@api_view``\ s, because the same view can handle multiple methods, and thus represent multiple
|
|
operations, you have to add the decorator multiple times if you want to override different operations:
|
|
|
|
.. code-block:: python
|
|
|
|
test_param = openapi.Parameter('test', openapi.IN_QUERY, description="test manual param", type=openapi.TYPE_BOOLEAN)
|
|
user_response = openapi.Response('response description', UserSerializer)
|
|
|
|
# 'method' can be used to customize a single HTTP method of a view
|
|
@swagger_auto_schema(method='get', manual_parameters=[test_param], responses={200: user_response})
|
|
# 'methods' can be used to apply the same modification to multiple methods
|
|
@swagger_auto_schema(methods=['put', 'post'], request_body=UserSerializer)
|
|
@api_view(['GET', 'PUT', 'POST'])
|
|
def user_detail(request, pk):
|
|
...
|
|
|
|
* for class based ``APIView``, ``GenericAPIView`` and non-``ViewSet`` derivatives, you have to decorate the respective
|
|
method of each operation:
|
|
|
|
.. code-block:: python
|
|
|
|
class UserList(APIView):
|
|
@swagger_auto_schema(responses={200: UserSerializer(many=True)})
|
|
def get(self, request):
|
|
...
|
|
|
|
@swagger_auto_schema(operation_description="description")
|
|
def post(self, request):
|
|
...
|
|
|
|
* for ``ViewSet``, ``GenericViewSet``, ``ModelViewSet``, because each viewset corresponds to multiple **paths**, you have
|
|
to decorate the *action methods*, i.e. ``list``, ``create``, ``retrieve``, etc. |br|
|
|
Additionally, ``@list_route``\ s or ``@detail_route``\ s defined on the viewset, like function based api views, can
|
|
respond to multiple HTTP methods and thus have multiple operations that must be decorated separately:
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
class ArticleViewSet(viewsets.ModelViewSet):
|
|
# method or 'methods' can be skipped because the list_route only handles a single method (GET)
|
|
@swagger_auto_schema(operation_description='GET /articles/today/')
|
|
@list_route(methods=['get'])
|
|
def today(self, request):
|
|
...
|
|
|
|
@swagger_auto_schema(method='get', operation_description="GET /articles/{id}/image/")
|
|
@swagger_auto_schema(method='post', operation_description="POST /articles/{id}/image/")
|
|
@detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,))
|
|
def image(self, request, id=None):
|
|
...
|
|
|
|
@swagger_auto_schema(operation_description="PUT /articles/{id}/")
|
|
def update(self, request, *args, **kwargs):
|
|
...
|
|
|
|
@swagger_auto_schema(operation_description="PATCH /articles/{id}/")
|
|
def partial_update(self, request, *args, **kwargs):
|
|
...
|
|
|
|
.. Tip::
|
|
|
|
If you want to customize the generation of a method you are not implementing yourself, you can use
|
|
``swagger_auto_schema`` in combination with Django's ``method_decorator``:
|
|
|
|
.. code-block:: python
|
|
|
|
@method_decorator(name='list', decorator=swagger_auto_schema(
|
|
operation_description="description from swagger_auto_schema via method_decorator"
|
|
))
|
|
class ArticleViewSet(viewsets.ModelViewSet):
|
|
...
|
|
|
|
This allows you to avoid unnecessarily overriding the method.
|
|
|
|
.. Tip::
|
|
|
|
You can go even further and directly decorate the result of ``as_view``, in the same manner you would
|
|
override an ``@api_view`` as described above:
|
|
|
|
.. code-block:: python
|
|
|
|
decorated_login_view = \
|
|
swagger_auto_schema(
|
|
method='post',
|
|
responses={status.HTTP_200_OK: LoginResponseSerializer}
|
|
)(LoginView.as_view())
|
|
|
|
urlpatterns = [
|
|
...
|
|
url(r'^login/$', decorated_login_view, name='login')
|
|
]
|
|
|
|
This can allow you to avoid skipping an unnecessary *subclass* altogether.
|
|
|
|
.. Warning::
|
|
|
|
However, do note that both of the methods above can lead to unexpected (and maybe surprising) results by
|
|
replacing/decorating methods on the base class itself.
|
|
|
|
|
|
********************************
|
|
Serializer ``Meta`` nested class
|
|
********************************
|
|
|
|
You can define some per-serializer options by adding a ``Meta`` class to your serializer, e.g.:
|
|
|
|
.. code-block:: python
|
|
|
|
class WhateverSerializer(Serializer):
|
|
...
|
|
|
|
class Meta:
|
|
... options here ...
|
|
|
|
Currently, the only option you can add here is
|
|
|
|
* ``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
|
|
|
|
*************************
|
|
Subclassing and extending
|
|
*************************
|
|
|
|
|
|
---------------------
|
|
``SwaggerAutoSchema``
|
|
---------------------
|
|
|
|
For more advanced control you can subclass :class:`~.inspectors.SwaggerAutoSchema` - see the documentation page
|
|
for a list of methods you can override.
|
|
|
|
You can put your custom subclass to use by setting it on a view method using the
|
|
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>` decorator described above, by setting it as a
|
|
class-level attribute named ``swagger_schema`` on the view class, or
|
|
:ref:`globally via settings <default-class-settings>`.
|
|
|
|
For example, to generate all operation IDs as camel case, you could do:
|
|
|
|
.. code-block:: python
|
|
|
|
from inflection import camelize
|
|
|
|
class CamelCaseOperationIDAutoSchema(SwaggerAutoSchema):
|
|
def get_operation_id(self, operation_keys):
|
|
operation_id = super(CamelCaseOperationIDAutoSchema, self).get_operation_id(operation_keys)
|
|
return camelize(operation_id, uppercase_first_letter=False)
|
|
|
|
|
|
SWAGGER_SETTINGS = {
|
|
'DEFAULT_AUTO_SCHEMA_CLASS': 'path.to.CamelCaseOperationIDAutoSchema',
|
|
...
|
|
}
|
|
|
|
--------------------------
|
|
``OpenAPISchemaGenerator``
|
|
--------------------------
|
|
|
|
If you need to control things at a higher level than :class:`.Operation` objects (e.g. overall document structure,
|
|
vendor extensions in metadata) you can also subclass :class:`.OpenAPISchemaGenerator` - again, see the documentation
|
|
page for a list of its methods.
|
|
|
|
This custom generator can be put to use by setting it as the :attr:`.generator_class` of a :class:`.SchemaView` using
|
|
:func:`.get_schema_view`.
|
|
|
|
.. _custom-spec-inspectors:
|
|
|
|
---------------------
|
|
``Inspector`` classes
|
|
---------------------
|
|
|
|
For customizing behavior related to specific field, serializer, filter or paginator classes you can implement the
|
|
:class:`~.inspectors.FieldInspector`, :class:`~.inspectors.SerializerInspector`, :class:`~.inspectors.FilterInspector`,
|
|
:class:`~.inspectors.PaginatorInspector` classes and use them with
|
|
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>` or one of the
|
|
:ref:`related settings <default-class-settings>`.
|
|
|
|
A :class:`~.inspectors.FilterInspector` that adds a description to all ``DjangoFilterBackend`` parameters could be
|
|
implemented like so:
|
|
|
|
.. code-block:: python
|
|
|
|
class DjangoFilterDescriptionInspector(CoreAPICompatInspector):
|
|
def get_filter_parameters(self, filter_backend):
|
|
if isinstance(filter_backend, DjangoFilterBackend):
|
|
result = super(DjangoFilterDescriptionInspector, self).get_filter_parameters(filter_backend)
|
|
for param in result:
|
|
if not param.get('description', ''):
|
|
param.description = "Filter the returned list by {field_name}".format(field_name=param.name)
|
|
|
|
return result
|
|
|
|
return NotHandled
|
|
|
|
@method_decorator(name='list', decorator=swagger_auto_schema(
|
|
filter_inspectors=[DjangoFilterDescriptionInspector]
|
|
))
|
|
class ArticleViewSet(viewsets.ModelViewSet):
|
|
filter_backends = (DjangoFilterBackend,)
|
|
filter_fields = ('title',)
|
|
...
|
|
|
|
|
|
A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``title`` attribute from all generated
|
|
:class:`.Schema` objects:
|
|
|
|
.. code-block:: python
|
|
|
|
class NoSchemaTitleInspector(FieldInspector):
|
|
def process_result(self, result, method_name, obj, **kwargs):
|
|
# remove the `title` attribute of all Schema objects
|
|
if isinstance(result, openapi.Schema.OR_REF):
|
|
# traverse any references and alter the Schema object in place
|
|
schema = openapi.resolve_ref(result, self.components)
|
|
schema.pop('title', None)
|
|
|
|
# no ``return schema`` here, because it would mean we always generate
|
|
# an inline `object` instead of a definition reference
|
|
|
|
# return back the same object that we got - i.e. a reference if we got a reference
|
|
return result
|
|
|
|
|
|
class NoTitleAutoSchema(SwaggerAutoSchema):
|
|
field_inspectors = [NoSchemaTitleInspector] + swagger_settings.DEFAULT_FIELD_INSPECTORS
|
|
|
|
class ArticleViewSet(viewsets.ModelViewSet):
|
|
swagger_schema = NoTitleAutoSchema
|
|
...
|
|
|
|
|
|
.. Note::
|
|
|
|
A note on references - :class:`.Schema` objects are sometimes output by reference (:class:`.SchemaRef`); in fact,
|
|
that is how named models are implemented in OpenAPI:
|
|
|
|
- in the output swagger document there is a ``definitions`` section containing :class:`.Schema` objects for all
|
|
models
|
|
- every usage of a model refers to that single :class:`.Schema` object - for example, in the ArticleViewSet
|
|
above, all requests and responses containg an ``Article`` model would refer to the same schema definition by a
|
|
``'$ref': '#/definitions/Article'``
|
|
|
|
This is implemented by only generating **one** :class:`.Schema` object for every serializer **class** encountered.
|
|
|
|
This means that you should generally avoid view or method-specific ``FieldInspector``\ s if you are dealing with
|
|
references (a.k.a named models), because you can never know which view will be the first to generate the schema
|
|
for a given serializer.
|