forked from openedx/edx-platform
-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [AXIMST-26] API endpoint to return components in unit (#2488)
- Loading branch information
1 parent
ef972dc
commit 990a6f1
Showing
8 changed files
with
406 additions
and
18 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
110 changes: 110 additions & 0 deletions
110
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,110 @@ | ||
""" | ||
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 | ||
|
||
|
||
class ChildVerticalContainerSerializer(serializers.Serializer): | ||
""" | ||
Serializer for representing a xblock child of vertical container. | ||
""" | ||
|
||
name = serializers.CharField(source="display_name_with_default") | ||
block_id = serializers.CharField(source="location") | ||
|
||
|
||
class VerticalContainerSerializer(serializers.Serializer): | ||
""" | ||
Serializer for representing a vertical container with state and children. | ||
""" | ||
|
||
children = ChildVerticalContainerSerializer(many=True) | ||
is_published = serializers.BooleanField() |
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
178 changes: 178 additions & 0 deletions
178
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,178 @@ | ||
""" | ||
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 | ||
from xmodule.modulestore import ( | ||
ModuleStoreEnum, | ||
) # lint-amnesty, pylint: disable=wrong-import-order | ||
|
||
|
||
class BaseXBlockContainer(CourseTestCase): | ||
""" | ||
Base xBlock container handler. | ||
Contains common function for processing course xblocks. | ||
""" | ||
|
||
view_name = None | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.store = modulestore() | ||
self.setup_xblock() | ||
|
||
def setup_xblock(self): | ||
""" | ||
Set up XBlock objects for testing purposes. | ||
This method creates XBlock objects representing a course structure with chapters, | ||
sequentials, verticals and others. | ||
""" | ||
self.chapter = self.create_block( | ||
parent=self.course.location, | ||
category="chapter", | ||
display_name="Week 1", | ||
) | ||
|
||
self.sequential = self.create_block( | ||
parent=self.chapter.location, | ||
category="sequential", | ||
display_name="Lesson 1", | ||
) | ||
|
||
self.vertical = self.create_block(self.sequential.location, "vertical", "Unit") | ||
|
||
self.html_unit_first = self.create_block( | ||
parent=self.vertical.location, | ||
category="html", | ||
display_name="Html Content 1", | ||
) | ||
|
||
self.html_unit_second = self.create_block( | ||
parent=self.vertical.location, | ||
category="html", | ||
display_name="Html Content 2", | ||
) | ||
|
||
def create_block(self, parent, category, display_name, **kwargs): | ||
""" | ||
Creates a block without publishing it. | ||
""" | ||
return BlockFactory.create( | ||
parent_location=parent, | ||
category=category, | ||
display_name=display_name, | ||
modulestore=self.store, | ||
publish_item=False, | ||
user_id=self.user.id, | ||
**kwargs, | ||
) | ||
|
||
def get_reverse_url(self, location): | ||
""" | ||
Creates url to current view api name | ||
""" | ||
return reverse( | ||
f"cms.djangoapps.contentstore:v1:{self.view_name}", | ||
kwargs={"usage_key_string": location}, | ||
) | ||
|
||
def publish_item(self, store, item_location): | ||
""" | ||
Publish the item at the given location | ||
""" | ||
with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): | ||
store.publish(item_location, ModuleStoreEnum.UserID.test) | ||
|
||
|
||
class ContainerHandlerViewTest(BaseXBlockContainer): | ||
""" | ||
Unit tests for the ContainerHandlerView. | ||
""" | ||
|
||
view_name = "container_handler" | ||
|
||
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) | ||
|
||
|
||
class ContainerVerticalViewTest(BaseXBlockContainer): | ||
""" | ||
Unit tests for the ContainerVerticalViewTest. | ||
""" | ||
|
||
view_name = "container_vertical" | ||
|
||
def test_success_response(self): | ||
""" | ||
Check that endpoint returns valid response data. | ||
""" | ||
url = self.get_reverse_url(self.vertical.location) | ||
response = self.client.get(url) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
self.assertEqual(len(response.data["children"]), 2) | ||
self.assertFalse(response.data["is_published"]) | ||
|
||
def test_xblock_is_published(self): | ||
""" | ||
Check that published xBlock container returns. | ||
""" | ||
self.publish_item(self.store, self.vertical.location) | ||
url = self.get_reverse_url(self.vertical.location) | ||
response = self.client.get(url) | ||
self.assertTrue(response.data["is_published"]) | ||
|
||
def test_children_content(self): | ||
""" | ||
Check that returns valid response with children of vertical container. | ||
""" | ||
url = self.get_reverse_url(self.vertical.location) | ||
response = self.client.get(url) | ||
|
||
expected_response = [ | ||
{ | ||
"name": self.html_unit_first.display_name_with_default, | ||
"block_id": str(self.html_unit_first.location), | ||
}, | ||
{ | ||
"name": self.html_unit_second.display_name_with_default, | ||
"block_id": str(self.html_unit_second.location), | ||
}, | ||
] | ||
self.assertEqual(response.data["children"], expected_response) | ||
|
||
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) |
Oops, something went wrong.