Exemplo n.º 1
0
    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
Exemplo n.º 2
0
 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)
Exemplo n.º 3
0
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),
     )
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
 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),
     )
Exemplo n.º 7
0
 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)
             )
Exemplo n.º 8
0
 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)
Exemplo n.º 9
0
 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)
Exemplo n.º 10
0
    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))
Exemplo n.º 11
0
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,
        )
Exemplo n.º 12
0
 def test_is_aggregator(self):
     self.assertEqual(XBlockCompletionMode.get_mode(UnitXBlock),
                      XBlockCompletionMode.AGGREGATOR)
Exemplo n.º 13
0
 def test_unknown_mode(self):
     block = self.blocklike('somenewmode')
     self.assertEqual(XBlockCompletionMode.get_mode(block), 'somenewmode')
Exemplo n.º 14
0
 def test_no_mode(self):
     self.assertEqual(
         XBlockCompletionMode.get_mode(object()),
         XBlockCompletionMode.COMPLETABLE,
     )
Exemplo n.º 15
0
 def test_explicit_mode(self, mode):
     block = self.blocklike(mode)
     self.assertEqual(XBlockCompletionMode.get_mode(block), mode)
Exemplo n.º 16
0
 def test_is_aggregator(self):
     self.assertEqual(
         XBlockCompletionMode.get_mode(AnnotatedVideoBlock),
         XBlockCompletionMode.AGGREGATOR,
     )