Merge pull request #23246 from open-craft/samuel/lx-744-more-metadata
[SE-2264] [LX-744] Return more metadata in xblock api
This commit is contained in:
@@ -32,6 +32,7 @@ URL_LIB_BLOCK_ASSET_FILE = URL_LIB_BLOCK + 'assets/{file_name}' # Get, delete,
|
||||
|
||||
URL_BLOCK_RENDER_VIEW = '/api/xblock/v2/xblocks/{block_key}/view/{view_name}/'
|
||||
URL_BLOCK_GET_HANDLER_URL = '/api/xblock/v2/xblocks/{block_key}/handler_url/{handler_name}/'
|
||||
URL_BLOCK_METADATA_URL = '/api/xblock/v2/xblocks/{block_key}/'
|
||||
|
||||
|
||||
# Decorator for tests that require blockstore
|
||||
@@ -72,9 +73,9 @@ class ContentLibrariesRestApiTest(APITestCase):
|
||||
# is not yet any Studio REST API for doing so:
|
||||
cls.collection = blockstore_api.create_collection("Content Library Test Collection")
|
||||
# Create an organization
|
||||
cls.organization = Organization.objects.create(
|
||||
name="Content Libraries Tachyon Exploration & Survey Team",
|
||||
cls.organization, _ = Organization.objects.get_or_create(
|
||||
short_name="CL-TEST",
|
||||
defaults={"name": "Content Libraries Tachyon Exploration & Survey Team"},
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
|
||||
@@ -16,6 +16,7 @@ from openedx.core.djangoapps.content_libraries.tests.base import (
|
||||
requires_blockstore,
|
||||
URL_BLOCK_RENDER_VIEW,
|
||||
URL_BLOCK_GET_HANDLER_URL,
|
||||
URL_BLOCK_METADATA_URL,
|
||||
)
|
||||
from openedx.core.djangoapps.content_libraries.tests.user_state_block import UserStateTestBlock
|
||||
from openedx.core.djangoapps.xblock import api as xblock_api
|
||||
@@ -113,6 +114,57 @@ class ContentLibraryRuntimeTest(ContentLibraryContentTestMixin, TestCase):
|
||||
# And problems do have has_score True:
|
||||
self.assertEqual(problem_block.has_score, True)
|
||||
|
||||
@skip_unless_cms # creating child blocks only works properly in Studio
|
||||
def test_xblock_metadata(self):
|
||||
"""
|
||||
Test the XBlock metadata API
|
||||
"""
|
||||
unit_block_key = library_api.create_library_block(self.library.key, "unit", "metadata-u1").usage_key
|
||||
problem_key = library_api.create_library_block_child(unit_block_key, "problem", "metadata-p1").usage_key
|
||||
new_olx = """
|
||||
<problem display_name="New Multi Choice Question" max_attempts="5">
|
||||
<multiplechoiceresponse>
|
||||
<p>This is a normal capa problem. It has "maximum attempts" set to **5**.</p>
|
||||
<label>Blockstore is designed to store.</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">XBlock metadata only</choice>
|
||||
<choice correct="true">XBlock data/metadata and associated static asset files</choice>
|
||||
<choice correct="false">Static asset files for XBlocks and courseware</choice>
|
||||
<choice correct="false">XModule metadata only</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
""".strip()
|
||||
library_api.set_library_block_olx(problem_key, new_olx)
|
||||
library_api.publish_changes(self.library.key)
|
||||
|
||||
# Now view the problem as Alice:
|
||||
client = APIClient()
|
||||
client.login(username=self.student_a.username, password='edx')
|
||||
|
||||
# Check the metadata API for the unit:
|
||||
metadata_view_result = client.get(
|
||||
URL_BLOCK_METADATA_URL.format(block_key=unit_block_key),
|
||||
{"include": "children,editable_children"},
|
||||
)
|
||||
self.assertEqual(metadata_view_result.data["children"], [str(problem_key)])
|
||||
self.assertEqual(metadata_view_result.data["editable_children"], [str(problem_key)])
|
||||
|
||||
# Check the metadata API for the problem:
|
||||
metadata_view_result = client.get(
|
||||
URL_BLOCK_METADATA_URL.format(block_key=problem_key),
|
||||
{"include": "student_view_data,index_dictionary"},
|
||||
)
|
||||
self.assertEqual(metadata_view_result.data["block_id"], str(problem_key))
|
||||
self.assertEqual(metadata_view_result.data["display_name"], "New Multi Choice Question")
|
||||
self.assertNotIn("children", metadata_view_result.data)
|
||||
self.assertNotIn("editable_children", metadata_view_result.data)
|
||||
self.assertDictContainsSubset({
|
||||
"content_type": "CAPA",
|
||||
"problem_types": ["multiplechoiceresponse"],
|
||||
}, metadata_view_result.data["index_dictionary"])
|
||||
self.assertEqual(metadata_view_result.data["student_view_data"], None) # Capa doesn't provide student_view_data
|
||||
|
||||
|
||||
@requires_blockstore
|
||||
# We can remove the line below to enable this in Studio once we implement a session-backed
|
||||
@@ -339,6 +391,7 @@ class ContentLibraryXBlockUserStateTest(ContentLibraryContentTestMixin, TestCase
|
||||
student_view_result = client.get(URL_BLOCK_RENDER_VIEW.format(block_key=block_id, view_name='student_view'))
|
||||
problem_key = "input_{}_2_1".format(block_id)
|
||||
self.assertIn(problem_key, student_view_result.data["content"])
|
||||
|
||||
# And submit a wrong answer:
|
||||
result = client.get(URL_BLOCK_GET_HANDLER_URL.format(block_key=block_id, handler_name='xmodule_handler'))
|
||||
problem_check_url = result.data["handler_url"] + 'problem_check'
|
||||
|
||||
@@ -79,16 +79,47 @@ def load_block(usage_key, user):
|
||||
return runtime.get_block(usage_key)
|
||||
|
||||
|
||||
def get_block_metadata(block):
|
||||
def get_block_metadata(block, includes=()):
|
||||
"""
|
||||
Get metadata about the specified XBlock
|
||||
Get metadata about the specified XBlock.
|
||||
|
||||
This metadata is the same for all users. Any data which varies per-user must
|
||||
be served from a different API.
|
||||
|
||||
Optionally provide a list or set of metadata keys to include. Valid keys are:
|
||||
index_dictionary: a dictionary of data used to add this XBlock's content
|
||||
to a search index.
|
||||
student_view_data: data needed to render the XBlock on mobile or in
|
||||
custom frontends.
|
||||
children: list of usage keys of the XBlock's children
|
||||
editable_children: children in the same bundle, as opposed to linked
|
||||
children in other bundles.
|
||||
"""
|
||||
return {
|
||||
data = {
|
||||
"block_id": six.text_type(block.scope_ids.usage_id),
|
||||
"block_type": block.scope_ids.block_type,
|
||||
"display_name": get_block_display_name(block),
|
||||
}
|
||||
|
||||
if "index_dictionary" in includes:
|
||||
data["index_dictionary"] = block.index_dictionary()
|
||||
|
||||
if "student_view_data" in includes:
|
||||
data["student_view_data"] = block.student_view_data() if hasattr(block, 'student_view_data') else None
|
||||
|
||||
if "children" in includes:
|
||||
data["children"] = block.children if hasattr(block, 'children') else [] # List of usage keys of children
|
||||
|
||||
if "editable_children" in includes:
|
||||
# "Editable children" means children in the same bundle, as opposed to linked children in other bundles.
|
||||
data["editable_children"] = []
|
||||
child_includes = block.runtime.child_includes_of(block)
|
||||
for idx, include in enumerate(child_includes):
|
||||
if include.link_id is None:
|
||||
data["editable_children"].append(block.children[idx])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def resolve_definition(block_or_key):
|
||||
"""
|
||||
|
||||
@@ -34,10 +34,18 @@ User = get_user_model()
|
||||
def block_metadata(request, usage_key_str):
|
||||
"""
|
||||
Get metadata about the specified block.
|
||||
|
||||
Accepts an "include" query parameter which must be a comma separated list of keys to include. Valid keys are
|
||||
"index_dictionary" and "student_view_data".
|
||||
"""
|
||||
usage_key = UsageKey.from_string(usage_key_str)
|
||||
block = load_block(usage_key, request.user)
|
||||
metadata_dict = get_block_metadata(block)
|
||||
includes = request.GET.get("include", "").split(",")
|
||||
metadata_dict = get_block_metadata(block, includes=includes)
|
||||
if 'children' in metadata_dict:
|
||||
metadata_dict['children'] = [str(key) for key in metadata_dict['children']]
|
||||
if 'editable_children' in metadata_dict:
|
||||
metadata_dict['editable_children'] = [str(key) for key in metadata_dict['editable_children']]
|
||||
return Response(metadata_dict)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user