Commit 64c4ae4a by Tomasz Gargas

Add block completion value as optional field in course_blocks.api.

Signed-off-by: Tomasz Gargas <tomasz@opencraft.com>
parent 3cc25a30
...@@ -8,6 +8,7 @@ from openedx.core.djangoapps.content.block_structure.transformers import BlockSt ...@@ -8,6 +8,7 @@ from openedx.core.djangoapps.content.block_structure.transformers import BlockSt
from .serializers import BlockDictSerializer, BlockSerializer from .serializers import BlockDictSerializer, BlockSerializer
from .transformers.blocks_api import BlocksAPITransformer from .transformers.blocks_api import BlocksAPITransformer
from .transformers.block_completion import BlockCompletionTransformer
from .transformers.milestones import MilestonesAndSpecialExamsTransformer from .transformers.milestones import MilestonesAndSpecialExamsTransformer
...@@ -51,9 +52,11 @@ def get_blocks( ...@@ -51,9 +52,11 @@ def get_blocks(
""" """
# create ordered list of transformers, adding BlocksAPITransformer at end. # create ordered list of transformers, adding BlocksAPITransformer at end.
transformers = BlockStructureTransformers() transformers = BlockStructureTransformers()
include_special_exams = False if requested_fields is None:
if requested_fields is not None and 'special_exam_info' in requested_fields: requested_fields = []
include_special_exams = True include_completion = 'completion' in requested_fields
include_special_exams = 'special_exam_info' in requested_fields
if user is not None: if user is not None:
transformers += COURSE_BLOCK_ACCESS_TRANSFORMERS transformers += COURSE_BLOCK_ACCESS_TRANSFORMERS
transformers += [MilestonesAndSpecialExamsTransformer(include_special_exams), HiddenContentTransformer()] transformers += [MilestonesAndSpecialExamsTransformer(include_special_exams), HiddenContentTransformer()]
...@@ -66,6 +69,9 @@ def get_blocks( ...@@ -66,6 +69,9 @@ def get_blocks(
) )
] ]
if include_completion:
transformers += [BlockCompletionTransformer()]
# transform # transform
blocks = get_course_blocks(user, usage_key, transformers) blocks = get_course_blocks(user, usage_key, transformers)
......
...@@ -4,6 +4,7 @@ Course API Block Transformers ...@@ -4,6 +4,7 @@ Course API Block Transformers
from lms.djangoapps.course_blocks.transformers.visibility import VisibilityTransformer from lms.djangoapps.course_blocks.transformers.visibility import VisibilityTransformer
from .student_view import StudentViewTransformer from .student_view import StudentViewTransformer
from .block_completion import BlockCompletionTransformer
from .block_counts import BlockCountsTransformer from .block_counts import BlockCountsTransformer
from .navigation import BlockNavigationTransformer from .navigation import BlockNavigationTransformer
from .milestones import MilestonesAndSpecialExamsTransformer from .milestones import MilestonesAndSpecialExamsTransformer
...@@ -63,5 +64,10 @@ SUPPORTED_FIELDS = [ ...@@ -63,5 +64,10 @@ SUPPORTED_FIELDS = [
'merged_visible_to_staff_only', 'merged_visible_to_staff_only',
VisibilityTransformer, VisibilityTransformer,
requested_field_name='visible_to_staff_only', requested_field_name='visible_to_staff_only',
),
SupportedFieldType(
BlockCompletionTransformer.COMPLETION,
BlockCompletionTransformer,
'completion'
) )
] ]
"""
Block Completion Transformer
"""
from xblock.completable import XBlockCompletionMode as CompletionMode
from lms.djangoapps.completion.models import BlockCompletion
from openedx.core.djangoapps.content.block_structure.transformer import BlockStructureTransformer
class BlockCompletionTransformer(BlockStructureTransformer):
"""
Keep track of the completion of each block within the block structure.
"""
READ_VERSION = 0
WRITE_VERSION = 1
COMPLETION = 'completion'
def __init__(self):
super(BlockCompletionTransformer, self).__init__()
@classmethod
def name(cls):
return "blocks_api:completion"
@classmethod
def get_block_completion(cls, block_structure, block_key):
"""
Return the precalculated completion of a block within the block_structure:
Arguments:
block_structure: a BlockStructure instance
block_key: the key of the block whose completion we want to know
Returns:
block_completion: float or None
"""
return block_structure.get_transformer_block_field(
block_key,
cls,
cls.COMPLETION,
)
@classmethod
def collect(cls, block_structure):
block_structure.request_xblock_fields('completion_mode')
def transform(self, usage_info, block_structure):
"""
Mutates block_structure adding extra field which contains block's completion.
"""
def _is_block_an_aggregator_or_excluded(block_key):
"""
Checks whether block's completion method
is of `AGGREGATOR` or `EXCLUDED` type.
"""
completion_mode = block_structure.get_xblock_field(
block_key, 'completion_mode'
)
return completion_mode in (CompletionMode.AGGREGATOR, CompletionMode.EXCLUDED)
completions = BlockCompletion.objects.filter(
user=usage_info.user,
course_key=usage_info.course_key,
).values_list(
'block_key',
'completion',
)
completions_dict = {
block.map_into_course(usage_info.course_key): completion
for block, completion in completions
}
for block_key in block_structure.topological_traversal():
if _is_block_an_aggregator_or_excluded(block_key):
completion_value = None
elif block_key in completions_dict:
completion_value = completions_dict[block_key]
else:
completion_value = 0.0
block_structure.set_transformer_block_field(
block_key, self, self.COMPLETION, completion_value
)
"""
Tests for BlockCompletionTransformer.
"""
from xblock.core import XBlock
from xblock.completable import CompletableXBlockMixin, XBlockCompletionMode
from lms.djangoapps.completion.models import BlockCompletion
from lms.djangoapps.completion.test_utils import CompletionWaffleTestMixin
from lms.djangoapps.course_api.blocks.transformers.block_completion import BlockCompletionTransformer
from lms.djangoapps.course_blocks.transformers.tests.helpers import ModuleStoreTestCase, TransformerRegistryTestMixin
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from ...api import get_course_blocks
class StubAggregatorXBlock(XBlock):
"""
XBlock to test behaviour of BlockCompletionTransformer
when transforming aggregator XBlock.
"""
completion_mode = XBlockCompletionMode.AGGREGATOR
class StubExcludedXBlock(XBlock):
"""
XBlock to test behaviour of BlockCompletionTransformer
when transforming excluded XBlock.
"""
completion_mode = XBlockCompletionMode.EXCLUDED
class StubCompletableXBlock(XBlock, CompletableXBlockMixin):
"""
XBlock to test behaviour of BlockCompletionTransformer
when transforming completable XBlock.
"""
pass
class BlockCompletionTransformerTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase, CompletionWaffleTestMixin):
"""
Tests behaviour of BlockCompletionTransformer
"""
TRANSFORMER_CLASS_TO_TEST = BlockCompletionTransformer
COMPLETION_TEST_VALUE = 0.4
def setUp(self):
super(BlockCompletionTransformerTestCase, self).setUp()
self.user = UserFactory.create(password='test')
self.override_waffle_switch(True)
@XBlock.register_temp_plugin(StubAggregatorXBlock, identifier='aggregator')
def test_transform_gives_none_for_aggregator(self):
course = CourseFactory.create()
block = ItemFactory.create(category='aggregator', parent=course)
block_structure = get_course_blocks(
self.user, course.location, self.transformers
)
self._assert_block_has_proper_completion_value(
block_structure, block.location, None
)
@XBlock.register_temp_plugin(StubExcludedXBlock, identifier='excluded')
def test_transform_gives_none_for_excluded(self):
course = CourseFactory.create()
block = ItemFactory.create(category='excluded', parent=course)
block_structure = get_course_blocks(
self.user, course.location, self.transformers
)
self._assert_block_has_proper_completion_value(
block_structure, block.location, None
)
@XBlock.register_temp_plugin(StubCompletableXBlock, identifier='comp')
def test_transform_gives_value_for_completable(self):
course = CourseFactory.create()
block = ItemFactory.create(category='comp', parent=course)
BlockCompletion.objects.submit_completion(
user=self.user,
course_key=block.location.course_key,
block_key=block.location,
completion=self.COMPLETION_TEST_VALUE,
)
block_structure = get_course_blocks(
self.user, course.location, self.transformers
)
self._assert_block_has_proper_completion_value(
block_structure, block.location, self.COMPLETION_TEST_VALUE
)
def test_transform_gives_zero_for_ordinary_block(self):
course = CourseFactory.create()
block = ItemFactory.create(category='html', parent=course)
block_structure = get_course_blocks(
self.user, course.location, self.transformers
)
self._assert_block_has_proper_completion_value(
block_structure, block.location, 0.0
)
def _assert_block_has_proper_completion_value(
self, block_structure, block_key, expected_value
):
"""
Checks whether block's completion has expected value.
"""
block_data = block_structure.get_transformer_block_data(
block_key, self.TRANSFORMER_CLASS_TO_TEST
)
completion_value = block_data.fields['completion']
self.assertEqual(completion_value, expected_value)
...@@ -128,6 +128,11 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): ...@@ -128,6 +128,11 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView):
the child blocks. Returned only if "children" is included in the the child blocks. Returned only if "children" is included in the
"requested_fields" parameter. "requested_fields" parameter.
* completion: (float or None) The level of completion of the block.
Its value can vary between 0.0 and 1.0 or be equal to None
if block is not completable. Returned only if "completion"
is included in the "requested_fields" parameter.
* block_counts: (dict) For each block type specified in the * block_counts: (dict) For each block type specified in the
block_counts parameter to the endpoint, the aggregate number of block_counts parameter to the endpoint, the aggregate number of
blocks of that type for this block and all of its descendants. blocks of that type for this block and all of its descendants.
......
...@@ -58,6 +58,7 @@ setup( ...@@ -58,6 +58,7 @@ setup(
"course_blocks_api = lms.djangoapps.course_api.blocks.transformers.blocks_api:BlocksAPITransformer", "course_blocks_api = lms.djangoapps.course_api.blocks.transformers.blocks_api:BlocksAPITransformer",
"milestones = lms.djangoapps.course_api.blocks.transformers.milestones:MilestonesAndSpecialExamsTransformer", "milestones = lms.djangoapps.course_api.blocks.transformers.milestones:MilestonesAndSpecialExamsTransformer",
"grades = lms.djangoapps.grades.transformer:GradesTransformer", "grades = lms.djangoapps.grades.transformer:GradesTransformer",
"completion = lms.djangoapps.course_api.blocks.transformers.block_completion:BlockCompletionTransformer"
], ],
"openedx.ace.policy": [ "openedx.ace.policy": [
"bulk_email_optout = lms.djangoapps.bulk_email.policies:CourseEmailOptout" "bulk_email_optout = lms.djangoapps.bulk_email.policies:CourseEmailOptout"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment