User guide
Operations
The API documentation will be extracted from the view class and methos docstrings. You can add documentation the following way:
class MyApiViewSet(viewsets.ModelViewSet):
"""
General description. It will be used for `tags` and operations (actions) that doesn't
provide a specific description.
list: this is the description of the list action
retrieve: this is the description of the retrieve action
mycustomaction: this is `mycustomaction` description and is the same regardless of http method.
another_action$get: this is `another_action` description just for the `get` http method.
"""
...
If an action has a custom implementation, its description is taken from the method docstring. The method docstring take precedence over the class docstring.
Headers
You can document http headers both for requests and responses.
class MyApiViewSet(viewsets.ModelViewSet):
schema_request_headers = {
'list': { # you could also use a method (lowercase) or '' to append to each request.
'X-My-Request-Header': {
'type': 'string',
'description': 'This is a request header for the list action',
}
}
}
schema_response_headers = {
'post,put': { # You could provide a comma-separeted list of methods and/or actions.
'X-My-Response-Header': {
'type': 'string',
'description': 'This is a response header for all kind of operations'.
}
}
}
If declared here, it takes precedence over the ones that are declared in
the DRF_CONNECT_DOCS
setting for the same action or method.
Filters
django-rql
offers support for schema generation from release 3 onward.
Please refers to https://git.int.zone/projects/SWFT/repos/django-rql/browse for more information.
Path parameters
Path parameters are extracted from the path. If a parameter name match a model field its description is taken from
the model field help_text
attribute.
Manual parameters
Querystring parameters can be described manually using the schema_manual_parameters
attribute of a view.
class MyView(views.APIView):
schema_manual_parameters = [
{
'name': 'my_param',
'in': 'query',
'required': True,
'schema': {
'type': 'string',
},
},
]
Serializers
To add a description to the Model handled by a serializer, simply add the class docstring to the serializer as for example:
class AssetDetailSerializer(serializers.ModelSerializer):
"""This is the description of the *AssetDetail* model"""
SerializerMethodField (or the to_representation serializer method)
In order to document the JSON subobject returned by the SerializerMethodField’s method you can use the @schema_method
decorator.
For example if the method returns a scalar value (string, integer, decimal, etc)
you can pass the corresponding serializers
field:
...
@schema_method(field=serializers.EmailField)
def get_email(self, obj):
return 'test@email.com'
...
or if the method returns an object using a Serializer, pass the serializer (or a dotted module path) as an argument of the decorator:
...
@schema_method(serializer=AssetReferenceSerializer)
def get_asset(self, obj):
asset_serializer = AssetReferenceSerializer(obj.asset, read_only=True)
setattr(asset_serializer, '__tiers', self.__tiers)
return asset_serializer.data
...
If the method returns an array of objects you can specify many=True
:
...
@schema_method(serializer=AssetReferenceSerializer, many=True)
def get_assets(self, obj):
asset_serializer = AssetReferenceSerializer(obj.assets, read_only=True, many=True)
return asset_serializer.data
...
finally, if the returned object is more complex and can’t be described using a serializer you can pass the schema for this object manually:
...
@schema_method(schema={
'type': 'object',
'properties': {
'created': {
'type': 'object',
'properties': {
'at': {
'type': 'string',
'format': 'date-time',
},
},
},
'updated': {
'type': 'object',
'properties': {
'at': {
'type': 'string',
'format': 'date-time',
},
},
},
},
})
def get_events(self, obj):
return {
'created': {'at': obj.created_at.isoformat() if obj.created_at else None},
'updated': {'at': obj.updated_at.isoformat() if obj.updated_at else None},
}
...
To describe a schema with the minimum human effort, it also accept serializers and fields:
...
@schema_method(schema={
'type': 'object',
'properties': {
'stats': {
'type': 'object',
'properties': {
'vendor': {
'type': 'object',
'properties': {
'last_request': BillingRequestSummarySerializer,
'count': serializers.IntegerField,
},
},
'provider': {
'type': 'object',
'properties': {
'last_request': BillingRequestSummarySerializer,
'count': serializers.IntegerField,
},
},
},
},
},
})
def get_billing(self, obj):
billingstat_serializer = BillingStatSerializer(many=True)
billing = billingstat_serializer.to_representation(obj.item.billing_request_stats.all())
return {'stats': dict(billing)} if billing else {}
...
You can also use the @schema_method
decorator to decorate the to_representation
method for serializers,
rest_framework.serializers.RelatedField
and rest_framework.serializers.ReadOnlyField
:
...
@schema_method(serializer=BillingRequestGetSerializer())
def to_representation(self, instance):
return BillingRequestGetSerializer(instance).data
...
The schema
argument of the @schema_method
decorator accept also a callable with no arguments or
an instance method of the same serializer. For example:
class MySerializer(serializers.Serializer):
events = serializers.SerializerMethodField()
def get_schema_for_events(self):
return {
'type': 'object',
'properties': {
...
}
}
@schema_method(schema=get_schema_for_events)
def get_events(self, obj):
pass
JSONField
If you want to describe schema for a JSONField, you can subclass it and provide a schema attribute for example:
class MyCoolJSONField(serializers.JSONField):
schema = {
'type': 'object',
'properties': {
'name': {
'type': 'string'
},
'birth_date': {
'type': 'string',
'format': 'date',
},
},
}
Manual responses
You can document responses for other http status codes than 2xx this way:
class MyApiViewSet(viewsets.ModelViewSet):
schema_responses = {
'list': { # Like headers, you can use action or method (both lowercase) or ''.
'400': {
'content': {
'application/json': {
'schema': {
'type': 'object',
'properties': {
'error_code': {
'type': 'string',
},
'errors': {
'type': 'array',
'items': {
'type': 'string',
},
},
},
},
},
},
'description': '',
},
}
}
If declared here, they take precedence over the ones that are declared in
the DRF_CONNECT_DOCS
setting for the same action or method.
Per-view customization
Please refer to https://www.django-rest-framework.org/api-guide/schemas/#per-view-customization if you want to completely disable schema generation for a view/viewset or provide a ConnectAutoSchema
subclass to a view with customization.
Different request/response serializer
If an action uses a serializer for the request and another for the response you can describe this situation the following way:
class MyViewSet(ViewSet):
serializer_class = DefaultSerializer
def get_request_serializer_class(self):
if self.action == 'create':
return CreateRequestSerializer
def get_response_serializer_class(self):
if self.action == 'create':
return CreateResponseSerializer
In this example, if the action is create
the CreateRequestSerializer
and CreateResponseSerializer`
for request and response bodies respectively.
Customize schema for specific action/method
If you want a completely custom schema for request and/or response:
class MyViewSet(ViewSet):
def get_request_schema(self):
if self.action == 'create':
return {
'content': {
'application/json': {
'schema': {
'type': 'object',
'properties': {
'my_prop': {'type': 'string'},
},
},
},
},
}
def get_response_schema(self):
if self.action == 'create':
return {
'201': {
'content': {
'application/json': {
'schema': {
'type': 'object',
'properties': {
'my_prop': {'type': 'string'},
},
},
},
},
},
}
Note that if the returned value from
get_request_schema
andget_response_schema
isNone
, drf-connect-docs will continue with the serializer inspection.
Customize models names
drf-connect-docs
determine the name of a model from the name of the serializer. For example if we have
a serializers named MyModelSerializer the model name will be MyModel.
To customize the name of a model (represented by a DRF serializer) you have to add the schema_name
attribute
to the serializer Meta.
For example if we have the following:
class AssetListSerializer(AssetBaseSerializer):
items = ItemAssetReferenceSerializer(source='assetitem_set', many=True, read_only=True)
events = serializers.SerializerMethodField()
class Meta:
model = Asset
schema_name = 'Assets'
the model will be named Assets instead of AssetList.
Generate the OpenAPI 3 schema file
The schema could be generated on-demand using the generate_schema
django command:
$ python manage.py generate_schema > myschema.yml
In some circumstances the lack of authentication information
inside the request
object can cause an exception. In this case
it’s possible to pass an api key through the Authorization
header
with the option:
$ python manage.py generate_schema --api-key "ApiKey SU-XXXX-XXXX:xxxxxxxxxxxx" > myschema.yml
It could also be generated automatically on application bootstrap with these settings:
DRF_CONNECT_DOCS = {
'OUTPUT_SCHEMA_ON_START': True,
'OUTPUT_SCHEMA_FILE': '/path/to/schema/folder/specs.yml',
}
Serve the schema
If you want that your django service expose a view to serve the schema you have to include the urlpatterns from the cnctdocs app in your URLConf:
from django.urls import include, path
from cnctdocs import urls as schema_view_urls
urlpatterns = [
...
path('/my/cool/path/', include(schema_view_urls)),
...
]
And you must set the path where the schema file is located:
DRF_CONNECT_DOCS = {
'OUTPUT_SCHEMA_FILE': '/path/to/schema/folder/specs.yml',
}
Finally you must run collectstatic:
$ ./manage.py collectstatic
This results in a view available at /my/cool/path/specification. If Django is in DEBUG mode, /my/cool/path/specification-ui will serve a view with an embedded swagger-ui component to make the life easier to programmers since they can look at how documentation is rendered and find possible problems quickly.
You can customize the last fragment of the path for example:
DRF_CONNECT_DOCS = {
'SCHEMA_VIEW_URL_PATH': 'specs/openapi',
}
So the resulting url will be /my/cool/path/specs/openapi.
By default, the specification view inherits authentication and permission classes
respectively from the DRF’s DEFAULT_AUTHENTICATION_CLASSES
and DEFAULT_PERMISSION_CLASSES
settings.
If you want to customize authentication or permissions for the view you have to set:
DRF_CONNECT_DOCS = {
'SCHEMA_VIEW_AUTHENTICATION_CLASSES': 'my.custom.Authentication',
'SCHEMA_VIEW_PERMISSION_CLASSES': 'my.custom.Permission',
}
or if you want to configure more than one authentication or permission class you can use a tuple or list:
DRF_CONNECT_DOCS = {
'SCHEMA_VIEW_AUTHENTICATION_CLASSES': ('my.custom.Authentication1', 'my.custom.Authentication1'),
'SCHEMA_VIEW_PERMISSION_CLASSES': ('my.custom.Permission1', 'my.custom.Permission2'),
}
Merge schemas
The merge_schemas
merge several slaves schemas file into a master.
The general information, servers and authentication sections of slaves
is ignored. If some component or tag is present both in the master schema
and one of the slaves schemas, the merge fails.
The merge_schemas
command works both with local files or files
served by a webserver.
$ python manage.py merge_schemas \
--master https://example.com/devportal.yml \
https://example.com/subscriptions.yml \
https://example.com/usage.yml > merged.yml