drf-spectacular
drf-spectacular copied to clipboard
Is there any way to annotate view methods for every app's in one place/file?
Right now I am doing this to each and every views.py file of my apps:
# [code...]
@extend_schema(tags=['Activity'])
@extend_schema_view(
get=extend_schema(
description=descriptions_detail_api['get'],
summary='Retrieve an activity by ID',
operation_id='Activity list',
responses={
200: ActivitySerializer,
400: inline_serializer(
name='ActivityDetailsResponse',
fields={
'id': serializers.IntegerField(),
}
),
}
),
put=extend_schema(
description=descriptions_detail_api['put'],
summary='Update an activity by ID',
operation_id='Activity update',
),
patch=extend_schema(
description=descriptions_detail_api['patch'],
summary='Partially update an activity by ID',
operation_id='Activity partial update',
),
delete=extend_schema(
description=descriptions_detail_api['delete'],
summary='Delete an activity by ID',
operation_id='Activity delete',
),
)
class ActivityDetailView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = ActivitySerializer
permission_classes = [IsAuthenticated, IsOwnerOrAdmin]
#[code...]
I want my views.py clean and put all these extensions at one place. How I can I achieve this?
@extend_schema* is the indented interface for one-off changes to the schema. Also, documentation which is too far removed from the code goes stale very quickly. So this is by design. Doing this for each an every endpoint is indeed not clean, but it is also not intended to be used like that.
If you have those infos stored somewhere else or they follow a specific pattern, I would recommend subclassing AutoSchema and overriding get_description(), get_summary(), get_operation_id() and adapt them to your needs. You can easily load those values from a central place then.
Note: remember to change DEFAULT_SCHEMA_CLASS also to that new AutoSchema.
This is foolish but I managed make it work like this in a separate file schema.py
class ActivityDetailsDoc(OpenApiViewExtension):
target_class = 'activity.views.ActivityDetailView'
def view_replacement(self):
@extend_schema(tags=['Activity']) # Global tag
@extend_schema_view(
get=extend_schema(
# tags=['Activity'],
# description=descriptions_detail_api['get'],
summary='Retrieve an activity by ID',
operation_id='retrieve_activity',
),
put=extend_schema(
# tags=['Activity'],
# description=descriptions_detail_api['put'],
summary='Update an activity by ID',
operation_id='update_activity',
),
patch=extend_schema(
# tags=['Activity'],
# description=descriptions_detail_api['patch'],
summary='Partially update an activity by ID',
operation_id='partial_update_activity',
),
delete=extend_schema(
# tags=['Activity'],
# description=descriptions_detail_api['delete'],
summary='Delete an activity by ID',
operation_id='delete_activity',
),
)
class Fixed(self.target_class):
def get(self, request, *args, **kwargs):
pass
def put(self, request, *args, **kwargs):
pass
def patch(self, request, *args, **kwargs):
pass
def delete(self, request, *args, **kwargs):
pass
return Fixed
I know this was not intended for this use case LoL.
So, after sbclassing AutoSchema where to put this class? I need to annotate every endpoints.
I would call that a lot less clean. You are writing a custom schema with 3 extra layers of boilerplate.
# schema.py
from drf_spectacular.openapi import AutoSchema
data = {
'ActivityDetailView': {
'put': {
'summary': "foo",
'description': "bar",
'operation_id': "baz"
},
...
},
...
}
class CustomSchema(AutoSchema):
def get_tags(self):
return ['Activity']
def get_description(self):
return data[self.view.__class__.__name__][self.method.lower()]['description']
def get_summary(self):
return data[self.view.__class__.__name__][self.method.lower()]['summary']
def get_operation_id(self):
return data[self.view.__class__.__name__][self.method.lower()]['operation_id']
put that in schema.py and then change your settings.py
# settings.py
REST_FRAMEWORK={
...
'DEFAULT_SCHEMA_CLASS': 'your.path.to.schema.CustomSchema',
},
Then you have all annotation in one file. This has the added benefit that you can still add extend_schema where needed on top.
"I would call that a lot less clean": I agree. Subclassing solves this problem and everything goes to one place. The problem is I am using GenericApiViews. As I am not touching get or post methods, this makes difficult to set summary, description of the api's and group them under a global tag. So get a good schema I have to annotate every action (which I really don't want to cause it cumbersome) to add these properties .