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.
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 @swagger_auto_schema:
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
The @swagger_auto_schema
decorator¶
You can use the @swagger_auto_schema
decorator on view functions to override
some properties of the generated Operation
. For example, in a ViewSet
,
from drf_yasg.utils import swagger_auto_schema
@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 @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:from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import api_view from drf_yasg import openapi 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: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.
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: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
:
@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:
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:
The
swagger_serializer_method
decorator for the use case where the serializer method is using a serializer. e.g.:from drf_yasg.utils import swagger_serializer_method class OtherStuffSerializer(serializers.Serializer): foo = serializers.CharField() class ParentSerializer(serializers.Serializer): other_stuff = serializers.SerializerMethodField() many_other_stuff = serializers.SerializerMethodField() @swagger_serializer_method(serializer_or_field=OtherStuffSerializer) def get_other_stuff(self, obj): return OtherStuffSerializer().data @swagger_serializer_method(serializer_or_field=OtherStuffSerializer(many=True)) def get_many_other_stuff(self, obj): return OtherStuffSerializer().data
Note that the
serializer_or_field
parameter can accept either a subclass or an instance ofserializers.Field
.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.:
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 withswagger_serializer_method
: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 or per-field options by adding a Meta
class to your Serializer
or
serializer Field
, e.g.:
class WhateverSerializer(Serializer):
...
class Meta:
... options here ...
The available options are:
ref_name
- a string which will be used as the model definition name for this serializer class; setting it toNone
will force the serializer to be generated as an inline model everywhere it is used. If two serializers have the sameref_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 anySerializer
suffix (e.g.UserSerializer
->User
,SerializerWithSuffix
->SerializerWithSuffix
)swagger_schema_fields
- a dictionary mappingSchema
field names to values. These attributes will be set on theSchema
object generated from theSerializer
. Field names must be python values, which are converted to SwaggerSchema
attribute names according tomake_swagger_name()
. Attribute names and values must conform to the OpenAPI 2.0 specification.
Suppose you wanted to model an email using a JSONField to store the subject and body for performance reasons:
from django.contrib.postgres.fields import JSONField
class Email(models.Model):
# Store data as JSON, but the data should be made up of
# an object that has two properties, "subject" and "body"
# Example:
# {
# "subject": "My Title",
# "body": "The body of the message.",
# }
message = JSONField()
To instruct drf-yasg
to output an OpenAPI schema that matches this, create a custom JSONField
:
class EmailMessageField(serializers.JSONField):
class Meta:
swagger_schema_fields = {
"type": openapi.TYPE_OBJECT,
"title": "Email",
"properties": {
"subject": openapi.Schema(
title="Email subject",
type=openapi.TYPE_STRING,
),
"body": openapi.Schema(
title="Email body",
type=openapi.TYPE_STRING,
),
},
"required": ["subject", "body"],
}
class EmailSerializer(ModelSerializer):
class Meta:
model = Email
fields = "__all__"
message = EmailMessageField()
Warning
Overriding a default Field
generated by a ModelSerializer
will also override automatically
generated validators for that Field
. To add Serializer
validation back in manually, see the relevant
DRF Validators and DRF Fields documentation.
One example way to do this is to set the default_validators
attribute on a field.
class EmailMessageField(serializers.JSONField):
default_validators = [my_custom_email_validator]
...
Subclassing and extending¶
SwaggerAutoSchema
¶
For more advanced control you can subclass 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
@swagger_auto_schema decorator described above, by setting it as a
class-level attribute named swagger_schema
on the view class, or
globally via settings.
For example, to generate all operation IDs as camel case, you could do:
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 Operation
objects (e.g. overall document structure,
vendor extensions in metadata) you can also subclass 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 generator_class
of a SchemaView
using
get_schema_view()
.
Inspector
classes¶
For customizing behavior related to specific field, serializer, filter or paginator classes you can implement the
FieldInspector
, SerializerInspector
, FilterInspector
,
PaginatorInspector
classes and use them with
@swagger_auto_schema or one of the
related settings.
A FilterInspector
that adds a description to all DjangoFilterBackend
parameters could be
implemented like so:
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 FieldInspector
that removes the title
attribute from all generated
Schema
objects:
from drf_yasg.inspectors import FieldInspector
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 - Schema
objects are sometimes output by reference (SchemaRef
); in fact,
that is how named models are implemented in OpenAPI:
- in the output swagger document there is a
definitions
section containingSchema
objects for all models- every usage of a model refers to that single
Schema
object - for example, in the ArticleViewSet above, all requests and responses containing anArticle
model would refer to the same schema definition by a'$ref': '#/definitions/Article'
This is implemented by only generating one 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 behavior you have to explicitly set the serializer class of nested
fields instead of letting ModelSerializer
generate one automatically; for example:
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.