From f7cd63f7846617a1f66f300823e04effb28a22c0 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 17 Sep 2019 16:18:34 -0400 Subject: [PATCH] Convert bookmarks and certificates API docs to yasg format --- lms/djangoapps/certificates/apis/v0/views.py | 42 +++--- .../core/djangoapps/bookmarks/serializers.py | 34 ++++- openedx/core/djangoapps/bookmarks/views.py | 121 +++++++++--------- 3 files changed, 110 insertions(+), 87 deletions(-) diff --git a/lms/djangoapps/certificates/apis/v0/views.py b/lms/djangoapps/certificates/apis/v0/views.py index 7d7561ee6b..df7805cd50 100644 --- a/lms/djangoapps/certificates/apis/v0/views.py +++ b/lms/djangoapps/certificates/apis/v0/views.py @@ -5,6 +5,7 @@ import logging import six from django.contrib.auth import get_user_model +from django.utils.decorators import method_decorator from edx_rest_framework_extensions import permissions from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser @@ -20,6 +21,8 @@ from openedx.core.djangoapps.certificates.api import certificates_viewable_for_c from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.user_api.accounts.api import visible_fields from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.openapi import swagger_auto_schema, openapi + log = logging.getLogger(__name__) User = get_user_model() @@ -131,21 +134,16 @@ class CertificatesDetailView(GenericAPIView): ) -class CertificatesListView(GenericAPIView): - """ +@method_decorator(name='get', decorator=swagger_auto_schema( + operation_summary="Get a paginated list of bookmarks for a user.", + operation_description=u"""\ **Use Case** - * Get the list of viewable course certificates for a specific user. + Get the list of viewable course certificates for a specific user. **Example Request** - GET /api/certificates/v0/certificates/{username} - - **GET Parameters** - - A GET request must include the following parameters. - - * username: A string representation of an user's username. + GET /api/certificates/v0/certificates/{username} **GET Response Values** @@ -187,8 +185,18 @@ class CertificatesListView(GenericAPIView): "download_url": "http://www.example.com/cert.pdf", "grade": "0.98" }] - """ - + """, + manual_parameters=[ + openapi.Parameter( + 'username', + openapi.IN_PATH, + type=openapi.TYPE_STRING, + description="The users to get certificates for", + ), + ], +)) +class CertificatesListView(GenericAPIView): + """REST API endpoints for listing certificates.""" authentication_classes = ( JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, @@ -209,16 +217,6 @@ class CertificatesListView(GenericAPIView): required_scopes = ['certificates:read'] def get(self, request, username): - """ - Gets the list of viewable course certificates for a specific user. - - Args: - request (Request): Django request object. - username (string): URI element specifying the user's username. - - Return: - A JSON serialized representation of the list of certificates. - """ user_certs = [] if self._viewable_by_requestor(request, username): for user_cert in self._get_certificates_for_user(username): diff --git a/openedx/core/djangoapps/bookmarks/serializers.py b/openedx/core/djangoapps/bookmarks/serializers.py index c476d5de7c..f38cb51ea6 100644 --- a/openedx/core/djangoapps/bookmarks/serializers.py +++ b/openedx/core/djangoapps/bookmarks/serializers.py @@ -7,8 +7,10 @@ import six from rest_framework import serializers from openedx.core.lib.api.serializers import CourseKeyField, UsageKeyField +from openedx.core.openapi import is_schema_request -from . import DEFAULT_FIELDS + +from . import DEFAULT_FIELDS, OPTIONAL_FIELDS from .models import Bookmark @@ -16,12 +18,27 @@ class BookmarkSerializer(serializers.ModelSerializer): """ Serializer for the Bookmark model. """ - id = serializers.SerializerMethodField() # pylint: disable=invalid-name - course_id = CourseKeyField(source='course_key') - usage_id = UsageKeyField(source='usage_key') + id = serializers.SerializerMethodField( # pylint: disable=invalid-name + help_text=u"The identifier string for the bookmark: {user_id},{usage_id}.", + ) + course_id = CourseKeyField( + source='course_key', + help_text=u"The identifier string of the bookmark's course.", + ) + usage_id = UsageKeyField( + source='usage_key', + help_text=u"The identifier string of the bookmark's XBlock.", + ) block_type = serializers.ReadOnlyField(source='usage_key.block_type') - display_name = serializers.ReadOnlyField() - path = serializers.SerializerMethodField() + display_name = serializers.ReadOnlyField( + help_text=u"Display name of the XBlock.", + ) + path = serializers.SerializerMethodField( + help_text=u""" + List of dicts containing {"usage_id": , display_name:} + for the XBlocks from the top of the course tree till the parent of the bookmarked XBlock. + """, + ) def __init__(self, *args, **kwargs): # Don't pass the 'fields' arg up to the superclass @@ -34,6 +51,11 @@ class BookmarkSerializer(serializers.ModelSerializer): # Drop any fields that are not specified in the `fields` argument. required_fields = set(fields) + + if 'request' in kwargs['context'] and is_schema_request(kwargs['context']['request']): + # We are serving the schema: include everything + required_fields.update(OPTIONAL_FIELDS) + all_fields = set(self.fields.keys()) for field_name in all_fields - required_fields: self.fields.pop(field_name) diff --git a/openedx/core/djangoapps/bookmarks/views.py b/openedx/core/djangoapps/bookmarks/views.py index 9f3cc2c01a..667bb0790a 100644 --- a/openedx/core/djangoapps/bookmarks/views.py +++ b/openedx/core/djangoapps/bookmarks/views.py @@ -11,6 +11,7 @@ import logging import eventtracking from django.conf import settings from django.core.exceptions import ObjectDoesNotExist +from django.utils.decorators import method_decorator from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_noop from edx_rest_framework_extensions.paginators import DefaultPagination @@ -26,6 +27,7 @@ from rest_framework_oauth.authentication import OAuth2Authentication from openedx.core.djangoapps.bookmarks.api import BookmarksLimitReachedError from openedx.core.lib.api.permissions import IsUserInUrl from openedx.core.lib.url_utils import unquote_slashes +from openedx.core.openapi import swagger_auto_schema, openapi from xmodule.modulestore.exceptions import ItemNotFoundError from . import DEFAULT_FIELDS, OPTIONAL_FIELDS, api @@ -95,68 +97,47 @@ class BookmarksViewMixin(object): ) -class BookmarksListView(ListCreateAPIView, BookmarksViewMixin): - """ - **Use Case** +@method_decorator(name='get', decorator=swagger_auto_schema( + operation_summary="Get a paginated list of bookmarks for a user.", + operation_description=u""" + The list can be filtered by passing parameter "course_id=" + to only include bookmarks from a particular course. - * Get a paginated list of bookmarks for a user. + The bookmarks are always sorted in descending order by creation date. - The list can be filtered by passing parameter "course_id=" - to only include bookmarks from a particular course. + Each page in the list contains 10 bookmarks by default. The page + size can be altered by passing parameter "page_size=". - The bookmarks are always sorted in descending order by creation date. + To include the optional fields pass the values in "fields" parameter + as a comma separated list. Possible values are: - Each page in the list contains 10 bookmarks by default. The page - size can be altered by passing parameter "page_size=". + * "display_name" + * "path" - To include the optional fields pass the values in "fields" parameter - as a comma separated list. Possible values are: - - * "display_name" - * "path" - - * Create a new bookmark for a user. - - The POST request only needs to contain one parameter "usage_id". - - Http400 is returned if the format of the request is not correct, - the usage_id is invalid or a block corresponding to the usage_id - could not be found. - - **Example Requests** + # Example Requests GET /api/bookmarks/v1/bookmarks/?course_id={course_id1}&fields=display_name,path + """, + manual_parameters=[ + openapi.Parameter( + 'course_id', + openapi.IN_QUERY, + type=openapi.TYPE_STRING, + description="The id of the course to limit the list", + ), + openapi.Parameter( + 'fields', + openapi.IN_QUERY, + type=openapi.TYPE_STRING, + description=""" + The fields to return: display_name, path. + """, + ), + ], +)) +class BookmarksListView(ListCreateAPIView, BookmarksViewMixin): + """REST endpoints for lists of bookmarks.""" - POST /api/bookmarks/v1/bookmarks/ - Request data: {"usage_id": } - - **Response Values** - - * count: The number of bookmarks in a course. - - * next: The URI to the next page of bookmarks. - - * previous: The URI to the previous page of bookmarks. - - * num_pages: The number of pages listing bookmarks. - - * results: A list of bookmarks returned. Each collection in the list - contains these fields. - - * id: String. The identifier string for the bookmark: {user_id},{usage_id}. - - * course_id: String. The identifier string of the bookmark's course. - - * usage_id: String. The identifier string of the bookmark's XBlock. - - * display_name: String. (optional) Display name of the XBlock. - - * path: List. (optional) List of dicts containing {"usage_id": , display_name:} - for the XBlocks from the top of the course tree till the parent of the bookmarked XBlock. - - * created: ISO 8601 String. The timestamp of bookmark's creation. - - """ authentication_classes = (OAuth2Authentication, SessionAuthentication) pagination_class = BookmarksPagination permission_classes = (permissions.IsAuthenticated,) @@ -220,11 +201,24 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin): return page - def post(self, request): - """ - POST /api/bookmarks/v1/bookmarks/ - Request data: {"usage_id": ""} - """ + @swagger_auto_schema( + operation_summary="Create a new bookmark for a user.", + operation_description=u""" + The POST request only needs to contain one parameter "usage_id". + + Http400 is returned if the format of the request is not correct, + the usage_id is invalid or a block corresponding to the usage_id + could not be found. + + # Example Requests + + POST /api/bookmarks/v1/bookmarks/ + Request data: {"usage_id": } + + """, + ) + def post(self, request, *unused_args, **unused_kwargs): + """Create a new bookmark for a user.""" if not request.data: return self.error_response(ugettext_noop(u'No data provided.'), DEFAULT_USER_MESSAGE) @@ -320,6 +314,15 @@ class BookmarksDetailView(APIView, BookmarksViewMixin): log.error(error_message) return self.error_response(error_message, error_status=status.HTTP_404_NOT_FOUND) + @swagger_auto_schema( + operation_summary="Get a specific bookmark for a user.", + operation_description=u""" + # Example Requests + + GET /api/bookmarks/v1/bookmarks/{username},{usage_id}/?fields=display_name,path + + """, + ) def get(self, request, username=None, usage_id=None): """ GET /api/bookmarks/v1/bookmarks/{username},{usage_id}?fields=display_name,path