Added schema descriptions

This commit is contained in:
Tom Christie 2016-10-05 15:37:25 +01:00
parent cd826ce422
commit 7e3a3a4081
3 changed files with 52 additions and 19 deletions

View File

@ -242,6 +242,14 @@ You could then either:
--- ---
# Schemas as documentation
One common usage of API schemas is to use them to build documentation pages.
The schema generation in REST framework uses docstrings to automatically
---
# Alternate schema formats # Alternate schema formats
In order to support an alternate schema format, you need to implement a custom renderer In order to support an alternate schema format, you need to implement a custom renderer

View File

@ -1,3 +1,4 @@
import re
from collections import OrderedDict from collections import OrderedDict
from importlib import import_module from importlib import import_module
@ -15,6 +16,8 @@ from rest_framework.response import Response
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.views import APIView from rest_framework.views import APIView
header_regex = re.compile('^[a-zA-Z][0-9A-Za-z_]*:')
def as_query_fields(items): def as_query_fields(items):
""" """
@ -53,8 +56,7 @@ def insert_into(target, keys, value):
def is_custom_action(action): def is_custom_action(action):
return action not in set([ return action not in set([
'read', 'retrieve', 'list', 'retrieve', 'list', 'create', 'update', 'partial_update', 'destroy'
'create', 'update', 'partial_update', 'delete', 'destroy'
]) ])
@ -171,17 +173,12 @@ class EndpointInspector(object):
class SchemaGenerator(object): class SchemaGenerator(object):
# Map methods onto 'actions' that are the names used in the link layout. # Map methods onto 'actions' that are the names used in the link layout.
default_mapping = { default_mapping = {
'get': 'read', 'get': 'retrieve',
'post': 'create', 'post': 'create',
'put': 'update', 'put': 'update',
'patch': 'partial_update', 'patch': 'partial_update',
'delete': 'destroy', 'delete': 'destroy',
} }
# Coerce the following viewset actions into different names.
coerce_actions = {
'retrieve': 'read',
'destroy': 'delete'
}
endpoint_inspector_cls = EndpointInspector endpoint_inspector_cls = EndpointInspector
def __init__(self, title=None, url=None, patterns=None, urlconf=None): def __init__(self, title=None, url=None, patterns=None, urlconf=None):
@ -283,6 +280,8 @@ class SchemaGenerator(object):
else: else:
encoding = None encoding = None
description = self.get_description(path, method, view)
if self.url and path.startswith('/'): if self.url and path.startswith('/'):
path = path[1:] path = path[1:]
@ -290,9 +289,38 @@ class SchemaGenerator(object):
url=urlparse.urljoin(self.url, path), url=urlparse.urljoin(self.url, path),
action=method.lower(), action=method.lower(),
encoding=encoding, encoding=encoding,
fields=fields fields=fields,
description=description
) )
def get_description(self, path, method, view):
"""
Determine a link description.
This with either be the class docstring, or a specific section for it.
For views, use method names, eg `get:` to introduce a section.
For viewsets, use action names, eg `retrieve:` to introduce a section.
The section names will correspond to the methods on the class.
"""
view_description = view.get_view_description()
lines = [line.strip() for line in view_description.splitlines()]
current_section = ''
sections = {'': ''}
for line in lines:
if header_regex.match(line):
current_section, seperator, lead = line.partition(':')
sections[current_section] = lead.strip()
else:
sections[current_section] += line + '\n'
header = getattr(view, 'action', method.lower())
if header in sections:
return sections[header].strip()
return sections[''].strip()
def get_encoding(self, path, method, view): def get_encoding(self, path, method, view):
""" """
Return the 'encoding' parameter to use for a given endpoint. Return the 'encoding' parameter to use for a given endpoint.
@ -401,10 +429,7 @@ class SchemaGenerator(object):
""" """
if hasattr(view, 'action'): if hasattr(view, 'action'):
# Viewsets have explicitly named actions. # Viewsets have explicitly named actions.
if view.action in self.coerce_actions: action = view.action
action = self.coerce_actions[view.action]
else:
action = view.action
else: else:
# Views have no associated action, so we determine one from the method. # Views have no associated action, so we determine one from the method.
if is_list_view(path, method, view): if is_list_view(path, method, view):

View File

@ -94,12 +94,12 @@ class TestRouterGeneratedSchema(TestCase):
action='get' action='get'
), ),
'custom_list_action_multiple_methods': { 'custom_list_action_multiple_methods': {
'read': coreapi.Link( 'retrieve': coreapi.Link(
url='/example/custom_list_action_multiple_methods/', url='/example/custom_list_action_multiple_methods/',
action='get' action='get'
) )
}, },
'read': coreapi.Link( 'retrieve': coreapi.Link(
url='/example/{pk}/', url='/example/{pk}/',
action='get', action='get',
fields=[ fields=[
@ -138,7 +138,7 @@ class TestRouterGeneratedSchema(TestCase):
coreapi.Field('b', required=False, location='form') coreapi.Field('b', required=False, location='form')
] ]
), ),
'read': coreapi.Link( 'retrieve': coreapi.Link(
url='/example/{pk}/', url='/example/{pk}/',
action='get', action='get',
fields=[ fields=[
@ -160,7 +160,7 @@ class TestRouterGeneratedSchema(TestCase):
action='get' action='get'
), ),
'custom_list_action_multiple_methods': { 'custom_list_action_multiple_methods': {
'read': coreapi.Link( 'retrieve': coreapi.Link(
url='/example/custom_list_action_multiple_methods/', url='/example/custom_list_action_multiple_methods/',
action='get' action='get'
), ),
@ -189,7 +189,7 @@ class TestRouterGeneratedSchema(TestCase):
coreapi.Field('b', required=False, location='form') coreapi.Field('b', required=False, location='form')
] ]
), ),
'delete': coreapi.Link( 'destroy': coreapi.Link(
url='/example/{pk}/', url='/example/{pk}/',
action='delete', action='delete',
fields=[ fields=[
@ -249,7 +249,7 @@ class TestSchemaGenerator(TestCase):
action='get', action='get',
fields=[] fields=[]
), ),
'read': coreapi.Link( 'retrieve': coreapi.Link(
url='/example/{pk}/', url='/example/{pk}/',
action='get', action='get',
fields=[ fields=[