def setUp(self): """ Initial data setup """ super(TestSubsectionGating, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments # Enable subsection gating for the test course self.course.enable_subsection_gating = True self.save_course() # create a chapter self.chapter = ItemFactory.create(parent_location=self.course.location, category='chapter', display_name='untitled chapter') # create 2 sequentials self.seq1 = ItemFactory.create(parent_location=self.chapter.location, category='sequential', display_name='untitled sequential 1') self.seq1_url = reverse_usage_url('xblock_handler', self.seq1.location) self.seq2 = ItemFactory.create(parent_location=self.chapter.location, category='sequential', display_name='untitled sequential 2') self.seq2_url = reverse_usage_url('xblock_handler', self.seq2.location)
def test_can_get_usage_info_when_special_characters_are_used(self): """ Test if group configurations json updated successfully when special characters are being used in content experiment """ self._add_user_partitions(count=1) __, split_test, __ = self._create_content_experiment(cid=0, name_suffix='0', special_characters=u"JOSÉ ANDRÉS") actual = GroupConfiguration.get_split_test_partitions_with_usage(self.store, self.course, ) expected = [{ 'id': 0, 'name': 'Name 0', 'scheme': 'random', 'description': 'Description 0', 'version': UserPartition.VERSION, 'groups': [ {'id': 0, 'name': 'Group A', 'version': 1}, {'id': 1, 'name': 'Group B', 'version': 1}, {'id': 2, 'name': 'Group C', 'version': 1}, ], 'usage': [{ 'url': reverse_usage_url("container_handler", split_test.location), 'label': u"Test Unit 0 / Test Content Experiment 0JOSÉ ANDRÉS", 'validation': None, }], 'parameters': {}, 'active': True, }] self.assertEqual(actual, expected)
def _get_usage_dict(course, unit, item, scheme_name=None): """ Get usage info for unit/module. """ parent_unit = get_parent_unit(item) if unit == parent_unit and not item.has_children: # Display the topmost unit page if # the item is a child of the topmost unit and doesn't have its own children. unit_for_url = unit elif (not parent_unit and unit.get_parent()) or (unit == parent_unit and item.has_children): # Display the item's page rather than the unit page if # the item is one level below the topmost unit and has children, or # the item itself *is* the topmost unit (and thus does not have a parent unit, but is not an orphan). unit_for_url = item else: # If the item is nested deeper than two levels (the topmost unit > vertical > ... > item) # display the page for the nested vertical element. parent = item.get_parent() nested_vertical = item while parent != parent_unit: nested_vertical = parent parent = parent.get_parent() unit_for_url = nested_vertical unit_url = reverse_usage_url( 'container_handler', course.location.course_key.make_usage_key(unit_for_url.location.block_type, unit_for_url.location.block_id) ) usage_dict = {'label': u"{} / {}".format(unit.display_name, item.display_name), 'url': unit_url} if scheme_name == RANDOM_SCHEME: validation_summary = item.general_validation_message() usage_dict.update({'validation': validation_summary.to_json() if validation_summary else None}) return usage_dict
def test_preview_conditional_module_children_context( self, mock_is_condition_satisfied): """ Tests that when empty context is pass to children of ConditionalBlock it will not raise KeyError. """ mock_is_condition_satisfied.return_value = True client = Client() client.login(username=self.user.username, password=self.user_password) with self.store.default_store(ModuleStoreEnum.Type.split): course = CourseFactory.create() conditional_block = ItemFactory.create( parent_location=course.location, category="conditional") # child conditional_block ItemFactory.create(parent_location=conditional_block.location, category="conditional") url = reverse_usage_url( 'preview_handler', conditional_block.location, kwargs={'handler': 'xmodule_handler/conditional_get'}) response = client.post(url) self.assertEqual(response.status_code, 200)
def test_post_course_update(self): """ Test that a user can successfully post on course updates and handouts of a course """ self.post_course_update() updates_location = self.course.id.make_usage_key( 'course_info', 'updates') self.assertTrue(isinstance(updates_location, UsageKey)) self.assertEqual(updates_location.block_id, u'updates') # check posting on handouts handouts_location = self.course.id.make_usage_key( 'course_info', 'handouts') course_handouts_url = reverse_usage_url('xblock_handler', handouts_location) content = u"Sample handout" payload = {'data': content} resp = self.client.ajax_post(course_handouts_url, payload) # check that response status is 200 not 500 self.assertEqual(resp.status_code, 200) payload = json.loads(resp.content.decode('utf-8')) self.assertHTMLEqual(payload['data'], content)
def create_export_tarball(course_module, course_key, context, status=None): """ Generates the export tarball, or returns None if there was an error. Updates the context with any error information if applicable. """ name = course_module.url_name export_file = NamedTemporaryFile(prefix=name + '.', suffix=".tar.gz") root_dir = path(mkdtemp()) try: if isinstance(course_key, LibraryLocator): export_library_to_xml(modulestore(), contentstore(), course_key, root_dir, name) else: export_course_to_xml(modulestore(), contentstore(), course_module.id, root_dir, name) if status: status.set_state('Compressing') status.increment_completed_steps() LOGGER.debug('tar file being generated at %s', export_file.name) with tarfile.open(name=export_file.name, mode='w:gz') as tar_file: tar_file.add(root_dir / name, arcname=name) except SerializationError as exc: LOGGER.exception('There was an error exporting %s', course_key, exc_info=True) parent = None try: failed_item = modulestore().get_item(exc.location) parent_loc = modulestore().get_parent_location(failed_item.location) if parent_loc is not None: parent = modulestore().get_item(parent_loc) except: # pylint: disable=bare-except # if we have a nested exception, then we'll show the more generic error message pass context.update({ 'in_err': True, 'raw_err_msg': str(exc), 'edit_unit_url': reverse_usage_url("container_handler", parent.location) if parent else "", }) if status: status.fail(json.dumps({'raw_error_msg': context['raw_err_msg'], 'edit_unit_url': context['edit_unit_url']})) raise except Exception as exc: LOGGER.exception('There was an error exporting %s', course_key, exc_info=True) context.update({ 'in_err': True, 'edit_unit_url': None, 'raw_err_msg': str(exc)}) if status: status.fail(json.dumps({'raw_error_msg': context['raw_err_msg']})) raise finally: if os.path.exists(root_dir / name): shutil.rmtree(root_dir / name) return export_file
def _get_discussion_enabled_status(self, usage_key, client=None): """ Issue a GET request to fetch value of discussion_enabled flag of xblock represented by param:usage_key """ client = client if client is not None else self.client url = reverse_usage_url("xblock_handler", usage_key) resp = client.get(url, HTTP_ACCEPT="application/json") return resp
def _update_item(self, usage_key, metadata): """ Helper method: Uses the REST API to update the fields of an XBlock. This will result in the XBlock's editor_saved() method being called. """ update_url = reverse_usage_url("xblock_handler", usage_key) return self.client.ajax_post(update_url, data={ 'metadata': metadata, })
def test_delete_item_signal_handler_called(self, mock_remove_prereq, mock_set_required): seq3 = ItemFactory.create(parent_location=self.chapter.location, category='sequential', display_name='untitled sequential 3') self.client.delete(reverse_usage_url('xblock_handler', seq3.location)) mock_remove_prereq.assert_called_with(seq3.location) mock_set_required.assert_called_with(seq3.location.course_key, seq3.location, None, None, None)
def make_them_public(self): objs = [self.course, self.chapter, self.sequential] for x in objs: problem_usage_key = x.location #self.response_usage_key(x) problem_update_url = reverse_usage_url("xblock_handler", problem_usage_key) self.client.ajax_post( problem_update_url, data={'publish': 'make_public'} )
def test_handle_requests(self, aside_key_class): """ Checks that handler to save tags in StructuredTagsAside works properly """ handler_url = reverse_usage_url( 'component_handler', six.text_type(aside_key_class(self.problem.location, self.aside_name)), kwargs={'handler': 'save_tags'} ) client = AjaxEnabledTestClient() client.login(username=self.user.username, password=self.user_password) response = client.post(handler_url, json.dumps({}), content_type="application/json") self.assertEqual(response.status_code, 400) response = client.post(handler_url, json.dumps({'undefined_tag': ['undefined1', 'undefined2']}), content_type="application/json") self.assertEqual(response.status_code, 400) response = client.post(handler_url, json.dumps({self.aside_tag_dif: ['undefined1', 'undefined2']}), content_type="application/json") self.assertEqual(response.status_code, 400) def _test_helper_func(problem_location): """ Helper function """ problem = modulestore().get_item(problem_location) asides = problem.runtime.get_asides(problem) tag_aside = None for aside in asides: if isinstance(aside, StructuredTagsAside): tag_aside = aside break return tag_aside response = client.post(handler_url, json.dumps({self.aside_tag_dif: [self.aside_tag_dif_value]}), content_type="application/json") self.assertEqual(response.status_code, 200) tag_aside = _test_helper_func(self.problem.location) self.assertIsNotNone(tag_aside, "Necessary StructuredTagsAside object isn't found") self.assertEqual(tag_aside.saved_tags[self.aside_tag_dif], [self.aside_tag_dif_value]) response = client.post(handler_url, json.dumps({self.aside_tag_dif: [self.aside_tag_dif_value, self.aside_tag_dif_value2]}), content_type="application/json") self.assertEqual(response.status_code, 200) tag_aside = _test_helper_func(self.problem.location) self.assertIsNotNone(tag_aside, "Necessary StructuredTagsAside object isn't found") self.assertEqual(tag_aside.saved_tags[self.aside_tag_dif], [self.aside_tag_dif_value, self.aside_tag_dif_value2])
def _get_settings_html(): """ Helper function to get block settings HTML Used to check the available libraries. """ edit_view_url = reverse_usage_url("xblock_view_handler", lib_block.location, {"view_name": STUDIO_VIEW}) resp = self.client.get_json(edit_view_url) self.assertEqual(resp.status_code, 200) return parse_json(resp)['html']
def test_can_get_correct_usage_info_for_unit(self): """ When group access is set on the unit level, the usage info should return a url to the unit, not the sequential parent of the unit. """ self.course.user_partitions = [ UserPartition( id=0, name='User Partition', scheme=UserPartition.get_scheme('cohort'), description='User Partition', groups=[ Group(id=0, name="Group") ], ), ] vertical, __ = self._create_problem_with_content_group( cid=0, group_id=0, name_suffix='0' ) self.client.ajax_post( reverse_usage_url("xblock_handler", vertical.location), data={'metadata': {'group_access': {0: [0]}}} ) actual = self._get_user_partition('cohort') # order of usage list is arbitrary, sort for reliable comparison actual['groups'][0]['usage'].sort(key=itemgetter('label')) expected = { 'id': 0, 'name': 'User Partition', 'scheme': 'cohort', 'description': 'User Partition', 'version': UserPartition.VERSION, 'groups': [ {'id': 0, 'name': 'Group', 'version': 1, 'usage': [ { 'url': u"/container/{}".format(vertical.location), 'label': u"Test Subsection 0 / Test Unit 0" }, { 'url': u"/container/{}".format(vertical.location), 'label': u"Test Unit 0 / Test Problem 0" } ]}, ], u'parameters': {}, u'active': True, } self.maxDiff = None assert actual == expected
def set_discussion_enabled_status(self, xblock, value, client=None): """ Issue a POST request to update value of discussion_enabled flag of param:xblock's """ client = client if client is not None else self.client xblock_location = xblock.location url = reverse_usage_url("xblock_handler", xblock_location) resp = client.post( url, HTTP_ACCEPT="application/json", data=json.dumps({"metadata": {"discussion_enabled": value}}), content_type="application/json", ) return resp
def _refresh_children(self, lib_content_block, status_code_expected=200): """ Helper method: Uses the REST API to call the 'refresh_children' handler of a LibraryContent block """ if 'user' not in lib_content_block.runtime._services: # pylint: disable=protected-access user_service = DjangoXBlockUserService(self.user) lib_content_block.runtime._services['user'] = user_service # pylint: disable=protected-access handler_url = reverse_usage_url('component_handler', lib_content_block.location, kwargs={'handler': 'refresh_children'}) response = self.client.ajax_post(handler_url) self.assertEqual(response.status_code, status_code_expected) return modulestore().get_item(lib_content_block.location)
def _verify_deprecated_info(self, course_id, advanced_modules, info, deprecated_block_types): """ Verify deprecated info. """ expected_blocks = [] for block_type in deprecated_block_types: expected_blocks.append([ reverse_usage_url('container_handler', self.vertical.location), f'{block_type} Problem' ]) self.assertEqual(info['deprecated_enabled_block_types'], [ component for component in advanced_modules if component in deprecated_block_types ]) self.assertCountEqual(info['blocks'], expected_blocks) self.assertEqual( info['advance_settings_url'], reverse_course_url('advanced_settings_handler', course_id))
def test_block_branch_not_changed_by_preview_handler(self, default_store): """ Tests preview_handler should not update blocks being previewed """ client = Client() client.login(username=self.user.username, password=self.user_password) with self.store.default_store(default_store): course = CourseFactory.create() block = ItemFactory.create(parent_location=course.location, category="problem") url = reverse_usage_url( 'preview_handler', block.location, kwargs={'handler': 'xmodule_handler/problem_check'}) response = client.post(url) self.assertEqual(response.status_code, 200) self.assertFalse(modulestore().has_changes(modulestore().get_item( block.location)))
def _create_problem_with_content_group(self, cid, group_id, name_suffix='', special_characters='', orphan=False): """ Create a problem Assign content group to the problem. """ vertical_parent_location = self.course.location if not orphan: subsection = ItemFactory.create( category='sequential', parent_location=self.course.location, display_name=u"Test Subsection {}".format(name_suffix) ) vertical_parent_location = subsection.location vertical = ItemFactory.create( category='vertical', parent_location=vertical_parent_location, display_name=u"Test Unit {}".format(name_suffix) ) problem = ItemFactory.create( category='problem', parent_location=vertical.location, display_name=u"Test Problem {}{}".format(name_suffix, special_characters) ) group_access_content = {'group_access': {cid: [group_id]}} self.client.ajax_post( reverse_usage_url("xblock_handler", problem.location), data={'metadata': group_access_content} ) if not orphan: self.course.children.append(subsection.location) self.save_course() return vertical, problem
def test_read_only_role(self, use_org_level_role): """ Test the read-only role (LibraryUserRole and its org-level equivalent) """ # As staff user, add a block to self.library: block = self._add_simple_content_block() # Login as a non_staff_user: self._login_as_non_staff_user() self.assertFalse(self._can_access_library(self.library)) block_url = reverse_usage_url('xblock_handler', block.location) def can_read_block(): """ Check if studio lets us view the XBlock in the library """ response = self.client.get_json(block_url) self.assertIn(response.status_code, (200, 403)) # 400 would be ambiguous return response.status_code == 200 def can_edit_block(): """ Check if studio lets us edit the XBlock in the library """ response = self.client.ajax_post(block_url) self.assertIn(response.status_code, (200, 403)) # 400 would be ambiguous return response.status_code == 200 def can_delete_block(): """ Check if studio lets us delete the XBlock in the library """ response = self.client.delete(block_url) self.assertIn(response.status_code, (200, 403)) # 400 would be ambiguous return response.status_code == 200 def can_copy_block(): """ Check if studio lets us duplicate the XBlock in the library """ response = self.client.ajax_post( reverse_url('xblock_handler'), { 'parent_locator': str(self.library.location), 'duplicate_source_locator': str(block.location), }) self.assertIn(response.status_code, (200, 403)) # 400 would be ambiguous return response.status_code == 200 def can_create_block(): """ Check if studio lets us make a new XBlock in the library """ response = self.client.ajax_post( reverse_url('xblock_handler'), { 'parent_locator': str(self.library.location), 'category': 'html', }) self.assertIn(response.status_code, (200, 403)) # 400 would be ambiguous return response.status_code == 200 # Check that we do not have read or write access to block: self.assertFalse(can_read_block()) self.assertFalse(can_edit_block()) self.assertFalse(can_delete_block()) self.assertFalse(can_copy_block()) self.assertFalse(can_create_block()) # Give non_staff_user read-only permission: if use_org_level_role: OrgLibraryUserRole(self.lib_key.org).add_users(self.non_staff_user) else: LibraryUserRole(self.lib_key).add_users(self.non_staff_user) self.assertTrue(self._can_access_library(self.library)) self.assertTrue(can_read_block()) self.assertFalse(can_edit_block()) self.assertFalse(can_delete_block()) self.assertFalse(can_copy_block()) self.assertFalse(can_create_block())
def _create_content_experiment(self, cid=-1, group_id=None, cid_for_problem=None, name_suffix='', special_characters=''): """ Create content experiment. Assign Group Configuration to the experiment if cid is provided. Assigns a problem to the first group in the split test if group_id and cid_for_problem is provided. """ sequential = ItemFactory.create( category='sequential', parent_location=self.course.location, display_name=u'Test Subsection {}'.format(name_suffix) ) vertical = ItemFactory.create( category='vertical', parent_location=sequential.location, display_name=u'Test Unit {}'.format(name_suffix) ) c0_url = self.course.id.make_usage_key("vertical", "split_test_cond0") c1_url = self.course.id.make_usage_key("vertical", "split_test_cond1") c2_url = self.course.id.make_usage_key("vertical", "split_test_cond2") split_test = ItemFactory.create( category='split_test', parent_location=vertical.location, user_partition_id=cid, display_name=u"Test Content Experiment {}{}".format(name_suffix, special_characters), group_id_to_child={"0": c0_url, "1": c1_url, "2": c2_url} ) ItemFactory.create( parent_location=split_test.location, category="vertical", display_name="Condition 0 vertical", location=c0_url, ) c1_vertical = ItemFactory.create( parent_location=split_test.location, category="vertical", display_name="Condition 1 vertical", location=c1_url, ) ItemFactory.create( parent_location=split_test.location, category="vertical", display_name="Condition 2 vertical", location=c2_url, ) problem = None if group_id and cid_for_problem: problem = ItemFactory.create( category='problem', parent_location=c1_vertical.location, display_name=u"Test Problem" ) self.client.ajax_post( reverse_usage_url("xblock_handler", problem.location), data={'metadata': {'group_access': {cid_for_problem: [group_id]}}} ) c1_vertical.children.append(problem.location) partitions_json = [p.to_json() for p in self.course.user_partitions] self.client.ajax_post( reverse_usage_url("xblock_handler", split_test.location), data={'metadata': {'user_partitions': partitions_json}} ) self.save_course() return vertical, split_test, problem