Skip to content
Snippets Groups Projects
Unverified Commit 617fe431 authored by David Ormsbee's avatar David Ormsbee Committed by GitHub
Browse files

Merge pull request #23246 from open-craft/samuel/lx-744-more-metadata

[SE-2264] [LX-744] Return more metadata in xblock api
parents 0f5c474f 9f6b3a87
No related branches found
Tags release-2020-12-18-12.59
No related merge requests found
......@@ -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)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment