.. |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. .. _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 `: .. 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, ``@action``\ 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 action only handles a single method (GET) @swagger_auto_schema(operation_description='GET /articles/today/') @action(detail=False, 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/") @action(detail=True, 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. ********************************* Support for SerializerMethodField ********************************* Schema generation of ``serializers.SerializerMethodField`` is supported in two ways: 1) The :func:`swagger_serializer_method <.swagger_serializer_method>` decorator 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_or_field=OtherStuffSerializer) def get_other_stuff(self, obj): return OtherStuffSerializer().data Note that the ``serializer_or_field`` parameter can accept either a subclass or an instance of ``serializers.Field``. 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 When return type hinting is not supported, the equivalent ``serializers.Field`` subclass can be used with :func:`swagger_serializer_method <.swagger_serializer_method>`: .. code-block:: python class SomeSerializer(serializers.Serializer): some_number = serializers.SerializerMethodField() @swagger_serializer_method(serializer_or_field=serializers.FloatField) def get_some_number(self, obj): return 1.0 ******************************** 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 ... .. _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 ``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. 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``) * ``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 `_. ************************* 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 --------------------- 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,) filterset_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. **IMPORTANT:** nested fields on ``ModelSerializer``\ s that are generated from model ``ForeignKeys`` will always be output by value. If you want the by-reference behaviour you have to explictly set the serializer class of nested fields instead of letting ``ModelSerializer`` generate one automatically; for example: .. code-block:: python class OneSerializer(serializers.ModelSerializer): class Meta: model = SomeModel fields = ('id',) class AnotherSerializer(serializers.ModelSerializer): child = OneSerializer() class Meta: model = SomeParentModel fields = ('id', 'child') Another caveat that stems from this is that any serializer named "``NestedSerializer``" will be forced inline unless it has a ``ref_name`` set explicitly. .. _Python 3 type hinting: https://docs.python.org/3/library/typing.html