Problem content draft.
" - } + "id": str(self.problem_usage_key), + "metadata": {}, + "data": "Problem content draft.
", + }, ) # Both published and draft content should be different - draft = self.get_item_from_modulestore(self.problem_usage_key, verify_is_draft=True) + draft = self.get_item_from_modulestore( + self.problem_usage_key, verify_is_draft=True + ) self.assertNotEqual(draft.data, published.data) # Get problem by 'xblock_handler' - view_url = reverse_usage_url("xblock_view_handler", self.problem_usage_key, {"view_name": STUDENT_VIEW}) - resp = self.client.get(view_url, HTTP_ACCEPT='application/json') + view_url = reverse_usage_url( + "xblock_view_handler", self.problem_usage_key, {"view_name": STUDENT_VIEW} + ) + resp = self.client.get(view_url, HTTP_ACCEPT="application/json") self.assertEqual(resp.status_code, 200) # Activate the editing view - view_url = reverse_usage_url("xblock_view_handler", self.problem_usage_key, {"view_name": STUDIO_VIEW}) - resp = self.client.get(view_url, HTTP_ACCEPT='application/json') + view_url = reverse_usage_url( + "xblock_view_handler", self.problem_usage_key, {"view_name": STUDIO_VIEW} + ) + resp = self.client.get(view_url, HTTP_ACCEPT="application/json") self.assertEqual(resp.status_code, 200) # Both published and draft content should still be different - draft = self.get_item_from_modulestore(self.problem_usage_key, verify_is_draft=True) + draft = self.get_item_from_modulestore( + self.problem_usage_key, verify_is_draft=True + ) self.assertNotEqual(draft.data, published.data) # Fetch the published version again to make sure the data is correct. - published = modulestore().get_item(published.location, revision=ModuleStoreEnum.RevisionOption.published_only) + published = modulestore().get_item( + published.location, revision=ModuleStoreEnum.RevisionOption.published_only + ) self.assertNotEqual(draft.data, published.data) def test_publish_states_of_nested_xblocks(self): - """ Test publishing of a unit page containing a nested xblock """ + """Test publishing of a unit page containing a nested xblock""" - resp = self.create_xblock(parent_usage_key=self.seq_usage_key, display_name='Test Unit', category='vertical') + resp = self.create_xblock( + parent_usage_key=self.seq_usage_key, + display_name="Test Unit", + category="vertical", + ) unit_usage_key = self.response_usage_key(resp) - resp = self.create_xblock(parent_usage_key=unit_usage_key, category='wrapper') + resp = self.create_xblock(parent_usage_key=unit_usage_key, category="wrapper") wrapper_usage_key = self.response_usage_key(resp) - resp = self.create_xblock(parent_usage_key=wrapper_usage_key, category='html') + resp = self.create_xblock(parent_usage_key=wrapper_usage_key, category="html") html_usage_key = self.response_usage_key(resp) # The unit and its children should be private initially - unit_update_url = reverse_usage_url('xblock_handler', unit_usage_key) + unit_update_url = reverse_usage_url("xblock_handler", unit_usage_key) self.assertFalse(self._is_location_published(unit_usage_key)) self.assertFalse(self._is_location_published(html_usage_key)) # Make the unit public and verify that the problem is also made public - resp = self.client.ajax_post( - unit_update_url, - data={'publish': 'make_public'} - ) + resp = self.client.ajax_post(unit_update_url, data={"publish": "make_public"}) self.assertEqual(resp.status_code, 200) self._verify_published_with_no_draft(unit_usage_key) self._verify_published_with_no_draft(html_usage_key) @@ -1897,9 +2213,9 @@ def test_publish_states_of_nested_xblocks(self): resp = self.client.ajax_post( unit_update_url, data={ - 'id': str(unit_usage_key), - 'metadata': {}, - } + "id": str(unit_usage_key), + "metadata": {}, + }, ) self.assertEqual(resp.status_code, 200) self._verify_published_with_draft(unit_usage_key) @@ -1910,42 +2226,49 @@ def test_field_value_errors(self): Test that if the user's input causes a ValueError on an XBlock field, we provide a friendly error message back to the user. """ - response = self.create_xblock(parent_usage_key=self.seq_usage_key, category='video') + response = self.create_xblock( + parent_usage_key=self.seq_usage_key, category="video" + ) video_usage_key = self.response_usage_key(response) - update_url = reverse_usage_url('xblock_handler', video_usage_key) + update_url = reverse_usage_url("xblock_handler", video_usage_key) response = self.client.ajax_post( update_url, data={ - 'id': str(video_usage_key), - 'metadata': { - 'saved_video_position': "Not a valid relative time", + "id": str(video_usage_key), + "metadata": { + "saved_video_position": "Not a valid relative time", }, - } + }, ) self.assertEqual(response.status_code, 400) - parsed = json.loads(response.content.decode('utf-8')) + parsed = json.loads(response.content.decode("utf-8")) self.assertIn("error", parsed) - self.assertIn("Incorrect RelativeTime value", parsed["error"]) # See xmodule/fields.py + self.assertIn( + "Incorrect RelativeTime value", parsed["error"] + ) # See xmodule/fields.py class TestEditItemSplitMongo(TestEditItemSetup): """ Tests for EditItem running on top of the SplitMongoModuleStore. """ + def test_editing_view_wrappers(self): """ Verify that the editing view only generates a single wrapper, no matter how many times it's loaded Exposes: PLAT-417 """ - view_url = reverse_usage_url("xblock_view_handler", self.problem_usage_key, {"view_name": STUDIO_VIEW}) + view_url = reverse_usage_url( + "xblock_view_handler", self.problem_usage_key, {"view_name": STUDIO_VIEW} + ) for __ in range(3): - resp = self.client.get(view_url, HTTP_ACCEPT='application/json') + resp = self.client.get(view_url, HTTP_ACCEPT="application/json") self.assertEqual(resp.status_code, 200) - content = json.loads(resp.content.decode('utf-8')) - self.assertEqual(len(PyQuery(content['html'])(f'.xblock-{STUDIO_VIEW}')), 1) + content = json.loads(resp.content.decode("utf-8")) + self.assertEqual(len(PyQuery(content["html"])(f".xblock-{STUDIO_VIEW}")), 1) class TestEditSplitModule(ItemTest): @@ -1957,37 +2280,55 @@ def setUp(self): super().setUp() self.user = UserFactory() - self.first_user_partition_group_1 = Group(str(MINIMUM_STATIC_PARTITION_ID + 1), 'alpha') - self.first_user_partition_group_2 = Group(str(MINIMUM_STATIC_PARTITION_ID + 2), 'beta') + self.first_user_partition_group_1 = Group( + str(MINIMUM_STATIC_PARTITION_ID + 1), "alpha" + ) + self.first_user_partition_group_2 = Group( + str(MINIMUM_STATIC_PARTITION_ID + 2), "beta" + ) self.first_user_partition = UserPartition( - MINIMUM_STATIC_PARTITION_ID, 'first_partition', 'First Partition', - [self.first_user_partition_group_1, self.first_user_partition_group_2] + MINIMUM_STATIC_PARTITION_ID, + "first_partition", + "First Partition", + [self.first_user_partition_group_1, self.first_user_partition_group_2], ) # There is a test point below (test_create_groups) that purposefully wants the group IDs # of the 2 partitions to overlap (which is not something that normally happens). - self.second_user_partition_group_1 = Group(str(MINIMUM_STATIC_PARTITION_ID + 1), 'Group 1') - self.second_user_partition_group_2 = Group(str(MINIMUM_STATIC_PARTITION_ID + 2), 'Group 2') - self.second_user_partition_group_3 = Group(str(MINIMUM_STATIC_PARTITION_ID + 3), 'Group 3') + self.second_user_partition_group_1 = Group( + str(MINIMUM_STATIC_PARTITION_ID + 1), "Group 1" + ) + self.second_user_partition_group_2 = Group( + str(MINIMUM_STATIC_PARTITION_ID + 2), "Group 2" + ) + self.second_user_partition_group_3 = Group( + str(MINIMUM_STATIC_PARTITION_ID + 3), "Group 3" + ) self.second_user_partition = UserPartition( - MINIMUM_STATIC_PARTITION_ID + 10, 'second_partition', 'Second Partition', + MINIMUM_STATIC_PARTITION_ID + 10, + "second_partition", + "Second Partition", [ self.second_user_partition_group_1, self.second_user_partition_group_2, - self.second_user_partition_group_3 - ] + self.second_user_partition_group_3, + ], ) self.course.user_partitions = [ self.first_user_partition, - self.second_user_partition + self.second_user_partition, ] self.store.update_item(self.course, self.user.id) root_usage_key = self._create_vertical() - resp = self.create_xblock(category='split_test', parent_usage_key=root_usage_key) + resp = self.create_xblock( + category="split_test", parent_usage_key=root_usage_key + ) self.split_test_usage_key = self.response_usage_key(resp) - self.split_test_update_url = reverse_usage_url("xblock_handler", self.split_test_usage_key) + self.split_test_update_url = reverse_usage_url( + "xblock_handler", self.split_test_usage_key + ) self.request_factory = RequestFactory() - self.request = self.request_factory.get('/dummy-url') + self.request = self.request_factory.get("/dummy-url") self.request.user = self.user def _update_partition_id(self, partition_id): @@ -2001,11 +2342,13 @@ def _update_partition_id(self, partition_id): # Even though user_partition_id is Scope.content, it will get saved by the Studio editor as # metadata. The code in block.py will update the field correctly, even though it is not the # expected scope. - data={'metadata': {'user_partition_id': str(partition_id)}} + data={"metadata": {"user_partition_id": str(partition_id)}}, ) # Verify the partition_id was saved. - split_test = self.get_item_from_modulestore(self.split_test_usage_key, verify_is_draft=True) + split_test = self.get_item_from_modulestore( + self.split_test_usage_key, verify_is_draft=True + ) self.assertEqual(partition_id, split_test.user_partition_id) return split_test @@ -2022,7 +2365,9 @@ def test_create_groups(self): Test that verticals are created for the configuration groups when a spit test block is edited. """ - split_test = self.get_item_from_modulestore(self.split_test_usage_key, verify_is_draft=True) + split_test = self.get_item_from_modulestore( + self.split_test_usage_key, verify_is_draft=True + ) # Initially, no user_partition_id is set, and the split_test has no children. self.assertEqual(-1, split_test.user_partition_id) self.assertEqual(0, len(split_test.children)) @@ -2032,23 +2377,39 @@ def test_create_groups(self): # Verify that child verticals have been set to match the groups self.assertEqual(2, len(split_test.children)) - vertical_0 = self.get_item_from_modulestore(split_test.children[0], verify_is_draft=True) - vertical_1 = self.get_item_from_modulestore(split_test.children[1], verify_is_draft=True) + vertical_0 = self.get_item_from_modulestore( + split_test.children[0], verify_is_draft=True + ) + vertical_1 = self.get_item_from_modulestore( + split_test.children[1], verify_is_draft=True + ) self.assertEqual("vertical", vertical_0.category) self.assertEqual("vertical", vertical_1.category) - self.assertEqual("Group ID " + str(MINIMUM_STATIC_PARTITION_ID + 1), vertical_0.display_name) - self.assertEqual("Group ID " + str(MINIMUM_STATIC_PARTITION_ID + 2), vertical_1.display_name) + self.assertEqual( + "Group ID " + str(MINIMUM_STATIC_PARTITION_ID + 1), vertical_0.display_name + ) + self.assertEqual( + "Group ID " + str(MINIMUM_STATIC_PARTITION_ID + 2), vertical_1.display_name + ) # Verify that the group_id_to_child mapping is correct. self.assertEqual(2, len(split_test.group_id_to_child)) - self.assertEqual(vertical_0.location, split_test.group_id_to_child[str(self.first_user_partition_group_1.id)]) - self.assertEqual(vertical_1.location, split_test.group_id_to_child[str(self.first_user_partition_group_2.id)]) + self.assertEqual( + vertical_0.location, + split_test.group_id_to_child[str(self.first_user_partition_group_1.id)], + ) + self.assertEqual( + vertical_1.location, + split_test.group_id_to_child[str(self.first_user_partition_group_2.id)], + ) def test_split_xblock_info_group_name(self): """ Test that concise outline for split test component gives display name as group name. """ - split_test = self.get_item_from_modulestore(self.split_test_usage_key, verify_is_draft=True) + split_test = self.get_item_from_modulestore( + self.split_test_usage_key, verify_is_draft=True + ) # Initially, no user_partition_id is set, and the split_test has no children. self.assertEqual(split_test.user_partition_id, -1) self.assertEqual(len(split_test.children), 0) @@ -2064,10 +2425,14 @@ def test_split_xblock_info_group_name(self): include_child_info=True, include_children_predicate=lambda xblock: xblock.has_children, course=self.course, - user=self.request.user + user=self.request.user, + ) + self.assertEqual( + xblock_info["child_info"]["children"][0]["display_name"], "alpha" + ) + self.assertEqual( + xblock_info["child_info"]["children"][1]["display_name"], "beta" ) - self.assertEqual(xblock_info['child_info']['children'][0]['display_name'], 'alpha') - self.assertEqual(xblock_info['child_info']['children'][1]['display_name'], 'beta') def test_change_user_partition_id(self): """ @@ -2086,15 +2451,30 @@ def test_change_user_partition_id(self): self.assertEqual(5, len(split_test.children)) self.assertEqual(initial_vertical_0_location, split_test.children[0]) self.assertEqual(initial_vertical_1_location, split_test.children[1]) - vertical_0 = self.get_item_from_modulestore(split_test.children[2], verify_is_draft=True) - vertical_1 = self.get_item_from_modulestore(split_test.children[3], verify_is_draft=True) - vertical_2 = self.get_item_from_modulestore(split_test.children[4], verify_is_draft=True) + vertical_0 = self.get_item_from_modulestore( + split_test.children[2], verify_is_draft=True + ) + vertical_1 = self.get_item_from_modulestore( + split_test.children[3], verify_is_draft=True + ) + vertical_2 = self.get_item_from_modulestore( + split_test.children[4], verify_is_draft=True + ) # Verify that the group_id_to child mapping is correct. self.assertEqual(3, len(split_test.group_id_to_child)) - self.assertEqual(vertical_0.location, split_test.group_id_to_child[str(self.second_user_partition_group_1.id)]) - self.assertEqual(vertical_1.location, split_test.group_id_to_child[str(self.second_user_partition_group_2.id)]) - self.assertEqual(vertical_2.location, split_test.group_id_to_child[str(self.second_user_partition_group_3.id)]) + self.assertEqual( + vertical_0.location, + split_test.group_id_to_child[str(self.second_user_partition_group_1.id)], + ) + self.assertEqual( + vertical_1.location, + split_test.group_id_to_child[str(self.second_user_partition_group_2.id)], + ) + self.assertEqual( + vertical_2.location, + split_test.group_id_to_child[str(self.second_user_partition_group_3.id)], + ) self.assertNotEqual(initial_vertical_0_location, vertical_0.location) self.assertNotEqual(initial_vertical_1_location, vertical_1.location) @@ -2142,8 +2522,14 @@ def test_add_groups(self): new_group_id = "1002" split_test.user_partitions = [ UserPartition( - self.first_user_partition.id, 'first_partition', 'First Partition', - [self.first_user_partition_group_1, self.first_user_partition_group_2, Group(new_group_id, 'pie')] + self.first_user_partition.id, + "first_partition", + "First Partition", + [ + self.first_user_partition_group_1, + self.first_user_partition_group_2, + Group(new_group_id, "pie"), + ], ) ] self.store.update_item(split_test, self.user.id) @@ -2156,7 +2542,9 @@ def test_add_groups(self): # CachingDescriptorSystem is used in tests. # CachingDescriptorSystem doesn't have user service, that's needed for # SplitTestBlock. So, in this line of code we add this service manually. - split_test.runtime._services['user'] = DjangoXBlockUserService(self.user) # pylint: disable=protected-access + split_test.runtime._services["user"] = DjangoXBlockUserService( # pylint: disable=protected-access + self.user + ) # Call add_missing_groups method to add the missing group. split_test.add_missing_groups(self.request) @@ -2180,7 +2568,7 @@ def setUp(self): self.request_factory = RequestFactory() - patcher = patch('cms.djangoapps.contentstore.views.component.modulestore') + patcher = patch("cms.djangoapps.contentstore.views.component.modulestore") self.modulestore = patcher.start() self.addCleanup(patcher.stop) @@ -2190,23 +2578,28 @@ def setUp(self): self.block = self.modulestore.return_value.get_item.return_value self.usage_key = BlockUsageLocator( - CourseLocator('dummy_org', 'dummy_course', 'dummy_run'), 'dummy_category', 'dummy_name' + CourseLocator("dummy_org", "dummy_course", "dummy_run"), + "dummy_category", + "dummy_name", ) self.usage_key_string = str(self.usage_key) - self.user = StaffFactory(course_key=CourseLocator('dummy_org', 'dummy_course', 'dummy_run')) - self.request = self.request_factory.get('/dummy-url') + self.user = StaffFactory( + course_key=CourseLocator("dummy_org", "dummy_course", "dummy_run") + ) + self.request = self.request_factory.get("/dummy-url") self.request.user = self.user def test_invalid_handler(self): self.block.handle.side_effect = NoSuchHandlerError with self.assertRaises(Http404): - component_handler(self.request, self.usage_key_string, 'invalid_handler') + component_handler(self.request, self.usage_key_string, "invalid_handler") - @ddt.data('GET', 'POST', 'PUT', 'DELETE') + @ddt.data("GET", "POST", "PUT", "DELETE") def test_request_method(self, method): - - def check_handler(handler, request, suffix): # lint-amnesty, pylint: disable=unused-argument + def check_handler( + handler, request, suffix + ): # lint-amnesty, pylint: disable=unused-argument self.assertEqual(request.method, method) return Response() @@ -2214,21 +2607,27 @@ def check_handler(handler, request, suffix): # lint-amnesty, pylint: disable=un # Have to use the right method to create the request to get the HTTP method that we want req_factory_method = getattr(self.request_factory, method.lower()) - request = req_factory_method('/dummy-url') + request = req_factory_method("/dummy-url") request.user = self.user - component_handler(request, self.usage_key_string, 'dummy_handler') + component_handler(request, self.usage_key_string, "dummy_handler") @ddt.data(200, 404, 500) def test_response_code(self, status_code): - def create_response(handler, request, suffix): # lint-amnesty, pylint: disable=unused-argument + def create_response( + handler, request, suffix + ): # lint-amnesty, pylint: disable=unused-argument return Response(status_code=status_code) self.block.handle = create_response - self.assertEqual(component_handler(self.request, self.usage_key_string, 'dummy_handler').status_code, - status_code) + self.assertEqual( + component_handler( + self.request, self.usage_key_string, "dummy_handler" + ).status_code, + status_code, + ) - @patch('cms.djangoapps.contentstore.views.component.log') + @patch("cms.djangoapps.contentstore.views.component.log") def test_submit_studio_edits_checks_author_permission(self, mock_logger): """ Test logging a user without studio write permissions attempts to run a studio submit handler.. @@ -2237,36 +2636,45 @@ def test_submit_studio_edits_checks_author_permission(self, mock_logger): mock_logger (object): A mock logger object. """ - def create_response(handler, request, suffix): # lint-amnesty, pylint: disable=unused-argument + def create_response( + handler, request, suffix + ): # lint-amnesty, pylint: disable=unused-argument """create dummy response""" return Response(status_code=200) self.request.user = UserFactory() - mock_handler = 'dummy_handler' + mock_handler = "dummy_handler" self.block.handle = create_response with patch( - 'cms.djangoapps.contentstore.views.component.is_xblock_aside', - return_value=False - ), patch("cms.djangoapps.contentstore.views.component.webob_to_django_response"): + "cms.djangoapps.contentstore.views.component.is_xblock_aside", + return_value=False, + ), patch( + "cms.djangoapps.contentstore.views.component.webob_to_django_response" + ): component_handler(self.request, self.usage_key_string, mock_handler) mock_logger.warning.assert_called_with( "%s does not have have studio write permissions on course: %s. write operations not performed on %r", - self. request.user.id, + self.request.user.id, UsageKey.from_string(self.usage_key_string).course_key, - mock_handler + mock_handler, ) - @ddt.data((True, True), (False, False),) + @ddt.data( + (True, True), + (False, False), + ) @ddt.unpack def test_aside(self, is_xblock_aside, is_get_aside_called): """ test get_aside_from_xblock called """ - def create_response(handler, request, suffix): # lint-amnesty, pylint: disable=unused-argument + def create_response( + handler, request, suffix + ): # lint-amnesty, pylint: disable=unused-argument """create dummy response""" return Response(status_code=200) @@ -2281,18 +2689,14 @@ def get_usage_key(): self.block.handle = create_response with patch( - 'cms.djangoapps.contentstore.views.component.is_xblock_aside', - return_value=is_xblock_aside + "cms.djangoapps.contentstore.views.component.is_xblock_aside", + return_value=is_xblock_aside, ), patch( - 'cms.djangoapps.contentstore.views.component.get_aside_from_xblock' + "cms.djangoapps.contentstore.views.component.get_aside_from_xblock" ) as mocked_get_aside_from_xblock, patch( "cms.djangoapps.contentstore.views.component.webob_to_django_response" ) as mocked_webob_to_django_response: - component_handler( - self.request, - get_usage_key(), - 'dummy_handler' - ) + component_handler(self.request, get_usage_key(), "dummy_handler") assert mocked_webob_to_django_response.called is True assert mocked_get_aside_from_xblock.called is is_get_aside_called @@ -2306,22 +2710,46 @@ class TestComponentTemplates(CourseTestCase): def setUp(self): super().setUp() # Advanced Module support levels. - XBlockStudioConfiguration.objects.create(name='poll', enabled=True, support_level="fs") - XBlockStudioConfiguration.objects.create(name='survey', enabled=True, support_level="ps") - XBlockStudioConfiguration.objects.create(name='annotatable', enabled=True, support_level="us") + XBlockStudioConfiguration.objects.create( + name="poll", enabled=True, support_level="fs" + ) + XBlockStudioConfiguration.objects.create( + name="survey", enabled=True, support_level="ps" + ) + XBlockStudioConfiguration.objects.create( + name="annotatable", enabled=True, support_level="us" + ) # Basic component support levels. - XBlockStudioConfiguration.objects.create(name='html', enabled=True, support_level="fs") - XBlockStudioConfiguration.objects.create(name='discussion', enabled=True, support_level="ps") - XBlockStudioConfiguration.objects.create(name='problem', enabled=True, support_level="us") - XBlockStudioConfiguration.objects.create(name='video', enabled=True, support_level="us") + XBlockStudioConfiguration.objects.create( + name="html", enabled=True, support_level="fs" + ) + XBlockStudioConfiguration.objects.create( + name="discussion", enabled=True, support_level="ps" + ) + XBlockStudioConfiguration.objects.create( + name="problem", enabled=True, support_level="us" + ) + XBlockStudioConfiguration.objects.create( + name="video", enabled=True, support_level="us" + ) # ORA Block has it's own category. - XBlockStudioConfiguration.objects.create(name='openassessment', enabled=True, support_level="us") + XBlockStudioConfiguration.objects.create( + name="openassessment", enabled=True, support_level="us" + ) # Library Sourced Block and Library Content block has it's own category. - XBlockStudioConfiguration.objects.create(name='library_sourced', enabled=True, support_level="fs") - XBlockStudioConfiguration.objects.create(name='library_content', enabled=True, support_level="fs") + XBlockStudioConfiguration.objects.create( + name="library_sourced", enabled=True, support_level="fs" + ) + XBlockStudioConfiguration.objects.create( + name="library_content", enabled=True, support_level="fs" + ) # XBlock masquerading as a problem - XBlockStudioConfiguration.objects.create(name='drag-and-drop-v2', enabled=True, support_level="fs") - XBlockStudioConfiguration.objects.create(name='staffgradedxblock', enabled=True, support_level="us") + XBlockStudioConfiguration.objects.create( + name="drag-and-drop-v2", enabled=True, support_level="fs" + ) + XBlockStudioConfiguration.objects.create( + name="staffgradedxblock", enabled=True, support_level="us" + ) self.templates = get_component_templates(self.course) @@ -2330,26 +2758,40 @@ def get_templates_of_type(self, template_type): Returns the templates for the specified type, or None if none is found. """ template_dict = self._get_template_dict_of_type(template_type) - return template_dict.get('templates') if template_dict else None + return template_dict.get("templates") if template_dict else None def get_display_name_of_type(self, template_type): """ Returns the display name for the specified type, or None if none found. """ template_dict = self._get_template_dict_of_type(template_type) - return template_dict.get('display_name') if template_dict else None + return template_dict.get("display_name") if template_dict else None def _get_template_dict_of_type(self, template_type): """ Returns a dictionary of values for a category type. """ - return next((template for template in self.templates if template.get('type') == template_type), None) + return next( + ( + template + for template in self.templates + if template.get("type") == template_type + ), + None, + ) def get_template(self, templates, display_name): """ Returns the template which has the specified display name. """ - return next((template for template in templates if template.get('display_name') == display_name), None) + return next( + ( + template + for template in templates + if template.get("display_name") == display_name + ), + None, + ) def test_basic_components(self): """ @@ -2361,15 +2803,15 @@ def test_basic_components(self): self._verify_basic_component_display_name("discussion", "Discussion") self._verify_basic_component_display_name("video", "Video") self._verify_basic_component_display_name("openassessment", "Open Response") - self.assertGreater(len(self.get_templates_of_type('library')), 0) - self.assertGreater(len(self.get_templates_of_type('html')), 0) - self.assertGreater(len(self.get_templates_of_type('problem')), 0) - self.assertIsNone(self.get_templates_of_type('advanced')) + self.assertGreater(len(self.get_templates_of_type("library")), 0) + self.assertGreater(len(self.get_templates_of_type("html")), 0) + self.assertGreater(len(self.get_templates_of_type("problem")), 0) + self.assertIsNone(self.get_templates_of_type("advanced")) # Now fully disable video through XBlockConfiguration - XBlockConfiguration.objects.create(name='video', enabled=False) + XBlockConfiguration.objects.create(name="video", enabled=False) self.templates = get_component_templates(self.course) - self.assertIsNone(self.get_templates_of_type('video')) + self.assertIsNone(self.get_templates_of_type("video")) def test_basic_components_support_levels(self): """ @@ -2381,66 +2823,74 @@ def test_basic_components_support_levels(self): self.assertEqual([], self.get_templates_of_type("video")) supported_problem_templates = [ { - 'boilerplate_name': None, - 'category': 'drag-and-drop-v2', - 'display_name': 'Drag and Drop', - 'hinted': False, - 'support_level': 'fs', - 'tab': 'advanced' + "boilerplate_name": None, + "category": "drag-and-drop-v2", + "display_name": "Drag and Drop", + "hinted": False, + "support_level": "fs", + "tab": "advanced", } ] - self.assertEqual(supported_problem_templates, self.get_templates_of_type("problem")) + self.assertEqual( + supported_problem_templates, self.get_templates_of_type("problem") + ) self.course.allow_unsupported_xblocks = True self.templates = get_component_templates(self.course) self._verify_basic_component("video", "Video", "us") - problem_templates = self.get_templates_of_type('problem') - problem_no_boilerplate = self.get_template(problem_templates, 'Blank Advanced Problem') + problem_templates = self.get_templates_of_type("problem") + problem_no_boilerplate = self.get_template( + problem_templates, "Blank Advanced Problem" + ) self.assertIsNotNone(problem_no_boilerplate) - self.assertEqual('us', problem_no_boilerplate['support_level']) + self.assertEqual("us", problem_no_boilerplate["support_level"]) # Now fully disable video through XBlockConfiguration - XBlockConfiguration.objects.create(name='video', enabled=False) + XBlockConfiguration.objects.create(name="video", enabled=False) self.templates = get_component_templates(self.course) - self.assertIsNone(self.get_templates_of_type('video')) + self.assertIsNone(self.get_templates_of_type("video")) def test_advanced_components(self): """ Test the handling of advanced component templates. """ - self.course.advanced_modules.append('word_cloud') + self.course.advanced_modules.append("word_cloud") self.templates = get_component_templates(self.course) - advanced_templates = self.get_templates_of_type('advanced') + advanced_templates = self.get_templates_of_type("advanced") self.assertEqual(len(advanced_templates), 1) world_cloud_template = advanced_templates[0] - self.assertEqual(world_cloud_template.get('category'), 'word_cloud') - self.assertEqual(world_cloud_template.get('display_name'), 'Word cloud') - self.assertIsNone(world_cloud_template.get('boilerplate_name', None)) + self.assertEqual(world_cloud_template.get("category"), "word_cloud") + self.assertEqual(world_cloud_template.get("display_name"), "Word cloud") + self.assertIsNone(world_cloud_template.get("boilerplate_name", None)) # Verify that non-advanced components are not added twice - self.course.advanced_modules.append('video') - self.course.advanced_modules.append('drag-and-drop-v2') + self.course.advanced_modules.append("video") + self.course.advanced_modules.append("drag-and-drop-v2") self.templates = get_component_templates(self.course) - advanced_templates = self.get_templates_of_type('advanced') + advanced_templates = self.get_templates_of_type("advanced") self.assertEqual(len(advanced_templates), 1) only_template = advanced_templates[0] - self.assertNotEqual(only_template.get('category'), 'video') - self.assertNotEqual(only_template.get('category'), 'drag-and-drop-v2') + self.assertNotEqual(only_template.get("category"), "video") + self.assertNotEqual(only_template.get("category"), "drag-and-drop-v2") # Now fully disable word_cloud through XBlockConfiguration - XBlockConfiguration.objects.create(name='word_cloud', enabled=False) + XBlockConfiguration.objects.create(name="word_cloud", enabled=False) self.templates = get_component_templates(self.course) - self.assertIsNone(self.get_templates_of_type('advanced')) + self.assertIsNone(self.get_templates_of_type("advanced")) def test_advanced_problems(self): """ Test the handling of advanced problem templates. """ - problem_templates = self.get_templates_of_type('problem') - circuit_template = self.get_template(problem_templates, 'Circuit Schematic Builder') + problem_templates = self.get_templates_of_type("problem") + circuit_template = self.get_template( + problem_templates, "Circuit Schematic Builder" + ) self.assertIsNotNone(circuit_template) - self.assertEqual(circuit_template.get('category'), 'problem') - self.assertEqual(circuit_template.get('boilerplate_name'), 'circuitschematic.yaml') + self.assertEqual(circuit_template.get("category"), "problem") + self.assertEqual( + circuit_template.get("boilerplate_name"), "circuitschematic.yaml" + ) def test_deprecated_no_advance_component_button(self): """ @@ -2448,32 +2898,38 @@ def test_deprecated_no_advance_component_button(self): Studio support given that they are the only modules in `Advanced Module List` """ # Update poll and survey to have "enabled=False". - XBlockStudioConfiguration.objects.create(name='poll', enabled=False, support_level="fs") - XBlockStudioConfiguration.objects.create(name='survey', enabled=False, support_level="fs") + XBlockStudioConfiguration.objects.create( + name="poll", enabled=False, support_level="fs" + ) + XBlockStudioConfiguration.objects.create( + name="survey", enabled=False, support_level="fs" + ) XBlockStudioConfigurationFlag.objects.create(enabled=True) - self.course.advanced_modules.extend(['poll', 'survey']) + self.course.advanced_modules.extend(["poll", "survey"]) templates = get_component_templates(self.course) - button_names = [template['display_name'] for template in templates] - self.assertNotIn('Advanced', button_names) + button_names = [template["display_name"] for template in templates] + self.assertNotIn("Advanced", button_names) def test_cannot_create_deprecated_problems(self): """ Test that xblocks that have Studio support disabled do not show on the "new component" menu. """ # Update poll to have "enabled=False". - XBlockStudioConfiguration.objects.create(name='poll', enabled=False, support_level="fs") + XBlockStudioConfiguration.objects.create( + name="poll", enabled=False, support_level="fs" + ) XBlockStudioConfigurationFlag.objects.create(enabled=True) - self.course.advanced_modules.extend(['annotatable', 'poll', 'survey']) + self.course.advanced_modules.extend(["annotatable", "poll", "survey"]) # Annotatable doesn't show up because it is unsupported (in test setUp). - self._verify_advanced_xblocks(['Survey'], ['ps']) + self._verify_advanced_xblocks(["Survey"], ["ps"]) # Now enable unsupported components. self.course.allow_unsupported_xblocks = True - self._verify_advanced_xblocks(['Annotation', 'Survey'], ['us', 'ps']) + self._verify_advanced_xblocks(["Annotation", "Survey"], ["us", "ps"]) # Now disable Annotatable completely through XBlockConfiguration - XBlockConfiguration.objects.create(name='annotatable', enabled=False) - self._verify_advanced_xblocks(['Survey'], ['ps']) + XBlockConfiguration.objects.create(name="annotatable", enabled=False) + self._verify_advanced_xblocks(["Survey"], ["ps"]) def test_create_support_level_flag_off(self): """ @@ -2481,38 +2937,39 @@ def test_create_support_level_flag_off(self): XBlockConfiguration) if XBlockStudioConfigurationFlag is False. """ XBlockStudioConfigurationFlag.objects.create(enabled=False) - self.course.advanced_modules.extend(['annotatable', 'survey']) - self._verify_advanced_xblocks(['Annotation', 'Survey'], [True, True]) + self.course.advanced_modules.extend(["annotatable", "survey"]) + self._verify_advanced_xblocks(["Annotation", "Survey"], [True, True]) def test_xblock_masquerading_as_problem(self): """ Test the integration of xblocks masquerading as problems. """ + def get_xblock_problem(label): """ Helper method to get the template of any XBlock in the problems list """ self.templates = get_component_templates(self.course) - problem_templates = self.get_templates_of_type('problem') + problem_templates = self.get_templates_of_type("problem") return self.get_template(problem_templates, label) def verify_staffgradedxblock_present(support_level): """ Helper method to verify that staffgradedxblock template is present """ - sgp = get_xblock_problem('Staff Graded Points') + sgp = get_xblock_problem("Staff Graded Points") self.assertIsNotNone(sgp) - self.assertEqual(sgp.get('category'), 'staffgradedxblock') - self.assertEqual(sgp.get('support_level'), support_level) + self.assertEqual(sgp.get("category"), "staffgradedxblock") + self.assertEqual(sgp.get("support_level"), support_level) def verify_dndv2_present(support_level): """ Helper method to verify that DnDv2 template is present """ - dndv2 = get_xblock_problem('Drag and Drop') + dndv2 = get_xblock_problem("Drag and Drop") self.assertIsNotNone(dndv2) - self.assertEqual(dndv2.get('category'), 'drag-and-drop-v2') - self.assertEqual(dndv2.get('support_level'), support_level) + self.assertEqual(dndv2.get("category"), "drag-and-drop-v2") + self.assertEqual(dndv2.get("support_level"), support_level) verify_dndv2_present(True) verify_staffgradedxblock_present(True) @@ -2520,27 +2977,27 @@ def verify_dndv2_present(support_level): # Now enable XBlockStudioConfigurationFlag. The staffgradedxblock block is marked # unsupported, so will no longer show up, but DnDv2 will continue to appear. XBlockStudioConfigurationFlag.objects.create(enabled=True) - self.assertIsNone(get_xblock_problem('Staff Graded Points')) - self.assertIsNotNone(get_xblock_problem('Drag and Drop')) + self.assertIsNone(get_xblock_problem("Staff Graded Points")) + self.assertIsNotNone(get_xblock_problem("Drag and Drop")) # Now allow unsupported components. self.course.allow_unsupported_xblocks = True - verify_staffgradedxblock_present('us') - verify_dndv2_present('fs') + verify_staffgradedxblock_present("us") + verify_dndv2_present("fs") # Now disable the blocks completely through XBlockConfiguration - XBlockConfiguration.objects.create(name='staffgradedxblock', enabled=False) - XBlockConfiguration.objects.create(name='drag-and-drop-v2', enabled=False) - self.assertIsNone(get_xblock_problem('Staff Graded Points')) - self.assertIsNone(get_xblock_problem('Drag and Drop')) + XBlockConfiguration.objects.create(name="staffgradedxblock", enabled=False) + XBlockConfiguration.objects.create(name="drag-and-drop-v2", enabled=False) + self.assertIsNone(get_xblock_problem("Staff Graded Points")) + self.assertIsNone(get_xblock_problem("Drag and Drop")) def test_discussion_button_present_no_provider(self): """ Test the Discussion button present when no discussion provider configured for course """ templates = get_component_templates(self.course) - button_names = [template['display_name'] for template in templates] - assert 'Discussion' in button_names + button_names = [template["display_name"] for template in templates] + assert "Discussion" in button_names def test_discussion_button_present_legacy_provider(self): """ @@ -2549,11 +3006,13 @@ def test_discussion_button_present_legacy_provider(self): course_key = self.course.location.course_key # Create a discussion configuration with discussion provider set as legacy - DiscussionsConfiguration.objects.create(context_key=course_key, enabled=True, provider_type='legacy') + DiscussionsConfiguration.objects.create( + context_key=course_key, enabled=True, provider_type="legacy" + ) templates = get_component_templates(self.course) - button_names = [template['display_name'] for template in templates] - assert 'Discussion' in button_names + button_names = [template["display_name"] for template in templates] + assert "Discussion" in button_names def test_discussion_button_absent_non_legacy_provider(self): """ @@ -2562,33 +3021,41 @@ def test_discussion_button_absent_non_legacy_provider(self): course_key = self.course.location.course_key # Create a discussion configuration with discussion provider set as legacy - DiscussionsConfiguration.objects.create(context_key=course_key, enabled=False, provider_type='ed-discuss') + DiscussionsConfiguration.objects.create( + context_key=course_key, enabled=False, provider_type="ed-discuss" + ) templates = get_component_templates(self.course) - button_names = [template['display_name'] for template in templates] - assert 'Discussion' not in button_names + button_names = [template["display_name"] for template in templates] + assert "Discussion" not in button_names def _verify_advanced_xblocks(self, expected_xblocks, expected_support_levels): """ Verify the names of the advanced xblocks showing in the "new component" menu. """ templates = get_component_templates(self.course) - button_names = [template['display_name'] for template in templates] - self.assertIn('Advanced', button_names) - self.assertEqual(len(templates[0]['templates']), len(expected_xblocks)) - template_display_names = [template['display_name'] for template in templates[0]['templates']] + button_names = [template["display_name"] for template in templates] + self.assertIn("Advanced", button_names) + self.assertEqual(len(templates[0]["templates"]), len(expected_xblocks)) + template_display_names = [ + template["display_name"] for template in templates[0]["templates"] + ] self.assertEqual(template_display_names, expected_xblocks) - template_support_levels = [template['support_level'] for template in templates[0]['templates']] + template_support_levels = [ + template["support_level"] for template in templates[0]["templates"] + ] self.assertEqual(template_support_levels, expected_support_levels) - def _verify_basic_component(self, component_type, display_name, support_level=True, no_of_templates=1): + def _verify_basic_component( + self, component_type, display_name, support_level=True, no_of_templates=1 + ): """ Verify the display name and support level of basic components (that have no boilerplates). """ templates = self.get_templates_of_type(component_type) self.assertEqual(no_of_templates, len(templates)) - self.assertEqual(display_name, templates[0]['display_name']) - self.assertEqual(support_level, templates[0]['support_level']) + self.assertEqual(display_name, templates[0]["display_name"]) + self.assertEqual(support_level, templates[0]["support_level"]) def _verify_basic_component_display_name(self, component_type, display_name): """ @@ -2603,27 +3070,40 @@ class TestXBlockInfo(ItemTest): """ Unit tests for XBlock's outline handling. """ + def setUp(self): super().setUp() user_id = self.user.id self.chapter = BlockFactory.create( - parent_location=self.course.location, category='chapter', display_name="Week 1", user_id=user_id, - highlights=['highlight'], + parent_location=self.course.location, + category="chapter", + display_name="Week 1", + user_id=user_id, + highlights=["highlight"], ) self.sequential = BlockFactory.create( - parent_location=self.chapter.location, category='sequential', display_name="Lesson 1", user_id=user_id + parent_location=self.chapter.location, + category="sequential", + display_name="Lesson 1", + user_id=user_id, ) self.vertical = BlockFactory.create( - parent_location=self.sequential.location, category='vertical', display_name='Unit 1', user_id=user_id + parent_location=self.sequential.location, + category="vertical", + display_name="Unit 1", + user_id=user_id, ) self.video = BlockFactory.create( - parent_location=self.vertical.location, category='video', display_name='My Video', user_id=user_id + parent_location=self.vertical.location, + category="video", + display_name="My Video", + user_id=user_id, ) def test_json_responses(self): - outline_url = reverse_usage_url('xblock_outline_handler', self.usage_key) - resp = self.client.get(outline_url, HTTP_ACCEPT='application/json') - json_response = json.loads(resp.content.decode('utf-8')) + outline_url = reverse_usage_url("xblock_outline_handler", self.usage_key) + resp = self.client.get(outline_url, HTTP_ACCEPT="application/json") + json_response = json.loads(resp.content.decode("utf-8")) self.validate_course_xblock_info(json_response, course_outline=True) @ddt.data( @@ -2631,31 +3111,42 @@ def test_json_responses(self): (ModuleStoreEnum.Type.mongo, 8, 12), ) @ddt.unpack - def test_xblock_outline_handler_mongo_calls(self, store_type, chapter_queries, chapter_queries_1): + def test_xblock_outline_handler_mongo_calls( + self, store_type, chapter_queries, chapter_queries_1 + ): with self.store.default_store(store_type): course = CourseFactory.create() chapter = BlockFactory.create( - parent_location=course.location, category='chapter', display_name='Week 1' + parent_location=course.location, + category="chapter", + display_name="Week 1", ) - outline_url = reverse_usage_url('xblock_outline_handler', chapter.location) + outline_url = reverse_usage_url("xblock_outline_handler", chapter.location) with check_mongo_calls(chapter_queries): - self.client.get(outline_url, HTTP_ACCEPT='application/json') + self.client.get(outline_url, HTTP_ACCEPT="application/json") sequential = BlockFactory.create( - parent_location=chapter.location, category='sequential', display_name='Sequential 1' + parent_location=chapter.location, + category="sequential", + display_name="Sequential 1", ) BlockFactory.create( - parent_location=sequential.location, category='vertical', display_name='Vertical 1' + parent_location=sequential.location, + category="vertical", + display_name="Vertical 1", ) # calls should be same after adding two new children for split only. with check_mongo_calls(chapter_queries_1): - self.client.get(outline_url, HTTP_ACCEPT='application/json') + self.client.get(outline_url, HTTP_ACCEPT="application/json") def test_entrance_exam_chapter_xblock_info(self): chapter = BlockFactory.create( - parent_location=self.course.location, category='chapter', display_name="Entrance Exam", - user_id=self.user.id, is_entrance_exam=True + parent_location=self.course.location, + category="chapter", + display_name="Entrance Exam", + user_id=self.user.id, + is_entrance_exam=True, ) chapter = modulestore().get_item(chapter.location) xblock_info = create_xblock_info( @@ -2664,17 +3155,19 @@ def test_entrance_exam_chapter_xblock_info(self): include_children_predicate=ALWAYS, ) # entrance exam chapter should not be deletable, draggable and childAddable. - actions = xblock_info['actions'] - self.assertEqual(actions['deletable'], False) - self.assertEqual(actions['draggable'], False) - self.assertEqual(actions['childAddable'], False) - self.assertEqual(xblock_info['display_name'], 'Entrance Exam') - self.assertIsNone(xblock_info.get('is_header_visible', None)) + actions = xblock_info["actions"] + self.assertEqual(actions["deletable"], False) + self.assertEqual(actions["draggable"], False) + self.assertEqual(actions["childAddable"], False) + self.assertEqual(xblock_info["display_name"], "Entrance Exam") + self.assertIsNone(xblock_info.get("is_header_visible", None)) def test_none_entrance_exam_chapter_xblock_info(self): chapter = BlockFactory.create( - parent_location=self.course.location, category='chapter', display_name="Test Chapter", - user_id=self.user.id + parent_location=self.course.location, + category="chapter", + display_name="Test Chapter", + user_id=self.user.id, ) chapter = modulestore().get_item(chapter.location) xblock_info = create_xblock_info( @@ -2684,47 +3177,54 @@ def test_none_entrance_exam_chapter_xblock_info(self): ) # chapter should be deletable, draggable and childAddable if not an entrance exam. - actions = xblock_info['actions'] - self.assertEqual(actions['deletable'], True) - self.assertEqual(actions['draggable'], True) - self.assertEqual(actions['childAddable'], True) + actions = xblock_info["actions"] + self.assertEqual(actions["deletable"], True) + self.assertEqual(actions["draggable"], True) + self.assertEqual(actions["childAddable"], True) # chapter xblock info should not contains the key of 'is_header_visible'. - self.assertIsNone(xblock_info.get('is_header_visible', None)) + self.assertIsNone(xblock_info.get("is_header_visible", None)) def test_entrance_exam_sequential_xblock_info(self): chapter = BlockFactory.create( - parent_location=self.course.location, category='chapter', display_name="Entrance Exam", - user_id=self.user.id, is_entrance_exam=True, in_entrance_exam=True + parent_location=self.course.location, + category="chapter", + display_name="Entrance Exam", + user_id=self.user.id, + is_entrance_exam=True, + in_entrance_exam=True, ) subsection = BlockFactory.create( - parent_location=chapter.location, category='sequential', display_name="Subsection - Entrance Exam", - user_id=self.user.id, in_entrance_exam=True + parent_location=chapter.location, + category="sequential", + display_name="Subsection - Entrance Exam", + user_id=self.user.id, + in_entrance_exam=True, ) subsection = modulestore().get_item(subsection.location) xblock_info = create_xblock_info( - subsection, - include_child_info=True, - include_children_predicate=ALWAYS + subsection, include_child_info=True, include_children_predicate=ALWAYS ) # in case of entrance exam subsection, header should be hidden. - self.assertEqual(xblock_info['is_header_visible'], False) - self.assertEqual(xblock_info['display_name'], 'Subsection - Entrance Exam') + self.assertEqual(xblock_info["is_header_visible"], False) + self.assertEqual(xblock_info["display_name"], "Subsection - Entrance Exam") def test_none_entrance_exam_sequential_xblock_info(self): subsection = BlockFactory.create( - parent_location=self.chapter.location, category='sequential', display_name="Subsection - Exam", - user_id=self.user.id + parent_location=self.chapter.location, + category="sequential", + display_name="Subsection - Exam", + user_id=self.user.id, ) subsection = modulestore().get_item(subsection.location) xblock_info = create_xblock_info( subsection, include_child_info=True, include_children_predicate=ALWAYS, - parent_xblock=self.chapter + parent_xblock=self.chapter, ) # sequential xblock info should not contains the key of 'is_header_visible'. - self.assertIsNone(xblock_info.get('is_header_visible', None)) + self.assertIsNone(xblock_info.get("is_header_visible", None)) def test_chapter_xblock_info(self): chapter = modulestore().get_item(self.chapter.location) @@ -2752,7 +3252,7 @@ def test_vertical_xblock_info(self): include_child_info=True, include_children_predicate=ALWAYS, include_ancestor_info=True, - user=self.user + user=self.user, ) add_container_page_publishing_info(vertical, xblock_info) self.validate_vertical_xblock_info(xblock_info) @@ -2760,9 +3260,7 @@ def test_vertical_xblock_info(self): def test_component_xblock_info(self): video = modulestore().get_item(self.video.location) xblock_info = create_xblock_info( - video, - include_child_info=True, - include_children_predicate=ALWAYS + video, include_child_info=True, include_children_predicate=ALWAYS ) self.validate_component_xblock_info(xblock_info) @@ -2774,7 +3272,9 @@ def test_validate_start_date(self, store_type): with self.store.default_store(store_type): course = CourseFactory.create() chapter = BlockFactory.create( - parent_location=course.location, category='chapter', display_name='Week 1' + parent_location=course.location, + category="chapter", + display_name="Week 1", ) chapter.start = datetime(year=1899, month=1, day=1, tzinfo=UTC) @@ -2784,178 +3284,210 @@ def test_validate_start_date(self, store_type): include_child_info=True, include_children_predicate=ALWAYS, include_ancestor_info=True, - user=self.user + user=self.user, ) - self.assertEqual(xblock_info['start'], DEFAULT_START_DATE.strftime('%Y-%m-%dT%H:%M:%SZ')) + self.assertEqual( + xblock_info["start"], DEFAULT_START_DATE.strftime("%Y-%m-%dT%H:%M:%SZ") + ) def test_highlights_enabled(self): self.course.highlights_enabled_for_messaging = True self.store.update_item(self.course, None) course_xblock_info = create_xblock_info(self.course) - self.assertTrue(course_xblock_info['highlights_enabled_for_messaging']) + self.assertTrue(course_xblock_info["highlights_enabled_for_messaging"]) def test_xblock_public_video_sharing_enabled(self): """ Public video sharing is included in the xblock info when enable. """ - self.course.video_sharing_options = 'all-on' - with patch.object(PUBLIC_VIDEO_SHARE, 'is_enabled', return_value=True): + self.course.video_sharing_options = "all-on" + with patch.object(PUBLIC_VIDEO_SHARE, "is_enabled", return_value=True): self.store.update_item(self.course, None) course_xblock_info = create_xblock_info(self.course) - self.assertTrue(course_xblock_info['video_sharing_enabled']) - self.assertEqual(course_xblock_info['video_sharing_options'], 'all-on') + self.assertTrue(course_xblock_info["video_sharing_enabled"]) + self.assertEqual(course_xblock_info["video_sharing_options"], "all-on") def test_xblock_public_video_sharing_disabled(self): """ Public video sharing not is included in the xblock info when disabled. """ - self.course.video_sharing_options = 'arbitrary' - with patch.object(PUBLIC_VIDEO_SHARE, 'is_enabled', return_value=False): + self.course.video_sharing_options = "arbitrary" + with patch.object(PUBLIC_VIDEO_SHARE, "is_enabled", return_value=False): self.store.update_item(self.course, None) course_xblock_info = create_xblock_info(self.course) - self.assertNotIn('video_sharing_enabled', course_xblock_info) - self.assertNotIn('video_sharing_options', course_xblock_info) + self.assertNotIn("video_sharing_enabled", course_xblock_info) + self.assertNotIn("video_sharing_options", course_xblock_info) - def validate_course_xblock_info(self, xblock_info, has_child_info=True, course_outline=False): + def validate_course_xblock_info( + self, xblock_info, has_child_info=True, course_outline=False + ): """ Validate that the xblock info is correct for the test course. """ - self.assertEqual(xblock_info['category'], 'course') - self.assertEqual(xblock_info['id'], str(self.course.location)) - self.assertEqual(xblock_info['display_name'], self.course.display_name) - self.assertTrue(xblock_info['published']) - self.assertFalse(xblock_info['highlights_enabled_for_messaging']) + self.assertEqual(xblock_info["category"], "course") + self.assertEqual(xblock_info["id"], str(self.course.location)) + self.assertEqual(xblock_info["display_name"], self.course.display_name) + self.assertTrue(xblock_info["published"]) + self.assertFalse(xblock_info["highlights_enabled_for_messaging"]) # Finally, validate the entire response for consistency - self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info, course_outline=course_outline) + self.validate_xblock_info_consistency( + xblock_info, has_child_info=has_child_info, course_outline=course_outline + ) def validate_chapter_xblock_info(self, xblock_info, has_child_info=True): """ Validate that the xblock info is correct for the test chapter. """ - self.assertEqual(xblock_info['category'], 'chapter') - self.assertEqual(xblock_info['id'], str(self.chapter.location)) - self.assertEqual(xblock_info['display_name'], 'Week 1') - self.assertTrue(xblock_info['published']) - self.assertIsNone(xblock_info.get('edited_by', None)) - self.assertEqual(xblock_info['course_graders'], ['Homework', 'Lab', 'Midterm Exam', 'Final Exam']) - self.assertEqual(xblock_info['start'], '2030-01-01T00:00:00Z') - self.assertEqual(xblock_info['graded'], False) - self.assertEqual(xblock_info['due'], None) - self.assertEqual(xblock_info['format'], None) - self.assertEqual(xblock_info['highlights'], self.chapter.highlights) - self.assertTrue(xblock_info['highlights_enabled']) + self.assertEqual(xblock_info["category"], "chapter") + self.assertEqual(xblock_info["id"], str(self.chapter.location)) + self.assertEqual(xblock_info["display_name"], "Week 1") + self.assertTrue(xblock_info["published"]) + self.assertIsNone(xblock_info.get("edited_by", None)) + self.assertEqual( + xblock_info["course_graders"], + ["Homework", "Lab", "Midterm Exam", "Final Exam"], + ) + self.assertEqual(xblock_info["start"], "2030-01-01T00:00:00Z") + self.assertEqual(xblock_info["graded"], False) + self.assertEqual(xblock_info["due"], None) + self.assertEqual(xblock_info["format"], None) + self.assertEqual(xblock_info["highlights"], self.chapter.highlights) + self.assertTrue(xblock_info["highlights_enabled"]) # Finally, validate the entire response for consistency - self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info) + self.validate_xblock_info_consistency( + xblock_info, has_child_info=has_child_info + ) def validate_sequential_xblock_info(self, xblock_info, has_child_info=True): """ Validate that the xblock info is correct for the test sequential. """ - self.assertEqual(xblock_info['category'], 'sequential') - self.assertEqual(xblock_info['id'], str(self.sequential.location)) - self.assertEqual(xblock_info['display_name'], 'Lesson 1') - self.assertTrue(xblock_info['published']) - self.assertIsNone(xblock_info.get('edited_by', None)) + self.assertEqual(xblock_info["category"], "sequential") + self.assertEqual(xblock_info["id"], str(self.sequential.location)) + self.assertEqual(xblock_info["display_name"], "Lesson 1") + self.assertTrue(xblock_info["published"]) + self.assertIsNone(xblock_info.get("edited_by", None)) # Finally, validate the entire response for consistency - self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info) + self.validate_xblock_info_consistency( + xblock_info, has_child_info=has_child_info + ) def validate_vertical_xblock_info(self, xblock_info): """ Validate that the xblock info is correct for the test vertical. """ - self.assertEqual(xblock_info['category'], 'vertical') - self.assertEqual(xblock_info['id'], str(self.vertical.location)) - self.assertEqual(xblock_info['display_name'], 'Unit 1') - self.assertTrue(xblock_info['published']) - self.assertEqual(xblock_info['edited_by'], 'testuser') + self.assertEqual(xblock_info["category"], "vertical") + self.assertEqual(xblock_info["id"], str(self.vertical.location)) + self.assertEqual(xblock_info["display_name"], "Unit 1") + self.assertTrue(xblock_info["published"]) + self.assertEqual(xblock_info["edited_by"], "testuser") # Validate that the correct ancestor info has been included - ancestor_info = xblock_info.get('ancestor_info', None) + ancestor_info = xblock_info.get("ancestor_info", None) self.assertIsNotNone(ancestor_info) - ancestors = ancestor_info['ancestors'] + ancestors = ancestor_info["ancestors"] self.assertEqual(len(ancestors), 3) self.validate_sequential_xblock_info(ancestors[0], has_child_info=True) self.validate_chapter_xblock_info(ancestors[1], has_child_info=False) self.validate_course_xblock_info(ancestors[2], has_child_info=False) # Finally, validate the entire response for consistency - self.validate_xblock_info_consistency(xblock_info, has_child_info=True, has_ancestor_info=True) + self.validate_xblock_info_consistency( + xblock_info, has_child_info=True, has_ancestor_info=True + ) def validate_component_xblock_info(self, xblock_info): """ Validate that the xblock info is correct for the test component. """ - self.assertEqual(xblock_info['category'], 'video') - self.assertEqual(xblock_info['id'], str(self.video.location)) - self.assertEqual(xblock_info['display_name'], 'My Video') - self.assertTrue(xblock_info['published']) - self.assertIsNone(xblock_info.get('edited_by', None)) + self.assertEqual(xblock_info["category"], "video") + self.assertEqual(xblock_info["id"], str(self.video.location)) + self.assertEqual(xblock_info["display_name"], "My Video") + self.assertTrue(xblock_info["published"]) + self.assertIsNone(xblock_info.get("edited_by", None)) # Finally, validate the entire response for consistency self.validate_xblock_info_consistency(xblock_info) - def validate_xblock_info_consistency(self, xblock_info, has_ancestor_info=False, has_child_info=False, - course_outline=False): + def validate_xblock_info_consistency( + self, + xblock_info, + has_ancestor_info=False, + has_child_info=False, + course_outline=False, + ): """ Validate that the xblock info is internally consistent. """ - self.assertIsNotNone(xblock_info['display_name']) - self.assertIsNotNone(xblock_info['id']) - self.assertIsNotNone(xblock_info['category']) - self.assertTrue(xblock_info['published']) + self.assertIsNotNone(xblock_info["display_name"]) + self.assertIsNotNone(xblock_info["id"]) + self.assertIsNotNone(xblock_info["category"]) + self.assertTrue(xblock_info["published"]) if has_ancestor_info: - self.assertIsNotNone(xblock_info.get('ancestor_info', None)) - ancestors = xblock_info['ancestor_info']['ancestors'] - for ancestor in xblock_info['ancestor_info']['ancestors']: + self.assertIsNotNone(xblock_info.get("ancestor_info", None)) + ancestors = xblock_info["ancestor_info"]["ancestors"] + for ancestor in xblock_info["ancestor_info"]["ancestors"]: self.validate_xblock_info_consistency( ancestor, - has_child_info=(ancestor == ancestors[0]), # Only the direct ancestor includes children - course_outline=course_outline + has_child_info=( + ancestor == ancestors[0] + ), # Only the direct ancestor includes children + course_outline=course_outline, ) else: - self.assertIsNone(xblock_info.get('ancestor_info', None)) + self.assertIsNone(xblock_info.get("ancestor_info", None)) if has_child_info: - self.assertIsNotNone(xblock_info.get('child_info', None)) - if xblock_info['child_info'].get('children', None): - for child_response in xblock_info['child_info']['children']: + self.assertIsNotNone(xblock_info.get("child_info", None)) + if xblock_info["child_info"].get("children", None): + for child_response in xblock_info["child_info"]["children"]: self.validate_xblock_info_consistency( child_response, - has_child_info=(not child_response.get('child_info', None) is None), - course_outline=course_outline + has_child_info=( + not child_response.get("child_info", None) is None + ), + course_outline=course_outline, ) else: - self.assertIsNone(xblock_info.get('child_info', None)) + self.assertIsNone(xblock_info.get("child_info", None)) -@patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True}) +@patch.dict("django.conf.settings.FEATURES", {"ENABLE_SPECIAL_EXAMS": True}) @ddt.ddt class TestSpecialExamXBlockInfo(ItemTest): """ Unit tests for XBlock outline handling, specific to special exam XBlocks. """ + patch_get_exam_configuration_dashboard_url = patch.object( - item_module, 'get_exam_configuration_dashboard_url', return_value='test_url' + item_module, "get_exam_configuration_dashboard_url", return_value="test_url" ) patch_does_backend_support_onboarding = patch.object( - item_module, 'does_backend_support_onboarding', return_value=True + item_module, "does_backend_support_onboarding", return_value=True ) patch_get_exam_by_content_id_success = patch.object( - item_module, 'get_exam_by_content_id', return_value={'external_id': 'test_external_id'} + item_module, + "get_exam_by_content_id", + return_value={"external_id": "test_external_id"}, ) patch_get_exam_by_content_id_not_found = patch.object( - item_module, 'get_exam_by_content_id', side_effect=ProctoredExamNotFoundException + item_module, + "get_exam_by_content_id", + side_effect=ProctoredExamNotFoundException, ) def setUp(self): super().setUp() user_id = self.user.id self.chapter = BlockFactory.create( - parent_location=self.course.location, category='chapter', display_name="Week 1", user_id=user_id, - highlights=['highlight'], + parent_location=self.course.location, + category="chapter", + display_name="Week 1", + user_id=user_id, + highlights=["highlight"], ) self.course.enable_proctored_exams = True self.course.save() @@ -2969,20 +3501,20 @@ def test_proctoring_is_enabled_for_course(self): include_children_predicate=ALWAYS, ) # exam proctoring should be enabled and time limited. - assert xblock_info['enable_proctored_exams'] + assert xblock_info["enable_proctored_exams"] @patch_get_exam_configuration_dashboard_url @patch_does_backend_support_onboarding @patch_get_exam_by_content_id_success def test_special_exam_xblock_info( - self, - mock_get_exam_by_content_id, - _mock_does_backend_support_onboarding, - mock_get_exam_configuration_dashboard_url, + self, + mock_get_exam_by_content_id, + _mock_does_backend_support_onboarding, + mock_get_exam_configuration_dashboard_url, ): sequential = BlockFactory.create( parent_location=self.chapter.location, - category='sequential', + category="sequential", display_name="Test Lesson 1", user_id=self.user.id, is_proctored_exam=True, @@ -2997,62 +3529,64 @@ def test_special_exam_xblock_info( include_children_predicate=ALWAYS, ) # exam proctoring should be enabled and time limited. - assert xblock_info['is_proctored_exam'] is True - assert xblock_info['was_exam_ever_linked_with_external'] is True - assert xblock_info['is_time_limited'] is True - assert xblock_info['default_time_limit_minutes'] == 100 - assert xblock_info['proctoring_exam_configuration_link'] == 'test_url' - assert xblock_info['supports_onboarding'] is True - assert xblock_info['is_onboarding_exam'] is False - mock_get_exam_configuration_dashboard_url.assert_called_with(self.course.id, xblock_info['id']) + assert xblock_info["is_proctored_exam"] is True + assert xblock_info["was_exam_ever_linked_with_external"] is True + assert xblock_info["is_time_limited"] is True + assert xblock_info["default_time_limit_minutes"] == 100 + assert xblock_info["proctoring_exam_configuration_link"] == "test_url" + assert xblock_info["supports_onboarding"] is True + assert xblock_info["is_onboarding_exam"] is False + mock_get_exam_configuration_dashboard_url.assert_called_with( + self.course.id, xblock_info["id"] + ) @patch_get_exam_configuration_dashboard_url @patch_does_backend_support_onboarding @patch_get_exam_by_content_id_success @ddt.data( - ('test_external_id', True), + ("test_external_id", True), (None, False), ) @ddt.unpack def test_xblock_was_ever_proctortrack_proctored_exam( - self, - external_id, - expected_value, - mock_get_exam_by_content_id, - _mock_does_backend_support_onboarding_patch, - _mock_get_exam_configuration_dashboard_url, + self, + external_id, + expected_value, + mock_get_exam_by_content_id, + _mock_does_backend_support_onboarding_patch, + _mock_get_exam_configuration_dashboard_url, ): sequential = BlockFactory.create( parent_location=self.chapter.location, - category='sequential', + category="sequential", display_name="Test Lesson 1", user_id=self.user.id, is_proctored_exam=False, is_time_limited=False, is_onboarding_exam=False, ) - mock_get_exam_by_content_id.return_value = {'external_id': external_id} + mock_get_exam_by_content_id.return_value = {"external_id": external_id} sequential = modulestore().get_item(sequential.location) xblock_info = create_xblock_info( sequential, include_child_info=True, include_children_predicate=ALWAYS, ) - assert xblock_info['was_exam_ever_linked_with_external'] is expected_value + assert xblock_info["was_exam_ever_linked_with_external"] is expected_value assert mock_get_exam_by_content_id.call_count == 1 @patch_get_exam_configuration_dashboard_url @patch_does_backend_support_onboarding @patch_get_exam_by_content_id_not_found def test_xblock_was_never_proctortrack_proctored_exam( - self, - mock_get_exam_by_content_id, - _mock_does_backend_support_onboarding_patch, - _mock_get_exam_configuration_dashboard_url, + self, + mock_get_exam_by_content_id, + _mock_does_backend_support_onboarding_patch, + _mock_get_exam_configuration_dashboard_url, ): sequential = BlockFactory.create( parent_location=self.chapter.location, - category='sequential', + category="sequential", display_name="Test Lesson 1", user_id=self.user.id, is_proctored_exam=False, @@ -3065,7 +3599,7 @@ def test_xblock_was_never_proctortrack_proctored_exam( include_child_info=True, include_children_predicate=ALWAYS, ) - assert xblock_info['was_exam_ever_linked_with_external'] is False + assert xblock_info["was_exam_ever_linked_with_external"] is False assert mock_get_exam_by_content_id.call_count == 1 @@ -3079,44 +3613,55 @@ def setUp(self): user_id = self.user.id self.library = LibraryFactory.create() self.top_level_html = BlockFactory.create( - parent_location=self.library.location, category='html', user_id=user_id, publish_item=False + parent_location=self.library.location, + category="html", + user_id=user_id, + publish_item=False, ) self.vertical = BlockFactory.create( - parent_location=self.library.location, category='vertical', user_id=user_id, publish_item=False + parent_location=self.library.location, + category="vertical", + user_id=user_id, + publish_item=False, ) self.child_html = BlockFactory.create( - parent_location=self.vertical.location, category='html', display_name='Test HTML Child Block', - user_id=user_id, publish_item=False + parent_location=self.vertical.location, + category="html", + display_name="Test HTML Child Block", + user_id=user_id, + publish_item=False, ) def test_lib_xblock_info(self): html_block = modulestore().get_item(self.top_level_html.location) xblock_info = create_xblock_info(html_block) self.validate_component_xblock_info(xblock_info, html_block) - self.assertIsNone(xblock_info.get('child_info', None)) + self.assertIsNone(xblock_info.get("child_info", None)) def test_lib_child_xblock_info(self): html_block = modulestore().get_item(self.child_html.location) - xblock_info = create_xblock_info(html_block, include_ancestor_info=True, include_child_info=True) + xblock_info = create_xblock_info( + html_block, include_ancestor_info=True, include_child_info=True + ) self.validate_component_xblock_info(xblock_info, html_block) - self.assertIsNone(xblock_info.get('child_info', None)) - ancestors = xblock_info['ancestor_info']['ancestors'] + self.assertIsNone(xblock_info.get("child_info", None)) + ancestors = xblock_info["ancestor_info"]["ancestors"] self.assertEqual(len(ancestors), 2) - self.assertEqual(ancestors[0]['category'], 'vertical') - self.assertEqual(ancestors[0]['id'], str(self.vertical.location)) - self.assertEqual(ancestors[1]['category'], 'library') + self.assertEqual(ancestors[0]["category"], "vertical") + self.assertEqual(ancestors[0]["id"], str(self.vertical.location)) + self.assertEqual(ancestors[1]["category"], "library") def validate_component_xblock_info(self, xblock_info, original_block): """ Validate that the xblock info is correct for the test component. """ - self.assertEqual(xblock_info['category'], original_block.category) - self.assertEqual(xblock_info['id'], str(original_block.location)) - self.assertEqual(xblock_info['display_name'], original_block.display_name) - self.assertIsNone(xblock_info.get('has_changes', None)) - self.assertIsNone(xblock_info.get('published', None)) - self.assertIsNone(xblock_info.get('published_on', None)) - self.assertIsNone(xblock_info.get('graders', None)) + self.assertEqual(xblock_info["category"], original_block.category) + self.assertEqual(xblock_info["id"], str(original_block.location)) + self.assertEqual(xblock_info["display_name"], original_block.display_name) + self.assertIsNone(xblock_info.get("has_changes", None)) + self.assertIsNone(xblock_info.get("published", None)) + self.assertIsNone(xblock_info.get("published_on", None)) + self.assertIsNone(xblock_info.get("graders", None)) class TestLibraryXBlockCreation(ItemTest): @@ -3129,27 +3674,33 @@ def test_add_xblock(self): Verify we can add an XBlock to a Library. """ lib = LibraryFactory.create() - self.create_xblock(parent_usage_key=lib.location, display_name='Test', category="html") + self.create_xblock( + parent_usage_key=lib.location, display_name="Test", category="html" + ) lib = self.store.get_library(lib.location.library_key) self.assertTrue(lib.children) xblock_locator = lib.children[0] - self.assertEqual(self.store.get_item(xblock_locator).display_name, 'Test') + self.assertEqual(self.store.get_item(xblock_locator).display_name, "Test") def test_no_add_discussion(self): """ Verify we cannot add a discussion block to a Library. """ lib = LibraryFactory.create() - response = self.create_xblock(parent_usage_key=lib.location, display_name='Test', category='discussion') + response = self.create_xblock( + parent_usage_key=lib.location, display_name="Test", category="discussion" + ) self.assertEqual(response.status_code, 400) lib = self.store.get_library(lib.location.library_key) self.assertFalse(lib.children) def test_no_add_advanced(self): lib = LibraryFactory.create() - lib.advanced_modules = ['lti'] + lib.advanced_modules = ["lti"] lib.save() - response = self.create_xblock(parent_usage_key=lib.location, display_name='Test', category='lti') + response = self.create_xblock( + parent_usage_key=lib.location, display_name="Test", category="lti" + ) self.assertEqual(response.status_code, 400) lib = self.store.get_library(lib.location.library_key) self.assertFalse(lib.children) @@ -3160,17 +3711,23 @@ class TestXBlockPublishingInfo(ItemTest): """ Unit tests for XBlock's outline handling. """ + FIRST_SUBSECTION_PATH = [0] FIRST_UNIT_PATH = [0, 0] SECOND_UNIT_PATH = [0, 1] - def _create_child(self, parent, category, display_name, publish_item=False, staff_only=False): + def _create_child( + self, parent, category, display_name, publish_item=False, staff_only=False + ): """ Creates a child xblock for the given parent. """ child = BlockFactory.create( - parent_location=parent.location, category=category, display_name=display_name, - user_id=self.user.id, publish_item=publish_item + parent_location=parent.location, + category=category, + display_name=display_name, + user_id=self.user.id, + publish_item=publish_item, ) if staff_only: self._enable_staff_only(child.location) @@ -3181,7 +3738,7 @@ def _get_child_xblock_info(self, xblock_info, index): """ Returns the child xblock info at the specified index. """ - children = xblock_info['child_info']['children'] + children = xblock_info["child_info"]["children"] self.assertGreater(len(children), index) return children[index] @@ -3203,7 +3760,7 @@ def _get_xblock_outline_info(self, location): modulestore().get_item(location), include_child_info=True, include_children_predicate=ALWAYS, - course_outline=True + course_outline=True, ) def _set_release_date(self, location, start): @@ -3230,7 +3787,14 @@ def _set_display_name(self, location, display_name): xblock.display_name = display_name self.store.update_item(xblock, self.user.id) - def _verify_xblock_info_state(self, xblock_info, xblock_info_field, expected_state, path=None, should_equal=True): + def _verify_xblock_info_state( + self, + xblock_info, + xblock_info_field, + expected_state, + path=None, + should_equal=True, + ): """ Verify the state of an xblock_info field. If no path is provided then the root item will be verified. If should_equal is True, assert that the current state matches the expected state, otherwise assert that they @@ -3239,8 +3803,13 @@ def _verify_xblock_info_state(self, xblock_info, xblock_info_field, expected_sta if path: direct_child_xblock_info = self._get_child_xblock_info(xblock_info, path[0]) remaining_path = path[1:] if len(path) > 1 else None - self._verify_xblock_info_state(direct_child_xblock_info, xblock_info_field, - expected_state, remaining_path, should_equal) + self._verify_xblock_info_state( + direct_child_xblock_info, + xblock_info_field, + expected_state, + remaining_path, + should_equal, + ) else: if should_equal: self.assertEqual(xblock_info[xblock_info_field], expected_state) @@ -3251,22 +3820,32 @@ def _verify_has_staff_only_message(self, xblock_info, expected_state, path=None) """ Verify the staff_only_message field of xblock_info. """ - self._verify_xblock_info_state(xblock_info, 'staff_only_message', expected_state, path) + self._verify_xblock_info_state( + xblock_info, "staff_only_message", expected_state, path + ) - def _verify_visibility_state(self, xblock_info, expected_state, path=None, should_equal=True): + def _verify_visibility_state( + self, xblock_info, expected_state, path=None, should_equal=True + ): """ Verify the publish state of an item in the xblock_info. """ - self._verify_xblock_info_state(xblock_info, 'visibility_state', expected_state, path, should_equal) + self._verify_xblock_info_state( + xblock_info, "visibility_state", expected_state, path, should_equal + ) - def _verify_explicit_staff_lock_state(self, xblock_info, expected_state, path=None, should_equal=True): + def _verify_explicit_staff_lock_state( + self, xblock_info, expected_state, path=None, should_equal=True + ): """ Verify the explicit staff lock state of an item in the xblock_info. """ - self._verify_xblock_info_state(xblock_info, 'has_explicit_staff_lock', expected_state, path, should_equal) + self._verify_xblock_info_state( + xblock_info, "has_explicit_staff_lock", expected_state, path, should_equal + ) def test_empty_chapter(self): - empty_chapter = self._create_child(self.course, 'chapter', "Empty Chapter") + empty_chapter = self._create_child(self.course, "chapter", "Empty Chapter") xblock_info = self._get_xblock_info(empty_chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.unscheduled) @@ -3275,87 +3854,129 @@ def test_chapter_self_paced_default_start_date(self, store_type): course = CourseFactory.create(default_store=store_type) course.self_paced = True self.store.update_item(course, self.user.id) - chapter = self._create_child(course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - self._create_child(sequential, 'vertical', "Published Unit", publish_item=True) + chapter = self._create_child(course, "chapter", "Test Chapter") + sequential = self._create_child(chapter, "sequential", "Test Sequential") + self._create_child(sequential, "vertical", "Published Unit", publish_item=True) self._set_release_date(chapter.location, DEFAULT_START_DATE) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.live) def test_empty_sequential(self): - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - self._create_child(chapter, 'sequential', "Empty Sequential") + chapter = self._create_child(self.course, "chapter", "Test Chapter") + self._create_child(chapter, "sequential", "Empty Sequential") xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.unscheduled) - self._verify_visibility_state(xblock_info, VisibilityState.unscheduled, path=self.FIRST_SUBSECTION_PATH) + self._verify_visibility_state( + xblock_info, VisibilityState.unscheduled, path=self.FIRST_SUBSECTION_PATH + ) def test_published_unit(self): """ Tests the visibility state of a published unit with release date in the future. """ - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - self._create_child(sequential, 'vertical', "Published Unit", publish_item=True) - self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + sequential = self._create_child(chapter, "sequential", "Test Sequential") + self._create_child(sequential, "vertical", "Published Unit", publish_item=True) + self._create_child(sequential, "vertical", "Staff Only Unit", staff_only=True) self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1)) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.ready) - self._verify_visibility_state(xblock_info, VisibilityState.ready, path=self.FIRST_SUBSECTION_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.ready, path=self.FIRST_UNIT_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) + self._verify_visibility_state( + xblock_info, VisibilityState.ready, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.ready, path=self.FIRST_UNIT_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH + ) def test_released_unit(self): """ Tests the visibility state of a published unit with release date in the past. """ - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - self._create_child(sequential, 'vertical', "Published Unit", publish_item=True) - self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + sequential = self._create_child(chapter, "sequential", "Test Sequential") + self._create_child(sequential, "vertical", "Published Unit", publish_item=True) + self._create_child(sequential, "vertical", "Staff Only Unit", staff_only=True) self._set_release_date(chapter.location, datetime.now(UTC) - timedelta(days=1)) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.live) - self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) + self._verify_visibility_state( + xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH + ) def test_unpublished_changes(self): """ Tests the visibility state of a published unit with draft (unpublished) changes. """ - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - unit = self._create_child(sequential, 'vertical', "Published Unit", publish_item=True) - self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + sequential = self._create_child(chapter, "sequential", "Test Sequential") + unit = self._create_child( + sequential, "vertical", "Published Unit", publish_item=True + ) + self._create_child(sequential, "vertical", "Staff Only Unit", staff_only=True) # Setting the display name creates a draft version of unit. - self._set_display_name(unit.location, 'Updated Unit') + self._set_display_name(unit.location, "Updated Unit") xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.needs_attention) - self._verify_visibility_state(xblock_info, VisibilityState.needs_attention, path=self.FIRST_SUBSECTION_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.needs_attention, path=self.FIRST_UNIT_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) + self._verify_visibility_state( + xblock_info, + VisibilityState.needs_attention, + path=self.FIRST_SUBSECTION_PATH, + ) + self._verify_visibility_state( + xblock_info, VisibilityState.needs_attention, path=self.FIRST_UNIT_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH + ) def test_partially_released_section(self): - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - released_sequential = self._create_child(chapter, 'sequential', "Released Sequential") - self._create_child(released_sequential, 'vertical', "Released Unit", publish_item=True) - self._create_child(released_sequential, 'vertical', "Staff Only Unit", staff_only=True) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + released_sequential = self._create_child( + chapter, "sequential", "Released Sequential" + ) + self._create_child( + released_sequential, "vertical", "Released Unit", publish_item=True + ) + self._create_child( + released_sequential, "vertical", "Staff Only Unit", staff_only=True + ) self._set_release_date(chapter.location, datetime.now(UTC) - timedelta(days=1)) - published_sequential = self._create_child(chapter, 'sequential', "Published Sequential") - self._create_child(published_sequential, 'vertical', "Published Unit", publish_item=True) - self._create_child(published_sequential, 'vertical', "Staff Only Unit", staff_only=True) - self._set_release_date(published_sequential.location, datetime.now(UTC) + timedelta(days=1)) + published_sequential = self._create_child( + chapter, "sequential", "Published Sequential" + ) + self._create_child( + published_sequential, "vertical", "Published Unit", publish_item=True + ) + self._create_child( + published_sequential, "vertical", "Staff Only Unit", staff_only=True + ) + self._set_release_date( + published_sequential.location, datetime.now(UTC) + timedelta(days=1) + ) xblock_info = self._get_xblock_info(chapter.location) # Verify the state of the released sequential self._verify_visibility_state(xblock_info, VisibilityState.live, path=[0]) self._verify_visibility_state(xblock_info, VisibilityState.live, path=[0, 0]) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=[0, 1]) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=[0, 1] + ) # Verify the state of the published sequential self._verify_visibility_state(xblock_info, VisibilityState.ready, path=[1]) self._verify_visibility_state(xblock_info, VisibilityState.ready, path=[1, 0]) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=[1, 1]) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=[1, 1] + ) # Finally verify the state of the chapter self._verify_visibility_state(xblock_info, VisibilityState.ready) @@ -3364,32 +3985,50 @@ def test_staff_only_section(self): """ Tests that an explicitly staff-locked section and all of its children are visible to staff only. """ - chapter = self._create_child(self.course, 'chapter', "Test Chapter", staff_only=True) - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - vertical = self._create_child(sequential, 'vertical', "Unit") + chapter = self._create_child( + self.course, "chapter", "Test Chapter", staff_only=True + ) + sequential = self._create_child(chapter, "sequential", "Test Sequential") + vertical = self._create_child(sequential, "vertical", "Unit") xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.staff_only) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_SUBSECTION_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_UNIT_PATH) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.FIRST_UNIT_PATH + ) self._verify_explicit_staff_lock_state(xblock_info, True) - self._verify_explicit_staff_lock_state(xblock_info, False, path=self.FIRST_SUBSECTION_PATH) - self._verify_explicit_staff_lock_state(xblock_info, False, path=self.FIRST_UNIT_PATH) + self._verify_explicit_staff_lock_state( + xblock_info, False, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_explicit_staff_lock_state( + xblock_info, False, path=self.FIRST_UNIT_PATH + ) vertical_info = self._get_xblock_info(vertical.location) add_container_page_publishing_info(vertical, vertical_info) - self.assertEqual(_xblock_type_and_display_name(chapter), vertical_info["staff_lock_from"]) + self.assertEqual( + _xblock_type_and_display_name(chapter), vertical_info["staff_lock_from"] + ) def test_no_staff_only_section(self): """ Tests that a section with a staff-locked subsection and a visible subsection is not staff locked itself. """ - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - self._create_child(chapter, 'sequential', "Test Visible Sequential") - self._create_child(chapter, 'sequential', "Test Staff Locked Sequential", staff_only=True) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + self._create_child(chapter, "sequential", "Test Visible Sequential") + self._create_child( + chapter, "sequential", "Test Staff Locked Sequential", staff_only=True + ) xblock_info = self._get_xblock_info(chapter.location) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, should_equal=False) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=[0], should_equal=False) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, should_equal=False + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=[0], should_equal=False + ) self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=[1]) def test_staff_only_subsection(self): @@ -3397,101 +4036,160 @@ def test_staff_only_subsection(self): Tests that an explicitly staff-locked subsection and all of its children are visible to staff only. In this case the parent section is also visible to staff only because all of its children are staff only. """ - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential", staff_only=True) - vertical = self._create_child(sequential, 'vertical', "Unit") + chapter = self._create_child(self.course, "chapter", "Test Chapter") + sequential = self._create_child( + chapter, "sequential", "Test Sequential", staff_only=True + ) + vertical = self._create_child(sequential, "vertical", "Unit") xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.staff_only) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_SUBSECTION_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_UNIT_PATH) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.FIRST_UNIT_PATH + ) self._verify_explicit_staff_lock_state(xblock_info, False) - self._verify_explicit_staff_lock_state(xblock_info, True, path=self.FIRST_SUBSECTION_PATH) - self._verify_explicit_staff_lock_state(xblock_info, False, path=self.FIRST_UNIT_PATH) + self._verify_explicit_staff_lock_state( + xblock_info, True, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_explicit_staff_lock_state( + xblock_info, False, path=self.FIRST_UNIT_PATH + ) vertical_info = self._get_xblock_info(vertical.location) add_container_page_publishing_info(vertical, vertical_info) - self.assertEqual(_xblock_type_and_display_name(sequential), vertical_info["staff_lock_from"]) + self.assertEqual( + _xblock_type_and_display_name(sequential), vertical_info["staff_lock_from"] + ) def test_no_staff_only_subsection(self): """ Tests that a subsection with a staff-locked unit and a visible unit is not staff locked itself. """ - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - self._create_child(sequential, 'vertical', "Unit") - self._create_child(sequential, 'vertical', "Locked Unit", staff_only=True) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + sequential = self._create_child(chapter, "sequential", "Test Sequential") + self._create_child(sequential, "vertical", "Unit") + self._create_child(sequential, "vertical", "Locked Unit", staff_only=True) xblock_info = self._get_xblock_info(chapter.location) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, self.FIRST_SUBSECTION_PATH, - should_equal=False) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, self.FIRST_UNIT_PATH, should_equal=False) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, self.SECOND_UNIT_PATH) + self._verify_visibility_state( + xblock_info, + VisibilityState.staff_only, + self.FIRST_SUBSECTION_PATH, + should_equal=False, + ) + self._verify_visibility_state( + xblock_info, + VisibilityState.staff_only, + self.FIRST_UNIT_PATH, + should_equal=False, + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, self.SECOND_UNIT_PATH + ) def test_staff_only_unit(self): - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - vertical = self._create_child(sequential, 'vertical', "Unit", staff_only=True) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + sequential = self._create_child(chapter, "sequential", "Test Sequential") + vertical = self._create_child(sequential, "vertical", "Unit", staff_only=True) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.staff_only) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_SUBSECTION_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_UNIT_PATH) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.FIRST_UNIT_PATH + ) self._verify_explicit_staff_lock_state(xblock_info, False) - self._verify_explicit_staff_lock_state(xblock_info, False, path=self.FIRST_SUBSECTION_PATH) - self._verify_explicit_staff_lock_state(xblock_info, True, path=self.FIRST_UNIT_PATH) + self._verify_explicit_staff_lock_state( + xblock_info, False, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_explicit_staff_lock_state( + xblock_info, True, path=self.FIRST_UNIT_PATH + ) vertical_info = self._get_xblock_info(vertical.location) add_container_page_publishing_info(vertical, vertical_info) - self.assertEqual(_xblock_type_and_display_name(vertical), vertical_info["staff_lock_from"]) + self.assertEqual( + _xblock_type_and_display_name(vertical), vertical_info["staff_lock_from"] + ) def test_unscheduled_section_with_live_subsection(self): - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - self._create_child(sequential, 'vertical', "Published Unit", publish_item=True) - self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) - self._set_release_date(sequential.location, datetime.now(UTC) - timedelta(days=1)) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + sequential = self._create_child(chapter, "sequential", "Test Sequential") + self._create_child(sequential, "vertical", "Published Unit", publish_item=True) + self._create_child(sequential, "vertical", "Staff Only Unit", staff_only=True) + self._set_release_date( + sequential.location, datetime.now(UTC) - timedelta(days=1) + ) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.needs_attention) - self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) + self._verify_visibility_state( + xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH + ) def test_unreleased_section_with_live_subsection(self): - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - self._create_child(sequential, 'vertical', "Published Unit", publish_item=True) - self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + sequential = self._create_child(chapter, "sequential", "Test Sequential") + self._create_child(sequential, "vertical", "Published Unit", publish_item=True) + self._create_child(sequential, "vertical", "Staff Only Unit", staff_only=True) self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1)) - self._set_release_date(sequential.location, datetime.now(UTC) - timedelta(days=1)) + self._set_release_date( + sequential.location, datetime.now(UTC) - timedelta(days=1) + ) xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.needs_attention) - self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH) - self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) + self._verify_visibility_state( + xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH + ) + self._verify_visibility_state( + xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH + ) def test_locked_section_staff_only_message(self): """ Tests that a locked section has a staff only message and its descendants do not. """ - chapter = self._create_child(self.course, 'chapter', "Test Chapter", staff_only=True) - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - self._create_child(sequential, 'vertical', "Unit") + chapter = self._create_child( + self.course, "chapter", "Test Chapter", staff_only=True + ) + sequential = self._create_child(chapter, "sequential", "Test Sequential") + self._create_child(sequential, "vertical", "Unit") xblock_info = self._get_xblock_outline_info(chapter.location) self._verify_has_staff_only_message(xblock_info, True) - self._verify_has_staff_only_message(xblock_info, False, path=self.FIRST_SUBSECTION_PATH) - self._verify_has_staff_only_message(xblock_info, False, path=self.FIRST_UNIT_PATH) + self._verify_has_staff_only_message( + xblock_info, False, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_has_staff_only_message( + xblock_info, False, path=self.FIRST_UNIT_PATH + ) def test_locked_unit_staff_only_message(self): """ Tests that a lone locked unit has a staff only message along with its ancestors. """ - chapter = self._create_child(self.course, 'chapter', "Test Chapter") - sequential = self._create_child(chapter, 'sequential', "Test Sequential") - self._create_child(sequential, 'vertical', "Unit", staff_only=True) + chapter = self._create_child(self.course, "chapter", "Test Chapter") + sequential = self._create_child(chapter, "sequential", "Test Sequential") + self._create_child(sequential, "vertical", "Unit", staff_only=True) xblock_info = self._get_xblock_outline_info(chapter.location) self._verify_has_staff_only_message(xblock_info, True) - self._verify_has_staff_only_message(xblock_info, True, path=self.FIRST_SUBSECTION_PATH) - self._verify_has_staff_only_message(xblock_info, True, path=self.FIRST_UNIT_PATH) + self._verify_has_staff_only_message( + xblock_info, True, path=self.FIRST_SUBSECTION_PATH + ) + self._verify_has_staff_only_message( + xblock_info, True, path=self.FIRST_UNIT_PATH + ) @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) def test_self_paced_item_visibility_state(self, store_type): @@ -3503,7 +4201,7 @@ def test_self_paced_item_visibility_state(self, store_type): # Create course, chapter and setup future release date to make chapter in scheduled state course = CourseFactory.create(default_store=store_type) - chapter = self._create_child(course, 'chapter', "Test Chapter") + chapter = self._create_child(course, "chapter", "Test Chapter") self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1)) # Check that chapter has scheduled state @@ -3521,8 +4219,10 @@ def test_self_paced_item_visibility_state(self, store_type): self._verify_visibility_state(xblock_info, VisibilityState.live) -@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', - lambda self, block: ['test_aside']) +@patch( + "xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types", + lambda self, block: ["test_aside"], +) class TestUpdateFromSource(ModuleStoreTestCase): """ Test update_from_source. @@ -3535,7 +4235,7 @@ def setUp(self): super().setUp() key_store = DictKeyValueStore() field_data = KvsFieldData(key_store) - self.runtime = TestRuntime(services={'field-data': field_data}) + self.runtime = TestRuntime(services={"field-data": field_data}) def create_source_block(self, course): """ @@ -3543,31 +4243,35 @@ def create_source_block(self, course): """ source_block = BlockFactory( parent=course, - category='course_info', - display_name='Source Block', - metadata={'due': datetime(2010, 11, 22, 4, 0, tzinfo=UTC)}, + category="course_info", + display_name="Source Block", + metadata={"due": datetime(2010, 11, 22, 4, 0, tzinfo=UTC)}, ) - def_id = self.runtime.id_generator.create_definition('html') + def_id = self.runtime.id_generator.create_definition("html") usage_id = self.runtime.id_generator.create_usage(def_id) - aside = AsideTest(scope_ids=ScopeIds('user', 'html', def_id, usage_id), runtime=self.runtime) - aside.field11 = 'html_new_value1' + aside = AsideTest( + scope_ids=ScopeIds("user", "html", def_id, usage_id), runtime=self.runtime + ) + aside.field11 = "html_new_value1" # The data attribute is handled in a special manner and should be updated. - source_block.data = '