Adds Custom Extra Fields to Course Blocks API through LMS Settings
This commit is contained in:
@@ -15,6 +15,7 @@ from .transformers.block_counts import BlockCountsTransformer
|
||||
from .transformers.milestones import MilestonesAndSpecialExamsTransformer
|
||||
from .transformers.navigation import BlockNavigationTransformer
|
||||
from .transformers.student_view import StudentViewTransformer
|
||||
from .transformers.extra_fields import ExtraFieldsTransformer
|
||||
|
||||
|
||||
class SupportedFieldType(object):
|
||||
@@ -80,7 +81,9 @@ SUPPORTED_FIELDS = [
|
||||
BlockCompletionTransformer.COMPLETION,
|
||||
BlockCompletionTransformer,
|
||||
'completion'
|
||||
)
|
||||
),
|
||||
|
||||
*[SupportedFieldType(field_name) for field_name in ExtraFieldsTransformer.get_requested_extra_fields()],
|
||||
]
|
||||
|
||||
# This lists the names of all fields that are allowed
|
||||
|
||||
@@ -222,6 +222,65 @@ class TestBlocksView(SharedModuleStoreTestCase):
|
||||
block_data['type'] in self.BLOCK_TYPES_WITH_STUDENT_VIEW_DATA
|
||||
)
|
||||
|
||||
def test_extra_field_when_requested(self):
|
||||
"""
|
||||
Tests if all requested extra fields appear in output
|
||||
|
||||
Requests the fields specified under "COURSE_BLOCKS_API_EXTRA_FIELDS"
|
||||
in the Test Django settings
|
||||
|
||||
Test setting "COURSE_BLOCKS_API_EXTRA_FIELDS" contains:
|
||||
- other_course_settings
|
||||
- course_visibility
|
||||
"""
|
||||
response = self.verify_response(params={
|
||||
'all_blocks': True,
|
||||
'requested_fields': ['other_course_settings', 'course_visibility'],
|
||||
})
|
||||
self.verify_response_block_dict(response)
|
||||
for block_data in six.itervalues(response.data['blocks']):
|
||||
self.assert_in_iff(
|
||||
'other_course_settings',
|
||||
block_data,
|
||||
block_data['type'] == 'course'
|
||||
)
|
||||
|
||||
self.assert_in_iff(
|
||||
'course_visibility',
|
||||
block_data,
|
||||
block_data['type'] == 'course'
|
||||
)
|
||||
|
||||
def test_extra_field_when_not_requested(self):
|
||||
"""
|
||||
Tests if fields that weren't requested would appear in output
|
||||
|
||||
Requests some of the fields specified under
|
||||
"COURSE_BLOCKS_API_EXTRA_FIELDS" in the Test Django settings
|
||||
The other extra fields specified in Test Django settings weren't
|
||||
requested to see if they would show up in the output or not
|
||||
|
||||
Test setting "COURSE_BLOCKS_API_EXTRA_FIELDS" contains:
|
||||
- other_course_settings
|
||||
- course_visibility
|
||||
"""
|
||||
response = self.verify_response(params={
|
||||
'all_blocks': True,
|
||||
'requested_fields': ['course_visibility'],
|
||||
})
|
||||
self.verify_response_block_dict(response)
|
||||
for block_data in six.itervalues(response.data['blocks']):
|
||||
self.assertNotIn(
|
||||
'other_course_settings',
|
||||
block_data
|
||||
)
|
||||
|
||||
self.assert_in_iff(
|
||||
'course_visibility',
|
||||
block_data,
|
||||
block_data['type'] == 'course'
|
||||
)
|
||||
|
||||
def test_navigation_param(self):
|
||||
response = self.verify_response(params={'nav_depth': 10})
|
||||
self.verify_response_block_dict(response)
|
||||
|
||||
@@ -10,6 +10,7 @@ from .block_depth import BlockDepthTransformer
|
||||
from .navigation import BlockNavigationTransformer
|
||||
from .student_view import StudentViewTransformer
|
||||
from .video_urls import VideoBlockURLTransformer
|
||||
from .extra_fields import ExtraFieldsTransformer
|
||||
|
||||
|
||||
class BlocksAPITransformer(BlockStructureTransformer):
|
||||
@@ -22,6 +23,7 @@ class BlocksAPITransformer(BlockStructureTransformer):
|
||||
BlockCountsTransformer
|
||||
BlockDepthTransformer
|
||||
BlockNavigationTransformer
|
||||
ExtraFieldsTransformer
|
||||
|
||||
Note:
|
||||
* BlockDepthTransformer must be executed before BlockNavigationTransformer.
|
||||
@@ -57,6 +59,7 @@ class BlocksAPITransformer(BlockStructureTransformer):
|
||||
BlockCountsTransformer.collect(block_structure)
|
||||
BlockDepthTransformer.collect(block_structure)
|
||||
BlockNavigationTransformer.collect(block_structure)
|
||||
ExtraFieldsTransformer.collect(block_structure)
|
||||
|
||||
# TODO support olx_data by calling export_to_xml(?)
|
||||
|
||||
@@ -69,3 +72,4 @@ class BlocksAPITransformer(BlockStructureTransformer):
|
||||
BlockDepthTransformer(self.depth).transform(usage_info, block_structure)
|
||||
BlockNavigationTransformer(self.nav_depth).transform(usage_info, block_structure)
|
||||
VideoBlockURLTransformer().transform(usage_info, block_structure)
|
||||
ExtraFieldsTransformer().transform(usage_info, block_structure)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Extra Fields Transformer
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
from openedx.core.djangoapps.content.block_structure.transformer import BlockStructureTransformer
|
||||
|
||||
|
||||
class ExtraFieldsTransformer(BlockStructureTransformer):
|
||||
"""
|
||||
A configurable transformer that adds additional XBlock fields to the course blocks API.
|
||||
|
||||
Extra fields must be specified using the "COURSE_BLOCKS_API_EXTRA_FIELDS"
|
||||
LMS Django settings variable. Open edX instances can use this to make
|
||||
additional XBlock fields available via the course blocks API, that aren't
|
||||
otherwise included by default.
|
||||
"""
|
||||
WRITE_VERSION = 1
|
||||
READ_VERSION = 1
|
||||
|
||||
@classmethod
|
||||
def name(cls):
|
||||
return "blocks_api:extra_fields"
|
||||
|
||||
@classmethod
|
||||
def collect(cls, block_structure):
|
||||
"""
|
||||
Collects any information that's necessary to execute this transformer's
|
||||
transform method.
|
||||
"""
|
||||
requested_field_names = cls.get_requested_extra_fields()
|
||||
|
||||
block_structure.request_xblock_fields('category', *requested_field_names)
|
||||
|
||||
@classmethod
|
||||
def get_requested_extra_fields(cls):
|
||||
"""
|
||||
Returns the names of the requested extra fields
|
||||
"""
|
||||
try:
|
||||
return [field_name for block_type, field_name in settings.COURSE_BLOCKS_API_EXTRA_FIELDS]
|
||||
except AttributeError:
|
||||
return []
|
||||
|
||||
def transform(self, usage_info, block_structure):
|
||||
"""
|
||||
Mutates block_structure based on the given usage_info.
|
||||
"""
|
||||
if len(self.get_requested_extra_fields()) == 0:
|
||||
return
|
||||
|
||||
for block_key in block_structure.topological_traversal():
|
||||
for requested_block_type, requested_field_name in settings.COURSE_BLOCKS_API_EXTRA_FIELDS:
|
||||
block_type = block_structure.get_xblock_field(block_key, 'category')
|
||||
if (requested_block_type == '*' or
|
||||
block_type in requested_block_type.split(',')):
|
||||
requested_field_data = block_structure.get_xblock_field(
|
||||
block_key,
|
||||
requested_field_name,
|
||||
None
|
||||
)
|
||||
|
||||
if requested_field_data is not None:
|
||||
block_structure.set_transformer_block_field(
|
||||
block_key,
|
||||
self,
|
||||
requested_field_name,
|
||||
requested_field_data
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
Tests for ExtraFieldsTransformer.
|
||||
"""
|
||||
from django.test import override_settings
|
||||
|
||||
# pylint: disable=protected-access
|
||||
from openedx.core.djangoapps.content.block_structure.factory import BlockStructureFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import SampleCourseFactory
|
||||
|
||||
from ..extra_fields import ExtraFieldsTransformer
|
||||
|
||||
|
||||
@override_settings(COURSE_BLOCKS_API_EXTRA_FIELDS=[('course', 'other_course_settings')])
|
||||
class TestExtraFieldsTransformer(ModuleStoreTestCase):
|
||||
"""
|
||||
Test proper behavior for ExtraFieldsTransformer
|
||||
"""
|
||||
shard = 4
|
||||
|
||||
OTHER_COURSE_SETTINGS_DEFAULT = {
|
||||
'test key': 'test value',
|
||||
'jackson 5': [
|
||||
['a', 'b', 'c'],
|
||||
'it\'s easy as',
|
||||
[1, 2, 3],
|
||||
'as simple as',
|
||||
['do', 're', 'mi']
|
||||
]
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestExtraFieldsTransformer, self).setUp()
|
||||
|
||||
self.course = SampleCourseFactory.create(
|
||||
other_course_settings=self.OTHER_COURSE_SETTINGS_DEFAULT
|
||||
)
|
||||
self.course_key = self.course.id
|
||||
|
||||
self.course_usage_key = self.store.make_course_usage_key(self.course_key)
|
||||
self.block_structure = BlockStructureFactory.create_from_modulestore(self.course_usage_key, self.store)
|
||||
|
||||
def test_transform(self):
|
||||
# collect phase
|
||||
ExtraFieldsTransformer.collect(self.block_structure)
|
||||
self.block_structure._collect_requested_xblock_fields()
|
||||
|
||||
# transform phase
|
||||
ExtraFieldsTransformer().transform(
|
||||
usage_info=None,
|
||||
block_structure=self.block_structure,
|
||||
)
|
||||
|
||||
block_data = self.block_structure.get_transformer_block_data(
|
||||
self.course_usage_key, ExtraFieldsTransformer,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
block_data.other_course_settings,
|
||||
self.OTHER_COURSE_SETTINGS_DEFAULT
|
||||
)
|
||||
@@ -187,6 +187,10 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView):
|
||||
|
||||
* show_correctness: Whether to show scores/correctness to learners for the current sequence or problem.
|
||||
Returned only if "show_correctness" is included in the "requested_fields" parameter.
|
||||
|
||||
* Additional XBlock fields can be included in the response if they are
|
||||
configured via the COURSE_BLOCKS_API_EXTRA_FIELDS Django setting and
|
||||
requested via the "requested_fields" parameter.
|
||||
"""
|
||||
|
||||
def list(self, request, usage_key_string, hide_access_denials=False): # pylint: disable=arguments-differ
|
||||
|
||||
@@ -488,6 +488,11 @@ FEATURES = {
|
||||
'ENABLE_ORA_USERNAMES_ON_DATA_EXPORT': False,
|
||||
}
|
||||
|
||||
# Specifies extra XBlock fields that should available when requested via the Course Blocks API
|
||||
# Should be a list of tuples of (block_type, field_name), where block_type can also be "*" for all block types.
|
||||
# e.g. COURSE_BLOCKS_API_EXTRA_FIELDS = [ ('course', 'other_course_settings'), ("problem", "weight") ]
|
||||
COURSE_BLOCKS_API_EXTRA_FIELDS = []
|
||||
|
||||
# Settings for the course reviews tool template and identification key, set either to None to disable course reviews
|
||||
COURSE_REVIEWS_TOOL_PROVIDER_FRAGMENT_NAME = 'coursetalk-reviews-fragment.html'
|
||||
COURSE_REVIEWS_TOOL_PROVIDER_PLATFORM_KEY = 'edx'
|
||||
|
||||
@@ -486,6 +486,11 @@ FEATURES['ORGANIZATIONS_APP'] = True
|
||||
# Financial assistance page
|
||||
FEATURES['ENABLE_FINANCIAL_ASSISTANCE_FORM'] = True
|
||||
|
||||
COURSE_BLOCKS_API_EXTRA_FIELDS = [
|
||||
('course', 'course_visibility'),
|
||||
('course', 'other_course_settings'),
|
||||
]
|
||||
|
||||
COURSE_CATALOG_URL_ROOT = 'https://catalog.example.com'
|
||||
COURSE_CATALOG_API_URL = '{}/api/v1'.format(COURSE_CATALOG_URL_ROOT)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user