Hi,
AFAIU there are two general use-cases for schema generation - schema for user, in which case I should use get_schema_view(public=False,) and second use-case - schema as API documentation, in which case I should use get_schema_view(public=True,).
Probably permissions documentation is not important in first case - use will see already filtered API subset, but is important in second case.
I'm generating documentation for project with lots of custom permissions. It's strange that there are view, fields and paginator inspectors, but no permissions inspectors.
I was wondering if there is some best-practice or recommended approach for this.
At the moment I use following hack, which does the job, but feels wrong:
class ViewInspector(SwaggerAutoSchema):
"""View inspector with some project-specific logic."""
def get_summary_and_description(self):
"""Return summary and description extended with permission docs."""
summary, description = super().get_summary_and_description()
permissions_description = self._get_permissions_description()
if permissions_description:
description += permissions_description
return summary, description
def _get_permissions_description(self):
permission_descriptions = []
for permission_class in getattr(self.view, 'permission_classes', []):
if hasattr(permission_class, 'get_description'):
permission_descriptions.append(
permission_class.get_description(self.view))
else:
permission_descriptions.append(
permission_class.__doc__.replace('\n', ' ').strip())
if permission_descriptions:
return '\n**Permissions**:\n' + '\n'.join(
'+ ' + description for description in permission_descriptions
)
else:
return None
There are no permission inspectors because there is no set way to describe permissions in swagger. The logic you showed in your snippet seems just fine if all you want is to show the required permissions in the description.
The closes thing to programatically defining permissions in swagger would be OAuth scopes, but I'm guessing that is not what you're after (and there is also no built-in support for those in drf-yasg).
I think it would be exceptionally useful if we somehow integrate this into drf_yasg. For example, we could automatically generate 403 responses based on permissions.
Anyway @K0Te approach quite good. Thanks!
I've change something in the snippet, maybe it might be useful for others.
from typing import List, Optional
from collections import namedtuple
from drf_yasg.inspectors import SwaggerAutoSchema as DrfYasgSwaggerAutoSchema
from rest_framework.permissions import OperandHolder, SingleOperandHolder
PermissionItem = namedtuple('PermissionItem', ['name', 'doc_str'])
def _render_permission_item(item):
return f'+ `{item.name}`: *{item.doc_str}*'
class SwaggerAutoSchema(DrfYasgSwaggerAutoSchema):
"""View inspector with some project-specific logic."""
def get_summary_and_description(self):
"""Return summary and description extended with permission docs."""
summary, description = super().get_summary_and_description()
permissions_description = self._get_permissions_description()
if permissions_description:
description += permissions_description
return summary, description
def _handle_permission(
self, permission_class) -> Optional[PermissionItem]:
permission = None
try:
permission = PermissionItem(
permission_class.__name__,
permission_class.get_description(self.view),
)
except AttributeError:
if permission_class.__doc__:
permission = PermissionItem(
permission_class.__name__,
permission_class.__doc__.replace('\n', ' ').strip(),
)
return permission
def _gather_permissions(self) -> List[PermissionItem]:
items = []
for permission_class in getattr(self.view, 'permission_classes', []):
if isinstance(permission_class, OperandHolder):
items.append(
self._handle_permission(permission_class.op1_class))
items.append(
self._handle_permission(permission_class.op2_class))
if isinstance(permission_class, SingleOperandHolder):
items.append(
self._handle_permission(permission_class.op1_class))
items.append(self._handle_permission(permission_class))
return [i for i in items if i]
def _get_permissions_description(self):
permission_items = self._gather_permissions()
if permission_items:
return '\n\n**Permissions:**\n' + '\n'.join(
_render_permission_item(item) for item in permission_items
)
else:
return None
@timur-orudzhov very useful. Thanks!
Most helpful comment
I've change something in the snippet, maybe it might be useful for others.