From 1084dca22b0b23e87ff37708f8071cfbf4f23bb4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 5 Oct 2016 16:14:23 +0100 Subject: [PATCH] Better descriptions for schemas --- docs/api-guide/schemas.md | 55 +++++++++++++++++++++++++++++++++++++++ rest_framework/schemas.py | 21 ++++++++------- tests/test_schemas.py | 4 +++ 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 868207dac..16d2bbb01 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -247,6 +247,61 @@ You could then either: One common usage of API schemas is to use them to build documentation pages. The schema generation in REST framework uses docstrings to automatically +populate descriptions in the schema document. + +These descriptions will be based on: + +* The corresponding method docstring if one exists. +* A named section within the class docstring, which can be either single line or multi-line. +* The class docstring. + +## Examples + +An `APIView`, with an explicit method docstring. + + class ListUsernames(APIView): + def get(self, request): + """ + Return a list of all user names in the system. + """ + usernames = [user.username for user in User.objects.all()] + return Response(usernames) + +A `ViewSet`, with an explict action docstring. + + class ListUsernames(ViewSet): + def list(self, request): + """ + Return a list of all user names in the system. + """ + usernames = [user.username for user in User.objects.all()] + return Response(usernames) + +A generic view with sections in the class docstring, using single-line style. + + class UserList(generics.ListCreateAPIView): + """ + get: Create a new user. + post: List all the users. + """ + queryset = User.objects.all() + serializer_class = UserSerializer + permission_classes = (IsAdminUser,) + +A generic viewset with sections in the class docstring, using multi-line style. + + class UserViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows users to be viewed or edited. + + retrieve: + Return a user instance. + + list: + Return all users, ordered by most recently joined. + """ + queryset = User.objects.all().order_by('-date_joined') + serializer_class = UserSerializer --- diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index dc4d8acb7..ad311dcfd 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -5,7 +5,7 @@ from importlib import import_module from django.conf import settings from django.contrib.admindocs.views import simplify_regex from django.utils import six -from django.utils.encoding import force_text +from django.utils.encoding import force_text, smart_text from rest_framework import exceptions, renderers, serializers from rest_framework.compat import ( @@ -14,6 +14,7 @@ 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.views import APIView header_regex = re.compile('^[a-zA-Z][0-9A-Za-z_]*:') @@ -297,15 +298,17 @@ class SchemaGenerator(object): """ 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. + This will be based on the method docstring if one exists, + or else the class docstring. """ - view_description = view.get_view_description() - lines = [line.strip() for line in view_description.splitlines()] + method_name = getattr(view, 'action', method.lower()) + method_docstring = getattr(view, method_name, None).__doc__ + if method_docstring: + # An explicit docstring on the method or action. + return formatting.dedent(smart_text(method_docstring)) + + description = view.get_view_description() + lines = [line.strip() for line in description.splitlines()] current_section = '' sections = {'': ''} diff --git a/tests/test_schemas.py b/tests/test_schemas.py index 1fc7c218e..edc602d98 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -46,6 +46,9 @@ class ExampleViewSet(ModelViewSet): @detail_route(methods=['post'], serializer_class=AnotherSerializer) def custom_action(self, request, pk): + """ + A description of custom action. + """ return super(ExampleSerializer, self).retrieve(self, request) @list_route() @@ -149,6 +152,7 @@ class TestRouterGeneratedSchema(TestCase): url='/example/{pk}/custom_action/', action='post', encoding='application/json', + description='A description of custom action.', fields=[ coreapi.Field('pk', required=True, location='path'), coreapi.Field('c', required=True, location='form'),