Allow @detail_route and @list_route methods to have docstring sections.

If a method decorated as a @detail_route or @list_route has a docstring
that includes sections named after the action for the http methods that
are supported for that route, then they will be used for the description
in the schema associated with that http method for the view.
This commit is contained in:
Tommy Beadle 2017-04-07 11:41:06 -04:00
parent aa10958627
commit f772d72093
3 changed files with 75 additions and 17 deletions

View File

@ -69,6 +69,20 @@ When using viewsets, you should use the relevant action names as delimiters.
Create a new user instance.
"""
When using the `@detail_route` or `@list_route` decorators, the docstring for
the decorated method may use the `action:` style delimiters.
class UserViewSet(viewsets.ModelViewSet):
@detail_route(methods=('get', 'post'))
def groups(self, request, pk=None):
"""
retrieve:
Return the groups for the given user.
create:
Add the user to a new group.
"""
---
## Third party packages

View File

@ -8,7 +8,7 @@ from django.core.exceptions import PermissionDenied
from django.db import models
from django.http import Http404
from django.utils import six
from django.utils.encoding import force_text, smart_text
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from rest_framework import exceptions, renderers, serializers
@ -19,7 +19,6 @@ from rest_framework.compat import (
from rest_framework.request import clone_request
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.utils import formatting
from rest_framework.utils.model_meta import _get_pk
from rest_framework.views import APIView
@ -467,31 +466,36 @@ class SchemaGenerator(object):
This will be based on the method docstring if one exists,
or else the class docstring.
"""
method_name = getattr(view, 'action', method.lower())
method_docstring = getattr(view, method_name, None).__doc__
if method_docstring:
method = method.lower()
method_name = getattr(view, 'action', method)
view_method = getattr(view, method_name, None)
description = None
if view_method:
# An explicit docstring on the method or action.
return formatting.dedent(smart_text(method_docstring))
description = view.get_view_description()
description = api_settings.VIEW_DESCRIPTION_FUNCTION(view_method)
if not description:
description = view.get_view_description()
lines = [line.strip() for line in description.splitlines()]
current_section = ''
sections = {'': ''}
for line in lines:
if header_regex.match(line):
current_section, seperator, lead = line.partition(':')
current_section, separator, lead = line.partition(':')
sections[current_section] = lead.strip()
else:
sections[current_section] += '\n' + line
header = getattr(view, 'action', method.lower())
if header in sections:
return sections[header].strip()
if header in self.coerce_method_names:
if self.coerce_method_names[header] in sections:
return sections[self.coerce_method_names[header]].strip()
return sections[''].strip()
section_name = ''
for attempt in (method_name, self.default_mapping.get(method, method)):
if attempt in sections:
section_name = attempt
break
if self.coerce_method_names.get(attempt) in sections:
section_name = self.coerce_method_names[attempt]
break
return sections[section_name].strip()
def get_encoding(self, path, method, view):
"""

View File

@ -55,7 +55,19 @@ class ExampleViewSet(ModelViewSet):
"""
A description of custom action.
"""
return super(ExampleSerializer, self).retrieve(self, request)
return super(ExampleViewSet, self).retrieve(self, request)
@detail_route(methods=['get', 'post'], serializer_class=EmptySerializer)
def custom_action2(self, request, pk):
"""
read: A description for getting items.
create: A description for creating items.
"""
if request.method == 'GET':
return super(ExampleViewSet, self).retrieve(self, request)
else:
return super(ExampleViewSet, self).create(self, request)
@list_route()
def custom_list_action(self, request):
@ -105,6 +117,16 @@ class TestRouterGeneratedSchema(TestCase):
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
]
),
'custom_action2': {
'read': coreapi.Link(
url='/example/{id}/custom_action2/',
action='get',
description='A description for getting items.',
fields=[
coreapi.Field('id', required=True, location='path', schema=coreschema.String()),
],
),
},
'custom_list_action': coreapi.Link(
url='/example/custom_list_action/',
action='get'
@ -173,6 +195,24 @@ class TestRouterGeneratedSchema(TestCase):
coreapi.Field('d', required=False, location='form', schema=coreschema.String(title='D')),
]
),
'custom_action2': {
'read': coreapi.Link(
url='/example/{id}/custom_action2/',
action='get',
description='A description for getting items.',
fields=[
coreapi.Field('id', required=True, location='path', schema=coreschema.String()),
],
),
'create': coreapi.Link(
url='/example/{id}/custom_action2/',
action='post',
description='A description for creating items.',
fields=[
coreapi.Field('id', required=True, location='path', schema=coreschema.String()),
],
),
},
'custom_list_action': coreapi.Link(
url='/example/custom_list_action/',
action='get'