Adds Custom Extra Fields to Course Blocks API through LMS Settings

This commit is contained in:
Nizar Mahmoud
2020-08-06 20:55:08 +03:00
parent 30750d6c52
commit c0b6260c3e
8 changed files with 211 additions and 1 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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

View File

@@ -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'

View File

@@ -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)