.. |br| raw:: html
######################## 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. ********************* Swagger spec overview ********************* This library generates OpenAPI 2.0 documents. The authoritative specification for this document's structure will always be the official documentation over at `swagger.io `__ and the `OpenAPI 2.0 specification page `__. Beause the above specifications are a bit heavy and convoluted, here is a general overview of how the specification is structured, starting from the root ``Swagger`` object. * :class:`.Swagger` object + ``info``, ``schemes``, ``securityDefinitions`` and other informative attributes + ``paths``: :class:`.Paths` object A list of all the paths in the API in the form of a mapping - ``{path}``: :class:`.PathItem` - each :class:`.PathItem` has multiple operations keyed by method * ``{http_method}``: :class:`.Operation` Each operation is thus uniquely identified by its ``(path, http_method)`` combination, e.g. ``GET /articles/``, ``POST /articles/``, etc. * ``parameters``: [:class:`.Parameter`] - and a list of path parameters + ``definitions``: named Models A list of all the named models in the API in the form of a mapping - ``{ModelName}``: :class:`.Schema` * :class:`.Operation` contains the following information about each operation: + ``parameters``: [:class:`.Parameter`] A list of all the *query*, *header* and *form* parameters accepted by the operation. - there can also be **at most one** body parameter whose structure is represented by a :class:`.Schema` or a reference to one (:class:`.SchemaRef`) + ``responses``: :class:`.Responses` A list of all the possible responses the operation is expected to return. Each response can optionally have a :class:`.Schema` which describes the structure of its body. - ``{status_code}``: :class:`.Response` - mapping of status code to response definition + ``operationId`` - should be unique across all operations + ``tags`` - used to group operations in the listing It is interesting to note the main differences between :class:`.Parameter` and :class:`.Schema` objects: +----------------------------------------------------------+-----------------------------------------------------------+ | :class:`.Schema` | :class:`.Parameter` | +==========================================================+===========================================================+ | Can nest other Schemas | Cannot nest other Parameters |br| | | | Can only nest a Schema if the parameter is ``in: body`` | +----------------------------------------------------------+-----------------------------------------------------------+ | Cannot describe file uploads |br| | Can describe file uploads via ``type`` = ``file``, |br| | | - ``file`` is not permitted as a value for ``type`` | but only as part of a form :class:`.Operation` [#formop]_ | +----------------------------------------------------------+-----------------------------------------------------------+ | Can be used in :class:`.Response`\ s | Cannot be used in :class:`.Response`\ s | +----------------------------------------------------------+-----------------------------------------------------------+ | Cannot be used in form :class:`.Operation`\ s [#formop]_ | Can be used in form :class:`.Operation`\ s [#formop]_ | +----------------------------------------------------------+-----------------------------------------------------------+ | Can only describe request or response bodies | Can describe ``query``, ``form``, ``header`` or ``path`` | | | parameters | +----------------------------------------------------------+-----------------------------------------------------------+ .. [#formop] a form Operation is an :class:`.Operation` that consumes ``multipart/form-data`` or ``application/x-www-form-urlencoded`` content * a form Operation cannot have ``body`` parameters * a non-form operation cannot have ``form`` parameters **************** Default behavior **************** This section describes where information is sourced from when using the default generation process. * :class:`.Paths` are generated by exploring the patterns registered in your default ``urlconf``, or the ``patterns`` and ``urlconf`` you specified when constructing :class:`.OpenAPISchemaGenerator`; only views inheriting from Django Rest Framework's ``APIView`` are looked at, all other views are ignored * ``path`` :class:`.Parameter`\ s are generated by looking in the URL pattern for any template parameters; attempts are made to guess their type from the views ``queryset`` and ``lookup_field``, if applicable. You can override path parameters via ``manual_parameters`` in :ref:`@swagger_auto_schema `. * ``query`` :class:`.Parameter`\ s - i.e. parameters specified in the URL as ``/path/?query1=value&query2=value`` - are generated from your view's ``filter_backends`` and ``paginator``, if any are declared. Additional parameters can be specified via the ``query_serializer`` and ``manual_parameters`` arguments of :ref:`@swagger_auto_schema ` * The request body is only generated for the HTTP ``POST``, ``PUT`` and ``PATCH`` methods, and is sourced from the view's ``serializer_class``. You can also override the request body using the ``request_body`` argument of :ref:`@swagger_auto_schema `. - if the view represents a form request (that is, all its parsers are of the ``multipart/form-data`` or ``application/x-www-form-urlencoded`` media types), the request body will be output as ``form`` :class:`.Parameter`\ s - if it is not a form request, the request body will be output as a single ``body`` :class:`.Parameter` wrapped around a :class:`.Schema` * ``header`` :class:`.Parameter`\ s are supported by the OpenAPI specification but are never generated by this library; you can still add them using ``manual_parameters``. * :class:`.Responses` are generated as follows: + if ``responses`` is provided to :ref:`@swagger_auto_schema ` and contains at least one success status code (i.e. any `2xx` status code), no automatic response is generated and the given response is used as described in the :func:`@swagger_auto_schema documentation <.swagger_auto_schema>` + otherwise, an attempt is made to generate a default response: - the success status code is assumed to be ``204` for ``DELETE`` requests, ``201`` for ``POST`` requests, and ``200`` for all other request methods - if the view has a request body, the same ``Serializer`` or :class:`.Schema` as in the request body is used in generating the :class:`.Response` schema; this is inline with the default ``GenericAPIView`` and ``GenericViewSet`` behavior - if the view has no request body, its ``serializer_class`` is used to generate the :class:`.Response` schema - if the view is a list view (as defined by :func:`.is_list_view`), the response schema is wrapped in an array - if the view is also paginated, the response schema is then wrapped in the appropriate paging response structure - the description of the response is left blank * :class:`.Response` headers are supported by the OpenAPI specification but not currently supported by this library; you can still add them manually by providing an `appropriately structured dictionary `_ to the ``headers`` property of a :class:`.Response` object * *descriptions* for :class:`.Operation`\ s, :class:`.Parameter`\ s and :class:`.Schema`\ s are picked up from docstrings and ``help_text`` attributes in the same manner as the `default DRF SchemaGenerator `_ .. _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 ` decorator described above, by setting it as a class-level attribute named ``swagger_schema`` on the view class, or :ref:`globally via 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 --------------------- .. versionadded:: 1.1 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 ` or one of the :ref:`related 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.