def __init__(self, **kwargs): request_cache_dict = DEFAULT_REQUEST_CACHE.data store = modulestore() services = kwargs.setdefault('services', {}) user = kwargs.get('user') if user and user.is_authenticated: services['completion'] = CompletionService( user=user, context_key=kwargs.get('course_id')) services['fs'] = xblock.reference.plugins.FSService() services['i18n'] = ModuleI18nService services['library_tools'] = LibraryToolsService( store, user_id=user.id if user else None) services['partitions'] = PartitionService( course_id=kwargs.get('course_id'), cache=request_cache_dict) services['settings'] = SettingsService() services['user_tags'] = UserTagsService(self) if badges_enabled(): services['badging'] = BadgingService( course_id=kwargs.get('course_id'), modulestore=store) self.request_token = kwargs.pop('request_token', None) services['teams'] = TeamsService() services['teams_configuration'] = TeamsConfigurationService() services['call_to_action'] = CallToActionService() super(LmsModuleSystem, self).__init__(**kwargs)
def service(self, block, service_name): """ Return a service, or None. Services are objects implementing arbitrary other interfaces. """ # TODO: Do these declarations actually help with anything? Maybe this check should # be removed from here and from XBlock.runtime declaration = block.service_declaration(service_name) if declaration is None: raise NoSuchServiceError("Service {!r} was not requested.".format(service_name)) # Most common service is field-data so check that first: if service_name == "field-data": if block.scope_ids not in self.block_field_datas: try: self.block_field_datas[block.scope_ids] = self._init_field_data_for_block(block) except: # Don't try again pointlessly every time another field is accessed self.block_field_datas[block.scope_ids] = None raise return self.block_field_datas[block.scope_ids] elif service_name == "completion": context_key = block.scope_ids.usage_id.context_key return CompletionService(user=self.user, context_key=context_key) # Check if the XBlockRuntimeSystem wants to handle this: service = self.system.get_service(block, service_name) # Otherwise, fall back to the base implementation which loads services # defined in the constructor: if service is None: service = super(XBlockRuntime, self).service(block, service_name) return service
def setUp(self): super(CompletionServiceTestCase, self).setUp() self.override_waffle_switch(True) self.user = UserFactory.create() self.other_user = UserFactory.create() self.course_key = self.course.id self.other_course_key = CourseKey.from_string("course-v1:ReedX+Hum110+1904") self.block_keys = [problem.location for problem in self.problems] self.completion_service = CompletionService(self.user, self.course_key) # Proper completions for the given runtime for idx, block_key in enumerate(self.block_keys[0:3]): BlockCompletion.objects.submit_completion( user=self.user, course_key=self.course_key, block_key=block_key, completion=1.0 - (0.2 * idx), ) # Wrong user for idx, block_key in enumerate(self.block_keys[2:]): BlockCompletion.objects.submit_completion( user=self.other_user, course_key=self.course_key, block_key=block_key, completion=0.9 - (0.2 * idx), ) # Wrong course BlockCompletion.objects.submit_completion( user=self.user, course_key=self.other_course_key, block_key=self.block_keys[4], completion=0.75, )
def setUp(self): super(CompletionServiceTestCase, self).setUp() self.override_waffle_switch(True) self.user = UserFactory.create() self.other_user = UserFactory.create() self.course_key = self.course.id self.other_course_key = CourseKey.from_string("course-v1:ReedX+Hum110+1904") self.block_keys = [problem.location for problem in self.problems] self.completion_service = CompletionService(self.user, self.course_key) # Proper completions for the given runtime BlockCompletion.objects.submit_completion( user=self.user, course_key=self.course_key, block_key=self.html.location, completion=1.0, ) for idx, block_key in enumerate(self.block_keys[0:3]): BlockCompletion.objects.submit_completion( user=self.user, course_key=self.course_key, block_key=block_key, completion=1.0 - (0.2 * idx), ) # Wrong user for idx, block_key in enumerate(self.block_keys[2:]): BlockCompletion.objects.submit_completion( user=self.other_user, course_key=self.course_key, block_key=block_key, completion=0.9 - (0.2 * idx), ) # Wrong course BlockCompletion.objects.submit_completion( user=self.user, course_key=self.other_course_key, block_key=self.block_keys[4], completion=0.75, )
def service(self, block, service_name): """ Return a service, or None. Services are objects implementing arbitrary other interfaces. """ # TODO: Do these declarations actually help with anything? Maybe this check should # be removed from here and from XBlock.runtime declaration = block.service_declaration(service_name) if declaration is None: raise NoSuchServiceError( f"Service {service_name!r} was not requested.") # Most common service is field-data so check that first: if service_name == "field-data": if block.scope_ids not in self.block_field_datas: try: self.block_field_datas[ block.scope_ids] = self._init_field_data_for_block( block) except: # Don't try again pointlessly every time another field is accessed self.block_field_datas[block.scope_ids] = None raise return self.block_field_datas[block.scope_ids] elif service_name == "completion": context_key = block.scope_ids.usage_id.context_key return CompletionService(user=self.user, context_key=context_key) elif service_name == "user": return DjangoXBlockUserService( self.user, # The value should be updated to whether the user is staff in the context when Blockstore runtime adds # support for courses. user_is_staff=self.user.is_staff, anonymous_user_id=self.anonymous_student_id, ) elif service_name == "mako": return MakoService() elif service_name == "i18n": return ModuleI18nService(block=block) elif service_name == 'sandbox': context_key = block.scope_ids.usage_id.context_key return SandboxService(contentstore=contentstore, course_id=context_key) elif service_name == 'cache': return CacheService(cache) # Check if the XBlockRuntimeSystem wants to handle this: service = self.system.get_service(block, service_name) # Otherwise, fall back to the base implementation which loads services # defined in the constructor: if service is None: service = super().service(block, service_name) return service
def __init__(self, **kwargs): request_cache_dict = RequestCache.get_request_cache().data store = modulestore() services = kwargs.setdefault('services', {}) services['completion'] = CompletionService( user=kwargs.get('user'), course_key=kwargs.get('course_id')) services['fs'] = xblock.reference.plugins.FSService() services['i18n'] = ModuleI18nService services['library_tools'] = LibraryToolsService(store) services['partitions'] = PartitionService( course_id=kwargs.get('course_id'), cache=request_cache_dict) services['settings'] = SettingsService() services['user_tags'] = UserTagsService(self) if badges_enabled(): services['badging'] = BadgingService( course_id=kwargs.get('course_id'), modulestore=store) self.request_token = kwargs.pop('request_token', None) super(LmsModuleSystem, self).__init__(**kwargs)
class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTestCase): """ Test the data returned by the CompletionService. """ @classmethod def setUpClass(cls): super(CompletionServiceTestCase, cls).setUpClass() cls.course = CourseFactory.create() with cls.store.bulk_operations(cls.course.id): cls.chapter = ItemFactory.create( parent=cls.course, category="chapter", ) cls.sequence = ItemFactory.create( parent=cls.chapter, category='sequential', ) cls.vertical = ItemFactory.create( parent=cls.sequence, category='vertical', ) cls.html = ItemFactory.create( parent=cls.vertical, category='html', ) cls.problem = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.problem2 = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.problem3 = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.problem4 = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.problem5 = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.store.update_item(cls.course, UserFactory().id) cls.problems = [cls.problem, cls.problem2, cls.problem3, cls.problem4, cls.problem5] def setUp(self): super(CompletionServiceTestCase, self).setUp() self.override_waffle_switch(True) self.user = UserFactory.create() self.other_user = UserFactory.create() self.course_key = self.course.id self.other_course_key = CourseKey.from_string("course-v1:ReedX+Hum110+1904") self.block_keys = [problem.location for problem in self.problems] self.completion_service = CompletionService(self.user, self.course_key) # Proper completions for the given runtime BlockCompletion.objects.submit_completion( user=self.user, block_key=self.html.location, completion=1.0, ) for idx, block_key in enumerate(self.block_keys[0:3]): BlockCompletion.objects.submit_completion( user=self.user, block_key=block_key, completion=1.0 - (0.2 * idx), ) # Wrong user for idx, block_key in enumerate(self.block_keys[2:]): BlockCompletion.objects.submit_completion( user=self.other_user, block_key=block_key, completion=0.9 - (0.2 * idx), ) # Wrong course BlockCompletion.objects.submit_completion( user=self.user, block_key=self.other_course_key.make_usage_key('problem', 'other'), completion=0.75, ) def test_completion_service(self): # Only the completions for the user and course specified for the CompletionService # are returned. Values are returned for all keys provided. self.assertEqual( self.completion_service.get_completions(self.block_keys), { self.block_keys[0]: 1.0, self.block_keys[1]: 0.8, self.block_keys[2]: 0.6, self.block_keys[3]: 0.0, self.block_keys[4]: 0.0 }, ) @ddt.data(True, False) def test_enabled_honors_waffle_switch(self, enabled): self.override_waffle_switch(enabled) self.assertEqual(self.completion_service.completion_tracking_enabled(), enabled) def test_vertical_completion(self): self.assertEqual( self.completion_service.vertical_is_complete(self.vertical), False, ) for block_key in self.block_keys: BlockCompletion.objects.submit_completion( user=self.user, block_key=block_key, completion=1.0 ) self.assertEqual( self.completion_service.vertical_is_complete(self.vertical), True, ) def test_vertical_partial_completion(self): block_keys_count = len(self.block_keys) for i in range(block_keys_count - 1): # Mark all the child blocks completed except the last one BlockCompletion.objects.submit_completion( user=self.user, block_key=self.block_keys[i], completion=1.0 ) self.assertEqual( self.completion_service.vertical_is_complete(self.vertical), False, ) def test_can_mark_block_complete_on_view(self): self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.course), False) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.chapter), False) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.sequence), False) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.vertical), False) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.html), True) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.problem), False)
class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTestCase): """ Test the data returned by the CompletionService. """ MODULESTORE = TEST_DATA_SPLIT_MODULESTORE @classmethod def setUpClass(cls): super(CompletionServiceTestCase, cls).setUpClass() cls.course = CourseFactory.create() with cls.store.bulk_operations(cls.course.id): cls.chapter = ItemFactory.create( parent=cls.course, category="chapter", publish_item=False, ) cls.sequence = ItemFactory.create( parent=cls.chapter, category='sequential', publish_item=False, ) cls.vertical = ItemFactory.create( parent=cls.sequence, category='vertical', publish_item=False, ) cls.html = ItemFactory.create( parent=cls.vertical, category='html', publish_item=False, ) cls.problem = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.problem2 = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.problem3 = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.problem4 = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.problem5 = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.store.update_item(cls.course, UserFactory().id) cls.problems = [ cls.problem, cls.problem2, cls.problem3, cls.problem4, cls.problem5 ] def setUp(self): super(CompletionServiceTestCase, self).setUp() self.override_waffle_switch(True) self.user = UserFactory.create() self.other_user = UserFactory.create() self.course_key = self.course.id self.other_course_key = CourseKey.from_string( "course-v1:ReedX+Hum110+1904") self.block_keys = [problem.location for problem in self.problems] self.completion_service = CompletionService(self.user, self.course_key) # Proper completions for the given runtime BlockCompletion.objects.submit_completion( user=self.user, block_key=self.html.location, completion=1.0, ) for idx, block_key in enumerate(self.block_keys[0:3]): BlockCompletion.objects.submit_completion( user=self.user, block_key=block_key, completion=1.0 - (0.2 * idx), ) # Wrong user for idx, block_key in enumerate(self.block_keys[2:]): BlockCompletion.objects.submit_completion( user=self.other_user, block_key=block_key, completion=0.9 - (0.2 * idx), ) # Wrong course BlockCompletion.objects.submit_completion( user=self.user, block_key=self.other_course_key.make_usage_key('problem', 'other'), completion=0.75, ) def _bind_course_module(self, module): """ Bind a module (part of self.course) so we can access student-specific data. """ module_system = get_test_system(course_id=module.location.course_key) module_system.descriptor_runtime = module.runtime._descriptor_system # pylint: disable=protected-access module_system._services['library_tools'] = LibraryToolsService( self.store, self.user.id) # pylint: disable=protected-access def get_module(descriptor): """Mocks module_system get_module function""" sub_module_system = get_test_system( course_id=module.location.course_key) sub_module_system.get_module = get_module sub_module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access descriptor.bind_for_student(sub_module_system, self.user.id) return descriptor module_system.get_module = get_module module.xmodule_runtime = module_system def test_completion_service(self): # Only the completions for the user and course specified for the CompletionService # are returned. Values are returned for all keys provided. self.assertEqual( self.completion_service.get_completions(self.block_keys), { self.block_keys[0]: 1.0, self.block_keys[1]: 0.8, self.block_keys[2]: 0.6, self.block_keys[3]: 0.0, self.block_keys[4]: 0.0 }, ) @ddt.data(True, False) def test_enabled_honors_waffle_switch(self, enabled): self.override_waffle_switch(enabled) self.assertEqual(self.completion_service.completion_tracking_enabled(), enabled) def test_vertical_completion(self): self.assertEqual( self.completion_service.vertical_is_complete(self.vertical), False, ) for block_key in self.block_keys: BlockCompletion.objects.submit_completion(user=self.user, block_key=block_key, completion=1.0) self.assertEqual( self.completion_service.vertical_is_complete(self.vertical), True, ) def test_vertical_partial_completion(self): block_keys_count = len(self.block_keys) for i in range(block_keys_count - 1): # Mark all the child blocks completed except the last one BlockCompletion.objects.submit_completion( user=self.user, block_key=self.block_keys[i], completion=1.0) self.assertEqual( self.completion_service.vertical_is_complete(self.vertical), False, ) def test_can_mark_block_complete_on_view(self): self.assertEqual( self.completion_service.can_mark_block_complete_on_view( self.course), False) self.assertEqual( self.completion_service.can_mark_block_complete_on_view( self.chapter), False) self.assertEqual( self.completion_service.can_mark_block_complete_on_view( self.sequence), False) self.assertEqual( self.completion_service.can_mark_block_complete_on_view( self.vertical), False) self.assertEqual( self.completion_service.can_mark_block_complete_on_view(self.html), True) self.assertEqual( self.completion_service.can_mark_block_complete_on_view( self.problem), False) def test_vertical_completion_with_library_content(self): library = LibraryFactory.create(modulestore=self.store) ItemFactory.create(parent=library, category='problem', publish_item=False, user_id=self.user.id) ItemFactory.create(parent=library, category='problem', publish_item=False, user_id=self.user.id) ItemFactory.create(parent=library, category='problem', publish_item=False, user_id=self.user.id) lib_vertical = ItemFactory.create(parent=self.sequence, category='vertical', publish_item=False) library_content_block = ItemFactory.create( parent=lib_vertical, category='library_content', max_count=1, source_library_id=str(library.location.library_key), user_id=self.user.id, ) library_content_block.refresh_children() lib_vertical = self.store.get_item(lib_vertical.location) self._bind_course_module(lib_vertical) # We need to refetch the library_content_block to retrieve the # fresh version from the call to get_item for lib_vertical library_content_block = [ child for child in lib_vertical.get_children() if child.scope_ids.block_type == 'library_content' ][0] ## Ensure the library_content_block is properly set up # This is needed so we can call get_child_descriptors self._bind_course_module(library_content_block) # Make sure the runtime knows that the block's children vary per-user: self.assertTrue(library_content_block.has_dynamic_children()) self.assertEqual(len(library_content_block.children), 3) # Check how many children each user will see: self.assertEqual(len(library_content_block.get_child_descriptors()), 1) # No problems are complete yet self.assertFalse( self.completion_service.vertical_is_complete(lib_vertical)) for block_key in self.block_keys: BlockCompletion.objects.submit_completion(user=self.user, block_key=block_key, completion=1.0) # Library content problems aren't complete yet self.assertFalse( self.completion_service.vertical_is_complete(lib_vertical)) for child in library_content_block.get_child_descriptors(): BlockCompletion.objects.submit_completion( user=self.user, block_key=child.scope_ids.usage_id, completion=1.0) self.assertTrue( self.completion_service.vertical_is_complete(lib_vertical)) def test_vertical_completion_with_nested_children(self): parent_vertical = ItemFactory(parent=self.sequence, category='vertical') extra_vertical = ItemFactory(parent=parent_vertical, category='vertical') problem = ItemFactory(parent=extra_vertical, category='problem') parent_vertical = self.store.get_item(parent_vertical.location) # Nothing is complete self.assertFalse( self.completion_service.vertical_is_complete(parent_vertical)) for block_key in self.block_keys: BlockCompletion.objects.submit_completion(user=self.user, block_key=block_key, completion=1.0) # The nested child isn't complete yet self.assertFalse( self.completion_service.vertical_is_complete(parent_vertical)) BlockCompletion.objects.submit_completion(user=self.user, block_key=problem.location, completion=1.0) self.assertTrue( self.completion_service.vertical_is_complete(parent_vertical))
class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTestCase): """ Test the data returned by the CompletionService. """ @classmethod def setUpClass(cls): super(CompletionServiceTestCase, cls).setUpClass() cls.course = CourseFactory.create() with cls.store.bulk_operations(cls.course.id): cls.chapter = ItemFactory.create( parent=cls.course, category="chapter", ) cls.sequence = ItemFactory.create( parent=cls.chapter, category='sequential', ) cls.vertical = ItemFactory.create( parent=cls.sequence, category='vertical', ) cls.html = ItemFactory.create( parent=cls.vertical, category='html', ) cls.problem = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.problem2 = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.problem3 = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.problem4 = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.problem5 = ItemFactory.create( parent=cls.vertical, category="problem", ) cls.store.update_item(cls.course, UserFactory().id) cls.problems = [cls.problem, cls.problem2, cls.problem3, cls.problem4, cls.problem5] def setUp(self): super(CompletionServiceTestCase, self).setUp() self.override_waffle_switch(True) self.user = UserFactory.create() self.other_user = UserFactory.create() self.course_key = self.course.id self.other_course_key = CourseKey.from_string("course-v1:ReedX+Hum110+1904") self.block_keys = [problem.location for problem in self.problems] self.completion_service = CompletionService(self.user, self.course_key) # Proper completions for the given runtime BlockCompletion.objects.submit_completion( user=self.user, course_key=self.course_key, block_key=self.html.location, completion=1.0, ) for idx, block_key in enumerate(self.block_keys[0:3]): BlockCompletion.objects.submit_completion( user=self.user, course_key=self.course_key, block_key=block_key, completion=1.0 - (0.2 * idx), ) # Wrong user for idx, block_key in enumerate(self.block_keys[2:]): BlockCompletion.objects.submit_completion( user=self.other_user, course_key=self.course_key, block_key=block_key, completion=0.9 - (0.2 * idx), ) # Wrong course BlockCompletion.objects.submit_completion( user=self.user, course_key=self.other_course_key, block_key=self.block_keys[4], completion=0.75, ) def test_completion_service(self): # Only the completions for the user and course specified for the CompletionService # are returned. Values are returned for all keys provided. self.assertEqual( self.completion_service.get_completions(self.block_keys), { self.block_keys[0]: 1.0, self.block_keys[1]: 0.8, self.block_keys[2]: 0.6, self.block_keys[3]: 0.0, self.block_keys[4]: 0.0 }, ) @ddt.data(True, False) def test_enabled_honors_waffle_switch(self, enabled): self.override_waffle_switch(enabled) self.assertEqual(self.completion_service.completion_tracking_enabled(), enabled) def test_vertical_completion(self): self.assertEqual( self.completion_service.vertical_is_complete(self.vertical), False, ) for block_key in self.block_keys: BlockCompletion.objects.submit_completion( user=self.user, course_key=self.course_key, block_key=block_key, completion=1.0 ) self.assertEqual( self.completion_service.vertical_is_complete(self.vertical), True, ) def test_vertical_partial_completion(self): block_keys_count = len(self.block_keys) for i in range(block_keys_count - 1): # Mark all the child blocks completed except the last one BlockCompletion.objects.submit_completion( user=self.user, course_key=self.course_key, block_key=self.block_keys[i], completion=1.0 ) self.assertEqual( self.completion_service.vertical_is_complete(self.vertical), False, ) def test_can_mark_block_complete_on_view(self): self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.course), False) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.chapter), False) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.sequence), False) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.vertical), False) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.html), True) self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.problem), False)
class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTestCase): """ Test the data returned by the CompletionService. """ @classmethod def setUpClass(cls): super().setUpClass() cls.course = CourseFactory.create() with cls.store.bulk_operations(cls.course.id): cls.chapter = ItemFactory.create( parent=cls.course, category="chapter", publish_item=False, ) cls.sequence = ItemFactory.create( parent=cls.chapter, category='sequential', publish_item=False, ) cls.vertical = ItemFactory.create( parent=cls.sequence, category='vertical', publish_item=False, ) cls.html = ItemFactory.create( parent=cls.vertical, category='html', publish_item=False, ) cls.problem = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.problem2 = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.problem3 = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.problem4 = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.problem5 = ItemFactory.create( parent=cls.vertical, category="problem", publish_item=False, ) cls.store.update_item(cls.course, UserFactory().id) cls.problems = [cls.problem, cls.problem2, cls.problem3, cls.problem4, cls.problem5] def setUp(self): super().setUp() self.override_waffle_switch(True) self.user = UserFactory.create() self.other_user = UserFactory.create() self.course_key = self.course.id self.other_course_key = CourseKey.from_string("course-v1:ReedX+Hum110+1904") self.block_keys = [problem.location for problem in self.problems] self.completion_service = CompletionService(self.user, self.course_key) # Proper completions for the given runtime BlockCompletion.objects.submit_completion( user=self.user, block_key=self.html.location, completion=1.0, ) for idx, block_key in enumerate(self.block_keys[0:3]): BlockCompletion.objects.submit_completion( user=self.user, block_key=block_key, completion=1.0 - (0.2 * idx), ) # Wrong user for idx, block_key in enumerate(self.block_keys[2:]): BlockCompletion.objects.submit_completion( user=self.other_user, block_key=block_key, completion=0.9 - (0.2 * idx), ) # Wrong course BlockCompletion.objects.submit_completion( user=self.user, block_key=self.other_course_key.make_usage_key('problem', 'other'), completion=0.75, ) def _bind_course_module(self, module): """ Bind a module (part of self.course) so we can access student-specific data. """ module_system = get_test_system(course_id=module.location.course_key) module_system.descriptor_runtime = module.runtime._descriptor_system # pylint: disable=protected-access module_system._services['library_tools'] = LibraryToolsService(self.store, self.user.id) # pylint: disable=protected-access def get_module(descriptor): """Mocks module_system get_module function""" sub_module_system = get_test_system(course_id=module.location.course_key) sub_module_system.get_module = get_module sub_module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access descriptor.bind_for_student(sub_module_system, self.user.id) return descriptor module_system.get_module = get_module module.xmodule_runtime = module_system def test_completion_service(self): # Only the completions for the user and course specified for the CompletionService # are returned. Values are returned for all keys provided. assert self.completion_service.get_completions(self.block_keys) == { self.block_keys[0]: 1.0, self.block_keys[1]: 0.8, self.block_keys[2]: 0.6, self.block_keys[3]: 0.0, self.block_keys[4]: 0.0 } @ddt.data(True, False) def test_enabled_honors_waffle_switch(self, enabled): self.override_waffle_switch(enabled) assert self.completion_service.completion_tracking_enabled() == enabled def test_vertical_completion(self): assert self.completion_service.vertical_is_complete(self.vertical) is False for block_key in self.block_keys: BlockCompletion.objects.submit_completion( user=self.user, block_key=block_key, completion=1.0 ) assert self.completion_service.vertical_is_complete(self.vertical) is True def test_vertical_partial_completion(self): block_keys_count = len(self.block_keys) for i in range(block_keys_count - 1): # Mark all the child blocks completed except the last one BlockCompletion.objects.submit_completion( user=self.user, block_key=self.block_keys[i], completion=1.0 ) assert self.completion_service.vertical_is_complete(self.vertical) is False def test_can_mark_block_complete_on_view(self): assert self.completion_service.can_mark_block_complete_on_view(self.course) is False assert self.completion_service.can_mark_block_complete_on_view(self.chapter) is False assert self.completion_service.can_mark_block_complete_on_view(self.sequence) is False assert self.completion_service.can_mark_block_complete_on_view(self.vertical) is False assert self.completion_service.can_mark_block_complete_on_view(self.html) is True assert self.completion_service.can_mark_block_complete_on_view(self.problem) is False @override_settings(FEATURES={**settings.FEATURES, 'MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW': True}) def test_can_mark_library_content_complete_on_view(self): library = LibraryFactory.create(modulestore=self.store) lib_vertical = ItemFactory.create(parent=self.sequence, category='vertical', publish_item=False) library_content_block = ItemFactory.create( parent=lib_vertical, category='library_content', max_count=1, source_library_id=str(library.location.library_key), user_id=self.user.id, ) self.assertTrue(self.completion_service.can_mark_block_complete_on_view(library_content_block)) def test_vertical_completion_with_library_content(self): library = LibraryFactory.create(modulestore=self.store) ItemFactory.create(parent=library, category='problem', publish_item=False, user_id=self.user.id) ItemFactory.create(parent=library, category='problem', publish_item=False, user_id=self.user.id) ItemFactory.create(parent=library, category='problem', publish_item=False, user_id=self.user.id) # Create a new vertical to hold the library content block # It is very important that we use parent_location=self.sequence.location (and not parent=self.sequence), since # sequence is a class attribute and passing it by value will update its .children=[] which will then leak into # other tests and cause errors if the children no longer exist. lib_vertical = ItemFactory.create( parent_location=self.sequence.location, category='vertical', publish_item=False, ) library_content_block = ItemFactory.create( parent=lib_vertical, category='library_content', max_count=1, source_library_id=str(library.location.library_key), user_id=self.user.id, ) # Library Content Block needs its children to be completed. self.assertFalse(self.completion_service.can_mark_block_complete_on_view(library_content_block)) library_content_block.refresh_children() lib_vertical = self.store.get_item(lib_vertical.location) self._bind_course_module(lib_vertical) # We need to refetch the library_content_block to retrieve the # fresh version from the call to get_item for lib_vertical library_content_block = [child for child in lib_vertical.get_children() if child.scope_ids.block_type == 'library_content'][0] ## Ensure the library_content_block is properly set up # This is needed so we can call get_child_descriptors self._bind_course_module(library_content_block) # Make sure the runtime knows that the block's children vary per-user: assert library_content_block.has_dynamic_children() assert len(library_content_block.children) == 3 # Check how many children each user will see: assert len(library_content_block.get_child_descriptors()) == 1 # No problems are complete yet assert not self.completion_service.vertical_is_complete(lib_vertical) for block_key in self.block_keys: BlockCompletion.objects.submit_completion( user=self.user, block_key=block_key, completion=1.0 ) # Library content problems aren't complete yet assert not self.completion_service.vertical_is_complete(lib_vertical) for child in library_content_block.get_child_descriptors(): BlockCompletion.objects.submit_completion( user=self.user, block_key=child.scope_ids.usage_id, completion=1.0 ) assert self.completion_service.vertical_is_complete(lib_vertical) def test_vertical_completion_with_nested_children(self): # Create a new vertical. # It is very important that we use parent_location=self.sequence.location (and not parent=self.sequence), since # sequence is a class attribute and passing it by value will update its .children=[] which will then leak into # other tests and cause errors if the children no longer exist. parent_vertical = ItemFactory(parent_location=self.sequence.location, category='vertical') extra_vertical = ItemFactory(parent=parent_vertical, category='vertical') problem = ItemFactory(parent=extra_vertical, category='problem') parent_vertical = self.store.get_item(parent_vertical.location) # Nothing is complete assert not self.completion_service.vertical_is_complete(parent_vertical) for block_key in self.block_keys: BlockCompletion.objects.submit_completion( user=self.user, block_key=block_key, completion=1.0 ) # The nested child isn't complete yet assert not self.completion_service.vertical_is_complete(parent_vertical) BlockCompletion.objects.submit_completion( user=self.user, block_key=problem.location, completion=1.0 ) assert self.completion_service.vertical_is_complete(parent_vertical)