As per the DRF documentation I started using ViewSet and have implemented list, retrieve, create, update and destroy
actions. I have another APIView for which I was able to write schema (ManualSchema) and when I navigate to /docs/
I am able to the documentation as well as live endpoint for interaction.
I wish to create separate schema for each of the viewset action. I tried writing one but it doesn't show up so I think I am missing something.
Here is the code:
class Clients(viewsets.ViewSet):
'''
Clients is DRF viewset which implements `create`, `update`, `read` actions by implementing create, update, list and retrieve functions respectively.
'''
list_schema = schemas.ManualSchema(fields=[
coreapi.Field(
'status',
required=False,
location='query',
description='Accepted values are `active`, `inactive`'
),
],
description='Clients list',
encoding='application/x-www-form-urlencoded')
@action(detail=True, schema=list_schema)
def list(self, request):
'''Logic for listing'''
def retrieve(self, request, oid=None):
'''Logic for retrieval'''
create_schema = schemas.ManualSchema(fields=[
coreapi.Field(
'name',
required=False,
location='body',
),
coreapi.Field(
'location',
required=False,
location='body',
),
],
description='Clients list',
encoding='application/x-www-form-urlencoded')
@action(detail=True, schema=create_schema)
def create(self, request):
'''Logic for creation'''
A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as . get() or . post() , and instead provides actions such as . list() and . create() .
APIView and ViewSet all have their right use cases. But most of the time, if you are only doing CRUD on resources, you can directly use ViewSets to respect the DRY principle. But if you are looking for more complex features, you can go low-level because after all, viewsets are also a subclass of APIView .
API schemas are a useful tool that allow for a range of use cases, including generating reference documentation, or driving dynamic client libraries that can interact with your API. Django REST Framework provides support for automatic generation of OpenAPI schemas.
It is more rigid than a generic APIView but it takes away from you some boilerplate/manual config that you would have to repeat again and again in most cases. One step further is the ModelViewSet , which is an extension of the ViewSet for when you are working with Django models.
So I will answer my own question. I took a look at DRF source code for schema generation. I came up with the plan and performed following steps.
I subclassed SchemaGenerator class defined in rest_framework.schemas
module. Below is the code.
class CoreAPISchemaGenerator(SchemaGenerator):
def get_links(self, request=None, **kwargs):
links = LinkNode()
paths = list()
view_endpoints = list()
for path, method, callback in self.endpoints:
view = self.create_view(callback, method, request)
path = self.coerce_path(path, method, view)
paths.append(path)
view_endpoints.append((path, method, view))
if not paths:
return None
prefix = self.determine_path_prefix(paths)
for path, method, view in view_endpoints:
if not self.has_view_permissions(path, method, view):
continue
actions = getattr(view, 'actions', None)
schemas = getattr(view, 'schemas', None)
if not schemas:
link = view.schema.get_link(path, method, base_url=self.url)
subpath = path[len(prefix):]
keys = self.get_keys(subpath, method, view, view.schema)
insert_into(links, keys, link)
else:
action_map = getattr(view, 'action_map', None)
method_name = action_map.get(method.lower())
schema = schemas.get(method_name)
link = schema.get_link(path, method, base_url=self.url)
subpath = path[len(prefix):]
keys = self.get_keys(subpath, method, view, schema)
insert_into(links, keys, link)
return links
def get_keys(self, subpath, method, view, schema=None):
if schema and hasattr(schema, 'endpoint_name'):
return [schema.endpoint_name]
else:
if hasattr(view, 'action'):
action = view.action
else:
if is_list_view(subpath, method, view):
action = 'list'
else:
action = self.default_mapping[method.lower()]
named_path_components = [
component for component
in subpath.strip('/').split('/')
if '{' not in component
]
if is_custom_action(action):
if len(view.action_map) > 1:
action = self.default_mapping[method.lower()]
if action in self.coerce_method_names:
action = self.coerce_method_names[action]
return named_path_components + [action]
else:
return named_path_components[:-1] + [action]
if action in self.coerce_method_names:
action = self.coerce_method_names[action]
return named_path_components + [action]
I specifically modified two functions get_links and get_keys as that allow me to achieve what I wanted.
Further, for all the functions in viewsets that I was writing I dedicated an individual schema for it. I simply created a dictionary to keep mappings of function name to schema instance. For better approach I created a separate file to store schemas. For eg. if I had a viewset Clients
I created a corresponding ClientsSchema
class and within in defined staticmethods which returned schema instances.
Example,
In file where I am defining my schemas,
class ClientsSchema():
@staticmethod
def list_schema():
schema = schemas.ManualSchema(
fields=[],
description=''
)
schema.endpoint_name = 'Clients Listing'
return schema
In my apis.py,
class Clients(viewsets.ViewSet):
schemas = {
'list': ClientsSchema.list_schema()
}
def list(self, request, **kwargs):
pass
This setup allows me to define schemas for any type of functions that I add to my viewsets. In addition to it, I also wanted that the endpoints have an identifiable name and not the one generated by DRF which is like a > b > update > update
.
In order to achieve that, I added endpoint_name
property to schema
object that is returned. That part is handled in get_keys
function that is overridden.
Finally in the urls.py where we include urls for documentation we need to use our custom schema generator. Something like this,
urlpatterns.append(url(r'^livedocs/', include_docs_urls(title='My Services', generator_class=CoreAPISchemaGenerator)))
For security purposes I cannot share any snapshots. Apologies for that. Hope this helps.
I think what you are trying to do is not possible. The ViewSet
does not provide any method handlers, hence, you cannot use the @action
decorator on the methods create
and list
, as they are existing routes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With