def get_completable_children(self, node): """ Verticals will sometimes have children that also have children (some examples are content libraries, split tests, conditionals, etc.). This function will recurse through the children to get to the bottom leaf and return only those children. This function heavily utilizes the XBlockCompletionMode class to determine if it should recurse or not. It will only recurse if the node is an AGGREGATOR Note: The nodes passed in should have already taken the user into account so the proper blocks are shown for this user. """ if XBlockCompletionMode.get_mode( node) == XBlockCompletionMode.EXCLUDED: return [] user_children = [] if XBlockCompletionMode.get_mode( node) == XBlockCompletionMode.AGGREGATOR: node_children = ((hasattr(node, 'get_child_descriptors') and node.get_child_descriptors()) or (hasattr(node, 'get_children') and node.get_children())) for child in node_children: user_children.extend(self.get_completable_children(child)) if XBlockCompletionMode.get_mode( node) == XBlockCompletionMode.COMPLETABLE: user_children = [node] return user_children
def test_is_aggregator(self): """ The unit XBlock is designed to hold other XBlocks, so check that its completion status is defined as the aggregation of its child blocks. """ self.assertEqual(XBlockCompletionMode.get_mode(UnitBlock), XBlockCompletionMode.AGGREGATOR)
def scorable_block_completion(sender, **kwargs): # pylint: disable=unused-argument """ When a problem is scored, submit a new BlockCompletion for that block. """ if not waffle.waffle().is_enabled(waffle.ENABLE_COMPLETION_TRACKING): return course_key = CourseKey.from_string(kwargs['course_id']) block_key = UsageKey.from_string(kwargs['usage_id']) block_cls = XBlock.load_class(block_key.block_type) if XBlockCompletionMode.get_mode( block_cls) != XBlockCompletionMode.COMPLETABLE: return if getattr(block_cls, 'has_custom_completion', False): return user = User.objects.get(id=kwargs['user_id']) if kwargs.get('score_deleted'): completion = 0.0 else: completion = 1.0 if not kwargs.get('grader_response'): BlockCompletion.objects.submit_completion( user=user, course_key=course_key, block_key=block_key, completion=completion, )
def test_all_blocks_excluded_from_completion(self, blockclass): xblock = XBlock.load_class(blockclass) self.assertEqual( XBlockCompletionMode.get_mode(xblock), XBlockCompletionMode.EXCLUDED, "Block {!r} did not have completion mode 'excluded'".format(xblock), )
def publish_completion(self): """ Mark scorm xbloxk as completed if user has completed the scorm course unit. it will work along with the edX completion tool: https://github.com/edx/completion """ if not completion_waffle.waffle().is_enabled(completion_waffle.ENABLE_COMPLETION_TRACKING): return if XBlockCompletionMode.get_mode(self) != XBlockCompletionMode.COMPLETABLE: return completion_value = 0.0 if not self.has_score: # component does not have any score if self.get_completion_status() == "completed": completion_value = 1.0 else: if self.get_completion_status() in ["passed", "failed"]: completion_value = 1.0 data = { "completion": completion_value } self.runtime.publish(self, "completion", data)
def test_all_blocks_excluded_from_completion(self, blockclass): xblock = XBlock.load_class(blockclass) self.assertEqual( XBlockCompletionMode.get_mode(xblock), XBlockCompletionMode.EXCLUDED, "Block {!r} did not have completion mode 'excluded'".format( xblock), )
def can_mark_block_complete_on_view(self, block): """ Returns True if the xblock can be marked complete on view. This is true of any non-customized, non-scorable, completable block. """ return (XBlockCompletionMode.get_mode(block) == XBlockCompletionMode.COMPLETABLE and not getattr(block, 'has_custom_completion', False) and not getattr(block, 'has_score', False) )
def test_completion_mode_property(self): """ Test `completion_mode` property is set by mixin. """ block = self._make_block() self.assertEqual(XBlockCompletionMode.get_mode(block), XBlockCompletionMode.COMPLETABLE) self.assertEqual(getattr(block, 'completion_mode'), XBlockCompletionMode.COMPLETABLE)
def _submit_completions(block, user): """ Recursively submits the children for completion to the Completion Service """ mode = XBlockCompletionMode.get_mode(block) if mode == XBlockCompletionMode.COMPLETABLE: block.runtime.publish(block, 'completion', {'completion': 1.0, 'user_id': user.id}) elif mode == XBlockCompletionMode.AGGREGATOR: # I know this looks weird, but at the time of writing at least, there isn't a good # single way to get the children assigned for a partcular user. Some blocks define the # child descriptors method, but others don't and with blocks like Randomized Content # (Library Content), the get_children method returns all children and not just assigned # children. So this is our way around situations like that. See also Split Test Module # for another use case where user state has to be taken into account via get_child_descriptors block_children = ((hasattr(block, 'get_child_descriptors') and block.get_child_descriptors()) or (hasattr(block, 'get_children') and block.get_children()) or []) for child in block_children: _submit_completions(child, user)
def update_for_block(self, block, affected_aggregators, force=False): """ Recursive function to perform updates for a given block. Dispatches to an appropriate method given the block's completion_mode. """ try: mode = XBlockCompletionMode.get_mode(XBlock.load_class(block.block_type)) except PluginMissingError: # Do not count blocks that aren't registered mode = XBlockCompletionMode.EXCLUDED if mode == XBlockCompletionMode.EXCLUDED: return self.update_for_excluded() elif mode == XBlockCompletionMode.COMPLETABLE: return self.update_for_completable(block) elif mode == XBlockCompletionMode.AGGREGATOR: return self.update_for_aggregator(block, affected_aggregators, force) else: raise ValueError("Invalid completion mode {}".format(mode))
def scorable_block_completion(sender, **kwargs): # pylint: disable=unused-argument """ When a problem is scored, submit a new BlockCompletion for that block. """ if not waffle.waffle().is_enabled(waffle.ENABLE_COMPLETION_TRACKING): return try: block_key = UsageKey.from_string(kwargs['usage_id']) except InvalidKeyError: log.exception("Unable to parse XBlock usage_id for completion: %s", block_key) return if block_key.context_key.is_course and block_key.context_key.run is None: # In the case of old mongo courses, the context_key cannot be derived # from the block key alone since it will be missing run info: course_key_with_run = LearningContextKey.from_string( kwargs['course_id']) block_key = block_key.replace(course_key=course_key_with_run) block_cls = XBlock.load_class(block_key.block_type) if XBlockCompletionMode.get_mode( block_cls) != XBlockCompletionMode.COMPLETABLE: return if getattr(block_cls, 'has_custom_completion', False): return user = User.objects.get(id=kwargs['user_id']) if kwargs.get('score_deleted'): completion = 0.0 else: completion = 1.0 if not kwargs.get('grader_response'): BlockCompletion.objects.submit_completion( user=user, block_key=block_key, completion=completion, )
def test_is_aggregator(self): self.assertEqual(XBlockCompletionMode.get_mode(UnitXBlock), XBlockCompletionMode.AGGREGATOR)
def test_unknown_mode(self): block = self.blocklike('somenewmode') self.assertEqual(XBlockCompletionMode.get_mode(block), 'somenewmode')
def test_no_mode(self): self.assertEqual( XBlockCompletionMode.get_mode(object()), XBlockCompletionMode.COMPLETABLE, )
def test_explicit_mode(self, mode): block = self.blocklike(mode) self.assertEqual(XBlockCompletionMode.get_mode(block), mode)
def test_is_aggregator(self): self.assertEqual( XBlockCompletionMode.get_mode(AnnotatedVideoBlock), XBlockCompletionMode.AGGREGATOR, )