-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2eabfe1
commit ff8d3ea
Showing
11 changed files
with
459 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
cms/djangoapps/contentstore/rest_api/v1/serializers/vertical_block.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
""" | ||
API Serializers for unit page | ||
""" | ||
|
||
from django.urls import reverse | ||
from rest_framework import serializers | ||
|
||
from cms.djangoapps.contentstore.helpers import ( | ||
xblock_studio_url, | ||
xblock_type_display_name, | ||
) | ||
|
||
|
||
class ChildAncestorSerializer(serializers.Serializer): | ||
""" | ||
Serializer for representing child blocks in the ancestor XBlock. | ||
""" | ||
|
||
url = serializers.SerializerMethodField() | ||
display_name = serializers.CharField(source="display_name_with_default") | ||
|
||
def get_url(self, obj): | ||
""" | ||
Method to generate studio URL for the child block. | ||
""" | ||
return xblock_studio_url(obj) | ||
|
||
|
||
class AncestorXBlockSerializer(serializers.Serializer): | ||
""" | ||
Serializer for representing the ancestor XBlock and its children. | ||
""" | ||
|
||
children = ChildAncestorSerializer(many=True) | ||
title = serializers.CharField() | ||
is_last = serializers.BooleanField() | ||
|
||
|
||
class ContainerXBlock(serializers.Serializer): | ||
""" | ||
Serializer for representing XBlock data. Doesn't include all data about XBlock. | ||
""" | ||
|
||
display_name = serializers.CharField(source="display_name_with_default") | ||
display_type = serializers.SerializerMethodField() | ||
category = serializers.CharField() | ||
|
||
def get_display_type(self, obj): | ||
""" | ||
Method to get the display type name for the container XBlock. | ||
""" | ||
return xblock_type_display_name(obj) | ||
|
||
|
||
class ContainerHandlerSerializer(serializers.Serializer): | ||
""" | ||
Serializer for container handler | ||
""" | ||
|
||
language_code = serializers.CharField() | ||
action = serializers.CharField() | ||
xblock = ContainerXBlock() | ||
is_unit_page = serializers.BooleanField() | ||
is_collapsible = serializers.BooleanField() | ||
position = serializers.IntegerField(min_value=1) | ||
prev_url = serializers.CharField(allow_null=True) | ||
next_url = serializers.CharField(allow_null=True) | ||
new_unit_category = serializers.CharField() | ||
outline_url = serializers.CharField() | ||
ancestor_xblocks = AncestorXBlockSerializer(many=True) | ||
component_templates = serializers.ListField(child=serializers.DictField()) | ||
xblock_info = serializers.DictField() | ||
draft_preview_link = serializers.CharField() | ||
published_preview_link = serializers.CharField() | ||
show_unit_tags = serializers.BooleanField() | ||
user_clipboard = serializers.DictField() | ||
is_fullwidth_content = serializers.BooleanField() | ||
assets_url = serializers.SerializerMethodField() | ||
unit_block_id = serializers.CharField(source="unit.location.block_id") | ||
subsection_location = serializers.CharField(source="subsection.location") | ||
|
||
def get_assets_url(self, obj): | ||
""" | ||
Method to get the assets URL based on the course id. | ||
""" | ||
|
||
context_course = obj.get("context_course", None) | ||
if context_course: | ||
return reverse( | ||
"assets_handler", kwargs={"course_key_string": context_course.id} | ||
) | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,3 +15,4 @@ | |
VideoDownloadView | ||
) | ||
from .help_urls import HelpUrlsView | ||
from .vertical_block import ContainerHandlerView |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
cms/djangoapps/contentstore/rest_api/v1/views/tests/test_vertical_block.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
""" | ||
Unit tests for the vertical block. | ||
""" | ||
from django.urls import reverse | ||
from rest_framework import status | ||
|
||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase | ||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order | ||
from xmodule.modulestore.tests.factories import BlockFactory # lint-amnesty, pylint: disable=wrong-import-order | ||
|
||
|
||
class ContainerHandlerViewTest(CourseTestCase): | ||
""" | ||
Unit tests for the ContainerHandlerView. | ||
""" | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.chapter = BlockFactory.create( | ||
parent=self.course, category="chapter", display_name="Week 1" | ||
) | ||
self.sequential = BlockFactory.create( | ||
parent=self.chapter, category="sequential", display_name="Lesson 1" | ||
) | ||
self.vertical = self._create_block(self.sequential, "vertical", "Unit") | ||
|
||
self.store = modulestore() | ||
self.store.publish(self.vertical.location, self.user.id) | ||
|
||
def _get_reverse_url(self, location): | ||
""" | ||
Creates url to current handler view api | ||
""" | ||
return reverse( | ||
"cms.djangoapps.contentstore:v1:container_handler", | ||
kwargs={"usage_key_string": location}, | ||
) | ||
|
||
def _create_block(self, parent, category, display_name, **kwargs): | ||
""" | ||
Creates a block without publishing it. | ||
""" | ||
return BlockFactory.create( | ||
parent=parent, | ||
category=category, | ||
display_name=display_name, | ||
publish_item=False, | ||
user_id=self.user.id, | ||
**kwargs | ||
) | ||
|
||
def test_success_response(self): | ||
""" | ||
Check that endpoint is valid and success response. | ||
""" | ||
url = self._get_reverse_url(self.vertical.location) | ||
response = self.client.get(url) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
|
||
def test_not_valid_usage_key_string(self): | ||
""" | ||
Check that invalid 'usage_key_string' raises Http404. | ||
""" | ||
usage_key_string = "i4x://InvalidOrg/InvalidCourse/vertical/static/InvalidContent" | ||
url = self._get_reverse_url(usage_key_string) | ||
response = self.client.get(url) | ||
self.assertEqual(response.status_code, 404) |
143 changes: 143 additions & 0 deletions
143
cms/djangoapps/contentstore/rest_api/v1/views/vertical_block.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
""" API Views for unit page """ | ||
|
||
import edx_api_doc_tools as apidocs | ||
from django.http import Http404, HttpResponseBadRequest | ||
from opaque_keys import InvalidKeyError | ||
from opaque_keys.edx.keys import UsageKey | ||
from rest_framework.request import Request | ||
from rest_framework.response import Response | ||
from rest_framework.views import APIView | ||
|
||
from cms.djangoapps.contentstore.utils import get_container_handler_context | ||
from cms.djangoapps.contentstore.views.component import _get_item_in_course | ||
from cms.djangoapps.contentstore.rest_api.v1.serializers import ContainerHandlerSerializer | ||
from openedx.core.lib.api.view_utils import view_auth_classes | ||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order | ||
from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order | ||
|
||
|
||
@view_auth_classes(is_authenticated=True) | ||
class ContainerHandlerView(APIView): | ||
""" | ||
View for container xblock requests to get vertical data. | ||
""" | ||
|
||
def get_object(self, usage_key_string): | ||
""" | ||
Get an object by usage-id of the block | ||
""" | ||
try: | ||
usage_key = UsageKey.from_string(usage_key_string) | ||
except InvalidKeyError: | ||
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from | ||
return usage_key | ||
|
||
@apidocs.schema( | ||
parameters=[ | ||
apidocs.string_parameter( | ||
"usage_key_string", | ||
apidocs.ParameterLocation.PATH, | ||
description="Container usage key", | ||
), | ||
], | ||
responses={ | ||
200: ContainerHandlerSerializer, | ||
401: "The requester is not authenticated.", | ||
404: "The requested locator does not exist.", | ||
}, | ||
) | ||
def get(self, request: Request, usage_key_string: str): | ||
""" | ||
Get an object containing vertical data. | ||
**Example Request** | ||
GET /api/contentstore/v1/container_handler/{usage_key_string} | ||
**Response Values** | ||
If the request is successful, an HTTP 200 "OK" response is returned. | ||
The HTTP 200 response contains a single dict that contains keys that | ||
are the vertical's container data. | ||
**Example Response** | ||
```json | ||
{ | ||
"language_code": "zh-cn", | ||
"action": "view", | ||
"xblock": { | ||
"display_name": "Labs and Demos", | ||
"display_type": "单元", | ||
"category": "vertical" | ||
}, | ||
"is_unit_page": true, | ||
"is_collapsible": false, | ||
"position": 1, | ||
"prev_url": "block-v1-edX%2BDemo_Course%2Btype%40vertical%2Bblock%404e592689563243c484", | ||
"next_url": "block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40vertical%2Bblock%40vertical_aae927868e55", | ||
"new_unit_category": "vertical", | ||
"outline_url": "/course/course-v1:edX+DemoX+Demo_Course?format=concise", | ||
"ancestor_xblocks": [ | ||
{ | ||
"children": [ | ||
{ | ||
"url": "/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%", | ||
"display_name": "Introduction" | ||
}, | ||
... | ||
], | ||
"title": "Example Week 2: Get Interactive", | ||
"is_last": false | ||
}, | ||
... | ||
], | ||
"component_templates": [ | ||
{ | ||
"type": "advanced", | ||
"templates": [ | ||
{ | ||
"display_name": "批注", | ||
"category": "annotatable", | ||
"boilerplate_name": null, | ||
"hinted": false, | ||
"tab": "common", | ||
"support_level": true | ||
}, | ||
... | ||
}, | ||
... | ||
], | ||
"xblock_info": {}, | ||
"draft_preview_link": "//preview.localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/...", | ||
"published_preview_link": "///courses/course-v1:edX+DemoX+Demo_Course/jump_to/...", | ||
"show_unit_tags": false, | ||
"user_clipboard": { | ||
"content": null, | ||
"source_usage_key": "", | ||
"source_context_title": "", | ||
"source_edit_url": "" | ||
}, | ||
"is_fullwidth_content": false, | ||
"assets_url": "/assets/course-v1:edX+DemoX+Demo_Course/", | ||
"unit_block_id": "d6cee45205a449369d7ef8f159b22bdf", | ||
"subsection_location": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@graded_simulations" | ||
} | ||
``` | ||
""" | ||
usage_key = self.get_object(usage_key_string) | ||
course_key = usage_key.course_key | ||
with modulestore().bulk_operations(course_key): | ||
try: | ||
course, xblock, lms_link, preview_lms_link = _get_item_in_course(request, usage_key) | ||
except ItemNotFoundError: | ||
return HttpResponseBadRequest() | ||
|
||
context = get_container_handler_context(request, usage_key, course, xblock) | ||
context.update({ | ||
'draft_preview_link': preview_lms_link, | ||
'published_preview_link': lms_link, | ||
}) | ||
serializer = ContainerHandlerSerializer(context) | ||
return Response(serializer.data) |
Oops, something went wrong.