def test_preview_html(self): """ Checks that html for the StructuredTagsAside is generated correctly """ request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. context = {'reorderable_items': set(), 'read_only': True} problem_html = get_preview_fragment(request, self.problem, context).content parser = etree.HTMLParser() tree = etree.parse(StringIO(problem_html), parser) main_div_nodes = tree.xpath('/html/body/div/section/div') self.assertEquals(len(main_div_nodes), 1) div_node = main_div_nodes[0] self.assertEquals(div_node.get('data-init'), 'StructuredTagsInit') self.assertEquals(div_node.get('data-runtime-class'), 'PreviewRuntime') self.assertEquals(div_node.get('data-block-type'), 'tagging_aside') self.assertEquals(div_node.get('data-runtime-version'), '1') self.assertIn('xblock_asides-v1', div_node.get('class')) select_nodes = div_node.xpath('div/select') self.assertEquals(len(select_nodes), 2) select_node1 = select_nodes[0] self.assertEquals(select_node1.get('name'), self.aside_tag_dif) option_nodes1 = select_node1.xpath('option') self.assertEquals(len(option_nodes1), 4) option_values1 = [opt_elem.text for opt_elem in option_nodes1] self.assertEquals(option_values1, ['Not selected', 'Easy', 'Medium', 'Hard']) select_node2 = select_nodes[1] self.assertEquals(select_node2.get('name'), self.aside_tag_lo) option_nodes2 = select_node2.xpath('option') self.assertEquals(len(option_nodes2), 4) option_values2 = [ opt_elem.text for opt_elem in option_nodes2 if opt_elem.text ] self.assertEquals(option_values2, [ 'Not selected', 'Learned nothing', 'Learned a few things', 'Learned everything' ]) # Now ensure the acid_aside is not in the result self.assertNotRegexpMatches(problem_html, r"data-block-type=[\"\']acid_aside[\"\']") # Ensure about video don't have asides video_html = get_preview_fragment(request, self.video, context).content self.assertNotRegexpMatches(video_html, "<select")
def test_preview_html(self): """ Checks that html for the StructuredTagsAside is generated correctly """ request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. context = { 'reorderable_items': set(), 'read_only': True } problem_html = get_preview_fragment(request, self.problem, context).content parser = etree.HTMLParser() tree = etree.parse(StringIO(problem_html), parser) main_div_nodes = tree.xpath('/html/body/div/section/div') self.assertEquals(len(main_div_nodes), 1) div_node = main_div_nodes[0] self.assertEquals(div_node.get('data-init'), 'StructuredTagsInit') self.assertEquals(div_node.get('data-runtime-class'), 'PreviewRuntime') self.assertEquals(div_node.get('data-block-type'), 'tagging_aside') self.assertEquals(div_node.get('data-runtime-version'), '1') self.assertIn('xblock_asides-v1', div_node.get('class')) select_nodes = div_node.xpath('div/select') self.assertEquals(len(select_nodes), 2) select_node1 = select_nodes[0] self.assertEquals(select_node1.get('name'), self.aside_tag_dif) option_nodes1 = select_node1.xpath('option') self.assertEquals(len(option_nodes1), 4) option_values1 = [opt_elem.text for opt_elem in option_nodes1] self.assertEquals(option_values1, ['Not selected', 'Easy', 'Medium', 'Hard']) select_node2 = select_nodes[1] self.assertEquals(select_node2.get('name'), self.aside_tag_lo) option_nodes2 = select_node2.xpath('option') self.assertEquals(len(option_nodes2), 4) option_values2 = [opt_elem.text for opt_elem in option_nodes2 if opt_elem.text] self.assertEquals(option_values2, ['Not selected', 'Learned nothing', 'Learned a few things', 'Learned everything']) # Now ensure the acid_aside is not in the result self.assertNotRegexpMatches(problem_html, r"data-block-type=[\"\']acid_aside[\"\']") # Ensure about video don't have asides video_html = get_preview_fragment(request, self.video, context).content self.assertNotRegexpMatches(video_html, "<select")
def test_preview_fragment(self): """ Test for calling get_preview_html. This test used to be specifically about Locators (ensuring that they did not get translated to Locations). The test now has questionable value. """ course = CourseFactory.create() html = ItemFactory.create( parent_location=course.location, category="html", data={'data': "<html>foobar</html>"} ) request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. html = get_preview_fragment(request, html, {}).content # Verify student view html is returned, and the usage ID is as expected. self.assertRegexpMatches( html, 'data-usage-id="i4x://MITx/999/html/html_[0-9]*"' ) self.assertRegexpMatches(html, '<html>foobar</html>')
def test_preview_handler_locator(self): """ Test for calling get_preview_html when descriptor.location is a Locator. """ course = CourseFactory.create() html = ItemFactory.create(parent_location=course.location, category="html", data={'data': "<html>foobar</html>"}) locator = loc_mapper().translate_location(course.location.course_id, html.location, True, True) # Change the stored location to a locator. html.location = locator html.save() request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Must call get_preview_fragment directly, as going through xblock RESTful API will attempt # to use item.location as a Location. html = get_preview_fragment(request, html, {}).content # Verify student view html is returned, and there are no old locations in it. self.assertRegexpMatches( html, 'data-usage-id="MITx.999.Robot_Super_Course;_branch;_published;_block;_html_[0-9]*"' ) self.assertRegexpMatches(html, '<html>foobar</html>') self.assertNotRegexpMatches(html, 'i4x')
def test_preview_no_asides(self): """ Test for calling get_preview_html. Ensures data-usage-id is correctly set and asides are correctly excluded because they are not enabled. """ course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) html = ItemFactory.create( parent_location=course.location, category="html", data={'data': "<html>foobar</html>"} ) config = StudioConfig.current() config.enabled = False config.save() request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. context = { 'reorderable_items': set(), 'read_only': True } html = get_preview_fragment(request, html, context).content self.assertNotRegexpMatches(html, r"data-block-type=[\"\']test_aside[\"\']") self.assertNotRegexpMatches(html, "Aside rendered")
def test_preview_no_asides(self): """ Test for calling get_preview_html. Ensures data-usage-id is correctly set and asides are correctly excluded because they are not enabled. """ course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) html = ItemFactory.create(parent_location=course.location, category="html", data={'data': "<html>foobar</html>"}) config = StudioConfig.current() config.enabled = False config.save() request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. context = {'reorderable_items': set(), 'read_only': True} html = get_preview_fragment(request, html, context).content self.assertNotRegexpMatches(html, r"data-block-type=[\"\']test_aside[\"\']") self.assertNotRegexpMatches(html, "Aside rendered")
def test_preview_fragment(self): """ Test for calling get_preview_html. This test used to be specifically about Locators (ensuring that they did not get translated to Locations). The test now has questionable value. """ course = CourseFactory.create() html = ItemFactory.create( parent_location=course.location, category="html", data={'data': "<html>foobar</html>"} ) request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. context = { 'reorderable_items': set(), 'read_only': True } html = get_preview_fragment(request, html, context).content # Verify student view html is returned, and the usage ID is as expected. html_pattern = unicode(course.id.make_usage_key('html', 'html_')).replace('html_', r'html_[0-9]*') self.assertRegexpMatches( html, 'data-usage-id="{}"'.format(html_pattern) ) self.assertRegexpMatches(html, '<html>foobar</html>')
def test_preview_fragment(self): """ Test for calling get_preview_html. This test used to be specifically about Locators (ensuring that they did not get translated to Locations). The test now has questionable value. """ course = CourseFactory.create() html = ItemFactory.create(parent_location=course.location, category="html", data={'data': "<html>foobar</html>"}) request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. context = {'reorderable_items': set(), 'read_only': True} html = get_preview_fragment(request, html, context).content # Verify student view html is returned, and the usage ID is as expected. html_pattern = unicode(course.id.make_usage_key( 'html', 'html_')).replace('html_', r'html_[0-9]*') self.assertRegexpMatches(html, 'data-usage-id="{}"'.format(html_pattern)) self.assertRegexpMatches(html, '<html>foobar</html>')
def test_preview_handler_locator(self): """ Test for calling get_preview_html when descriptor.location is a Locator. """ course = CourseFactory.create() html = ItemFactory.create( parent_location=course.location, category="html", data={'data': "<html>foobar</html>"} ) locator = loc_mapper().translate_location( course.location.course_id, html.location, True, True ) # Change the stored location to a locator. html.location = locator html.save() request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Must call get_preview_fragment directly, as going through xblock RESTful API will attempt # to use item.location as a Location. html = get_preview_fragment(request, html).content # Verify student view html is returned, and there are no old locations in it. self.assertRegexpMatches( html, 'data-usage-id="MITx.999.Robot_Super_Course;_branch;_published;_block;_html_[0-9]*"' ) self.assertRegexpMatches(html, '<html>foobar</html>') self.assertNotRegexpMatches(html, 'i4x')
def test_preview_fragment(self): """ Test for calling get_preview_html. Ensures data-usage-id is correctly set and asides are correctly included. """ course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) html = ItemFactory.create( parent_location=course.location, category="html", data={'data': "<html>foobar</html>"} ) config = StudioConfig.current() config.enabled = True config.save() request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. context = { 'reorderable_items': set(), 'read_only': True } html = get_preview_fragment(request, html, context).content # Verify student view html is returned, and the usage ID is as expected. html_pattern = re.escape(unicode(course.id.make_usage_key('html', 'replaceme'))).replace('replaceme', r'html_[0-9]*') self.assertRegexpMatches( html, 'data-usage-id="{}"'.format(html_pattern) ) self.assertRegexpMatches(html, '<html>foobar</html>') self.assertRegexpMatches(html, r"data-block-type=[\"\']test_aside[\"\']") self.assertRegexpMatches(html, "Aside rendered") # Now ensure the acid_aside is not in the result self.assertNotRegexpMatches(html, r"data-block-type=[\"\']acid_aside[\"\']") # Ensure about pages don't have asides about = modulestore().get_item(course.id.make_usage_key('about', 'overview')) html = get_preview_fragment(request, about, context).content self.assertNotRegexpMatches(html, r"data-block-type=[\"\']test_aside[\"\']") self.assertNotRegexpMatches(html, "Aside rendered")
def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, version_guid=None, block=None): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block) if not has_course_access(request.user, locator): raise PermissionDenied() old_location = loc_mapper().translate_locator_to_location(locator) accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/x-fragment+json' in accept_header: store = get_modulestore(old_location) component = store.get_item(old_location) # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly component.runtime.wrappers.append(partial(wrap_xblock, 'StudioRuntime')) if view_name == 'studio_view': try: fragment = component.render('studio_view') # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=w0703 log.debug("unable to render studio_view for %r", component, exc_info=True) fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)})) store.save_xmodule(component) elif view_name == 'student_view': fragment = get_preview_fragment(request, component) fragment.content = render_to_string('component.html', { 'preview': fragment.content, 'label': component.display_name or component.scope_ids.block_type, }) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': fragment.content, 'resources': hashed_resources.items() }) else: return HttpResponse(status=406)
def test_preview_html(self): """ Checks that html for the StructuredTagsAside is generated correctly """ request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. context = { 'reorderable_items': set(), 'read_only': True } problem_html = get_preview_fragment(request, self.problem, context).content parser = etree.HTMLParser() tree = etree.parse(StringIO(problem_html), parser) main_div_nodes = tree.xpath('/html/body/div/section/div') self.assertEquals(len(main_div_nodes), 1) div_node = main_div_nodes[0] self.assertEquals(div_node.get('data-init'), 'StructuredTagsInit') self.assertEquals(div_node.get('data-runtime-class'), 'PreviewRuntime') self.assertEquals(div_node.get('data-block-type'), 'tagging_aside') self.assertEquals(div_node.get('data-runtime-version'), '1') self.assertIn('xblock_asides-v1', div_node.get('class')) select_nodes = div_node.xpath('div/select') self.assertEquals(len(select_nodes), 1) select_node = select_nodes[0] self.assertEquals(select_node.get('name'), self.aside_tag) # Now ensure the acid_aside is not in the result self.assertNotRegexpMatches(problem_html, r"data-block-type=[\"\']acid_aside[\"\']") # Ensure about video don't have asides video_html = get_preview_fragment(request, self.video, context).content self.assertNotRegexpMatches(video_html, "<select")
def test_preview_html(self): """ Checks that html for the StructuredTagsAside is generated correctly """ request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} # Call get_preview_fragment directly. context = {'reorderable_items': set(), 'read_only': True} problem_html = get_preview_fragment(request, self.problem, context).content parser = etree.HTMLParser() tree = etree.parse(StringIO(problem_html), parser) main_div_nodes = tree.xpath('/html/body/div/section/div') self.assertEquals(len(main_div_nodes), 1) div_node = main_div_nodes[0] self.assertEquals(div_node.get('data-init'), 'StructuredTagsInit') self.assertEquals(div_node.get('data-runtime-class'), 'PreviewRuntime') self.assertEquals(div_node.get('data-block-type'), 'tagging_aside') self.assertEquals(div_node.get('data-runtime-version'), '1') self.assertIn('xblock_asides-v1', div_node.get('class')) select_nodes = div_node.xpath('div/select') self.assertEquals(len(select_nodes), 1) select_node = select_nodes[0] self.assertEquals(select_node.get('name'), self.aside_tag) # Now ensure the acid_aside is not in the result self.assertNotRegexpMatches(problem_html, r"data-block-type=[\"\']acid_aside[\"\']") # Ensure about video don't have asides video_html = get_preview_fragment(request, self.video, context).content self.assertNotRegexpMatches(video_html, "<select")
def xblock_view_handler(request, usage_key_string, view_name): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ usage_key = usage_key_with_run(usage_key_string) if not has_course_access(request.user, usage_key.course_key): raise PermissionDenied() accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/json' in accept_header: store = modulestore() xblock = store.get_item(usage_key) container_views = ['container_preview', 'reorderable_container_child_preview'] # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly xblock.runtime.wrappers.append(partial( wrap_xblock, 'StudioRuntime', usage_id_serializer=unicode, request_token=request_token(request), )) if view_name == STUDIO_VIEW: try: fragment = xblock.render(STUDIO_VIEW) # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=w0703 log.debug("unable to render studio_view for %r", xblock, exc_info=True) fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)})) store.update_item(xblock, request.user.id) elif view_name in (PREVIEW_VIEWS + container_views): is_pages_view = view_name == STUDENT_VIEW # Only the "Pages" view uses student view in Studio # Determine the items to be shown as reorderable. Note that the view # 'reorderable_container_child_preview' is only rendered for xblocks that # are being shown in a reorderable container, so the xblock is automatically # added to the list. reorderable_items = set() if view_name == 'reorderable_container_child_preview': reorderable_items.add(xblock.location) # Set up the context to be passed to each XBlock's render method. context = { 'is_pages_view': is_pages_view, # This setting disables the recursive wrapping of xblocks 'is_unit_page': is_unit(xblock), 'root_xblock': xblock if (view_name == 'container_preview') else None, 'reorderable_items': reorderable_items } fragment = get_preview_fragment(request, xblock, context) # Note that the container view recursively adds headers into the preview fragment, # so only the "Pages" view requires that this extra wrapper be included. if is_pages_view: fragment.content = render_to_string('component.html', { 'xblock_context': context, 'xblock': xblock, 'locator': usage_key, 'preview': fragment.content, 'label': xblock.display_name or xblock.scope_ids.block_type, }) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': fragment.content, 'resources': hashed_resources.items() }) else: return HttpResponse(status=406)
def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, version_guid=None, block=None): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block) if not has_course_access(request.user, locator): raise PermissionDenied() old_location = loc_mapper().translate_locator_to_location(locator) accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/json' in accept_header: store = get_modulestore(old_location) component = store.get_item(old_location) # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly component.runtime.wrappers.append(partial(wrap_xblock, 'StudioRuntime')) if view_name == 'studio_view': try: fragment = component.render('studio_view') # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=w0703 log.debug("unable to render studio_view for %r", component, exc_info=True) fragment = Fragment( render_to_string('html_error.html', {'message': str(exc)})) # change not authored by requestor but by xblocks. store.update_item(component, None) elif view_name == 'student_view' and component.has_children: # For non-leaf xblocks on the unit page, show the special rendering # which links to the new container page. html = render_to_string( 'container_xblock_component.html', { 'xblock': component, 'locator': locator, 'reordering_enabled': True, }) return JsonResponse({ 'html': html, 'resources': [], }) elif view_name in ('student_view', 'container_preview'): is_container_view = (view_name == 'container_preview') component_publish_state = compute_publish_state(component) is_read_only_view = component_publish_state == PublishState.public # Only show the new style HTML for the container view, i.e. for non-verticals # Note: this special case logic can be removed once the unit page is replaced # with the new container view. context = { 'runtime_type': 'studio', 'container_view': is_container_view, 'read_only': is_read_only_view, 'root_xblock': component, } fragment = get_preview_fragment(request, component, context) # For old-style pages (such as unit and static pages), wrap the preview with # the component div. Note that the container view recursively adds headers # into the preview fragment, so we don't want to add another header here. if not is_container_view: fragment.content = render_to_string( 'component.html', { 'preview': fragment.content, 'label': component.display_name or component.scope_ids.block_type, }) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': fragment.content, 'resources': hashed_resources.items() }) else: return HttpResponse(status=406)
def xblock_view_handler(request, usage_key_string, view_name): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ usage_key = usage_key_with_run(usage_key_string) if not has_course_author_access(request.user, usage_key.course_key): raise PermissionDenied() accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/json' in accept_header: store = modulestore() xblock = store.get_item(usage_key) container_views = [ 'container_preview', 'reorderable_container_child_preview' ] # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly xblock.runtime.wrappers.append( partial( wrap_xblock, 'StudioRuntime', usage_id_serializer=unicode, request_token=request_token(request), )) if view_name == STUDIO_VIEW: try: fragment = xblock.render(STUDIO_VIEW) # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=broad-except log.debug("unable to render studio_view for %r", xblock, exc_info=True) fragment = Fragment( render_to_string('html_error.html', {'message': str(exc)})) elif view_name in (PREVIEW_VIEWS + container_views): is_pages_view = view_name == STUDENT_VIEW # Only the "Pages" view uses student view in Studio # Determine the items to be shown as reorderable. Note that the view # 'reorderable_container_child_preview' is only rendered for xblocks that # are being shown in a reorderable container, so the xblock is automatically # added to the list. reorderable_items = set() if view_name == 'reorderable_container_child_preview': reorderable_items.add(xblock.location) # Set up the context to be passed to each XBlock's render method. context = { 'is_pages_view': is_pages_view, # This setting disables the recursive wrapping of xblocks 'is_unit_page': is_unit(xblock), 'root_xblock': xblock if (view_name == 'container_preview') else None, 'reorderable_items': reorderable_items } fragment = get_preview_fragment(request, xblock, context) # Note that the container view recursively adds headers into the preview fragment, # so only the "Pages" view requires that this extra wrapper be included. if is_pages_view: fragment.content = render_to_string( 'component.html', { 'xblock_context': context, 'xblock': xblock, 'locator': usage_key, 'preview': fragment.content, 'label': xblock.display_name or xblock.scope_ids.block_type, }) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': fragment.content, 'resources': hashed_resources.items() }) else: return HttpResponse(status=406)
def xblock_view_handler(request, usage_key_string, view_name): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ usage_key = usage_key_with_run(usage_key_string) if not has_studio_read_access(request.user, usage_key.course_key): raise PermissionDenied() accept_header = request.META.get("HTTP_ACCEPT", "application/json") if "application/json" in accept_header: store = modulestore() xblock = store.get_item(usage_key) container_views = ["container_preview", "reorderable_container_child_preview", "container_child_preview"] # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly xblock.runtime.wrappers.append( partial(wrap_xblock, "StudioRuntime", usage_id_serializer=unicode, request_token=request_token(request)) ) if view_name in (STUDIO_VIEW, VISIBILITY_VIEW): try: fragment = xblock.render(view_name) # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=broad-except log.debug("Unable to render %s for %r", view_name, xblock, exc_info=True) fragment = Fragment(render_to_string("html_error.html", {"message": str(exc)})) elif view_name in PREVIEW_VIEWS + container_views: is_pages_view = view_name == STUDENT_VIEW # Only the "Pages" view uses student view in Studio can_edit = has_studio_write_access(request.user, usage_key.course_key) # Determine the items to be shown as reorderable. Note that the view # 'reorderable_container_child_preview' is only rendered for xblocks that # are being shown in a reorderable container, so the xblock is automatically # added to the list. reorderable_items = set() if view_name == "reorderable_container_child_preview": reorderable_items.add(xblock.location) paging = None try: if request.REQUEST.get("enable_paging", "false") == "true": paging = { "page_number": int(request.REQUEST.get("page_number", 0)), "page_size": int(request.REQUEST.get("page_size", 0)), } except ValueError: # pylint: disable=too-many-format-args return HttpResponse( content="Couldn't parse paging parameters: enable_paging: " "{0}, page_number: {1}, page_size: {2}".format( request.REQUEST.get("enable_paging", "false"), request.REQUEST.get("page_number", 0), request.REQUEST.get("page_size", 0), ), status=400, content_type="text/plain", ) force_render = request.REQUEST.get("force_render", None) # Set up the context to be passed to each XBlock's render method. context = { "is_pages_view": is_pages_view, # This setting disables the recursive wrapping of xblocks "is_unit_page": is_unit(xblock), "can_edit": can_edit, "root_xblock": xblock if (view_name == "container_preview") else None, "reorderable_items": reorderable_items, "paging": paging, "force_render": force_render, } fragment = get_preview_fragment(request, xblock, context) # Note that the container view recursively adds headers into the preview fragment, # so only the "Pages" view requires that this extra wrapper be included. if is_pages_view: fragment.content = render_to_string( "component.html", { "xblock_context": context, "xblock": xblock, "locator": usage_key, "preview": fragment.content, "label": xblock.display_name or xblock.scope_ids.block_type, }, ) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({"html": fragment.content, "resources": hashed_resources.items()}) else: return HttpResponse(status=406)
def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, version_guid=None, block=None): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block) if not has_course_access(request.user, locator): raise PermissionDenied() old_location = loc_mapper().translate_locator_to_location(locator) accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/json' in accept_header: store = get_modulestore(old_location) component = store.get_item(old_location) # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly component.runtime.wrappers.append(partial(wrap_xblock, 'StudioRuntime')) if view_name == 'studio_view': try: fragment = component.render('studio_view') # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=w0703 log.debug("unable to render studio_view for %r", component, exc_info=True) fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)})) # change not authored by requestor but by xblocks. store.update_item(component, None) elif view_name == 'student_view' and component.has_children: # For non-leaf xblocks on the unit page, show the special rendering # which links to the new container page. html = render_to_string('container_xblock_component.html', { 'xblock': component, 'locator': locator, 'reordering_enabled': True, }) return JsonResponse({ 'html': html, 'resources': [], }) elif view_name in ('student_view', 'container_preview'): is_container_view = (view_name == 'container_preview') # Only show the new style HTML for the container view, i.e. for non-verticals # Note: this special case logic can be removed once the unit page is replaced # with the new container view. is_read_only_view = is_container_view context = { 'container_view': is_container_view, 'read_only': is_read_only_view, 'root_xblock': component } fragment = get_preview_fragment(request, component, context) # For old-style pages (such as unit and static pages), wrap the preview with # the component div. Note that the container view recursively adds headers # into the preview fragment, so we don't want to add another header here. if not is_container_view: fragment.content = render_to_string('component.html', { 'preview': fragment.content, 'label': component.display_name or component.scope_ids.block_type, # Native XBlocks are responsible for persisting their own data, # so they are also responsible for providing save/cancel buttons. 'show_save_cancel': isinstance(component, xmodule.x_module.XModuleDescriptor), }) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': fragment.content, 'resources': hashed_resources.items() }) else: return HttpResponse(status=406)
def xblock_view_handler(request, usage_key_string, view_name): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ usage_key = UsageKey.from_string(usage_key_string) if not has_course_access(request.user, usage_key.course_key): raise PermissionDenied() accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/json' in accept_header: store = modulestore() xblock = store.get_item(usage_key) is_read_only = _is_xblock_read_only(xblock) container_views = ['container_preview', 'reorderable_container_child_preview'] unit_views = PREVIEW_VIEWS # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly xblock.runtime.wrappers.append(partial(wrap_xblock, 'StudioRuntime', usage_id_serializer=unicode)) if view_name == STUDIO_VIEW: try: fragment = xblock.render(STUDIO_VIEW) # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=w0703 log.debug("unable to render studio_view for %r", xblock, exc_info=True) fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)})) store.update_item(xblock, request.user.id) elif view_name in (unit_views + container_views): is_container_view = (view_name in container_views) # Determine the items to be shown as reorderable. Note that the view # 'reorderable_container_child_preview' is only rendered for xblocks that # are being shown in a reorderable container, so the xblock is automatically # added to the list. reorderable_items = set() if view_name == 'reorderable_container_child_preview': reorderable_items.add(xblock.location) # Only show the new style HTML for the container view, i.e. for non-verticals # Note: this special case logic can be removed once the unit page is replaced # with the new container view. context = { 'container_view': is_container_view, 'read_only': is_read_only, 'root_xblock': xblock if (view_name == 'container_preview') else None, 'reorderable_items': reorderable_items } fragment = get_preview_fragment(request, xblock, context) # For old-style pages (such as unit and static pages), wrap the preview with # the component div. Note that the container view recursively adds headers # into the preview fragment, so we don't want to add another header here. if not is_container_view: # For non-leaf xblocks, show the special rendering which links to the new container page. if xblock_has_own_studio_page(xblock): template = 'container_xblock_component.html' else: template = 'component.html' fragment.content = render_to_string(template, { 'xblock_context': context, 'xblock': xblock, 'locator': usage_key, 'preview': fragment.content, 'label': xblock.display_name or xblock.scope_ids.block_type, }) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': fragment.content, 'resources': hashed_resources.items() }) else: return HttpResponse(status=406)
def xblock_view_handler(request, usage_key_string, view_name): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ usage_key = UsageKey.from_string(usage_key_string) if not has_course_access(request.user, usage_key.course_key): raise PermissionDenied() accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/json' in accept_header: store = get_modulestore(usage_key) xblock = store.get_item(usage_key) is_read_only = _is_xblock_read_only(xblock) container_views = [ 'container_preview', 'reorderable_container_child_preview' ] unit_views = ['student_view'] # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly xblock.runtime.wrappers.append( partial(wrap_xblock, 'StudioRuntime', usage_id_serializer=unicode)) if view_name == 'studio_view': try: fragment = xblock.render('studio_view') # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=w0703 log.debug("unable to render studio_view for %r", xblock, exc_info=True) fragment = Fragment( render_to_string('html_error.html', {'message': str(exc)})) # change not authored by requestor but by xblocks. store.update_item(xblock, None) elif view_name in (unit_views + container_views): is_container_view = (view_name in container_views) # Determine the items to be shown as reorderable. Note that the view # 'reorderable_container_child_preview' is only rendered for xblocks that # are being shown in a reorderable container, so the xblock is automatically # added to the list. reorderable_items = set() if view_name == 'reorderable_container_child_preview': reorderable_items.add(xblock.location) # Only show the new style HTML for the container view, i.e. for non-verticals # Note: this special case logic can be removed once the unit page is replaced # with the new container view. context = { 'runtime_type': 'studio', 'container_view': is_container_view, 'read_only': is_read_only, 'root_xblock': xblock if (view_name == 'container_preview') else None, 'reorderable_items': reorderable_items } fragment = get_preview_fragment(request, xblock, context) # For old-style pages (such as unit and static pages), wrap the preview with # the component div. Note that the container view recursively adds headers # into the preview fragment, so we don't want to add another header here. if not is_container_view: # For non-leaf xblocks, show the special rendering which links to the new container page. if xblock_has_own_studio_page(xblock): template = 'container_xblock_component.html' else: template = 'component.html' fragment.content = render_to_string( template, { 'xblock_context': context, 'xblock': xblock, 'locator': usage_key, 'preview': fragment.content, 'label': xblock.display_name or xblock.scope_ids.block_type, }) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': fragment.content, 'resources': hashed_resources.items() }) else: return HttpResponse(status=406)
def xblock_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None): """ The restful handler for xblock requests. DELETE json: delete this xblock instance from the course. Supports query parameters "recurse" to delete all children and "all_versions" to delete from all (mongo) versions. GET json: returns representation of the xblock (locator id, data, and metadata). if ?fields=graderType, it returns the graderType for the unit instead of the above. html: returns HTML for rendering the xblock (which includes both the "preview" view and the "editor" view) PUT or POST json: if xblock locator is specified, update the xblock instance. The json payload can contain these fields, all optional: :data: the new value for the data. :children: the locator ids of children for this xblock. :metadata: new values for the metadata fields. Any whose values are None will be deleted not set to None! Absent ones will be left alone. :nullout: which metadata fields to set to None :graderType: change how this unit is graded :publish: can be one of three values, 'make_public, 'make_private', or 'create_draft' The JSON representation on the updated xblock (minus children) is returned. if xblock locator is not specified, create a new xblock instance, either by duplicating an existing xblock, or creating an entirely new one. The json playload can contain these fields: :parent_locator: parent for new xblock, required for both duplicate and create new instance :duplicate_source_locator: if present, use this as the source for creating a duplicate copy :category: type of xblock, required if duplicate_source_locator is not present. :display_name: name for new xblock, optional :boilerplate: template name for populating fields, optional and only used if duplicate_source_locator is not present The locator (and old-style id) for the created xblock (minus children) is returned. """ if package_id is not None: locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block) if not has_course_access(request.user, locator): raise PermissionDenied() old_location = loc_mapper().translate_locator_to_location(locator) if request.method == 'GET': accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/x-fragment+json' in accept_header: component = modulestore().get_item(old_location) # Wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly component.runtime.wrappers.append(partial(wrap_xblock, 'StudioRuntime')) try: editor_fragment = component.render('studio_view') # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=W0703 log.debug("Unable to render studio_view for %r", component, exc_info=True) editor_fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)})) modulestore().save_xmodule(component) preview_fragment = get_preview_fragment(request, component) hashed_resources = OrderedDict() for resource in editor_fragment.resources + preview_fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': render_to_string('component.html', { 'preview': preview_fragment.content, 'editor': editor_fragment.content, 'label': component.display_name or component.scope_ids.block_type, }), 'resources': hashed_resources.items() }) elif 'application/json' in accept_header: fields = request.REQUEST.get('fields', '').split(',') if 'graderType' in fields: # right now can't combine output of this w/ output of _get_module_info, but worthy goal return JsonResponse(CourseGradingModel.get_section_grader_type(locator)) # TODO: pass fields to _get_module_info and only return those rsp = _get_module_info(locator) return JsonResponse(rsp) else: return HttpResponse(status=406) elif request.method == 'DELETE': delete_children = str_to_bool(request.REQUEST.get('recurse', 'False')) delete_all_versions = str_to_bool(request.REQUEST.get('all_versions', 'False')) return _delete_item_at_location(old_location, delete_children, delete_all_versions) else: # Since we have a package_id, we are updating an existing xblock. return _save_item( request, locator, old_location, data=request.json.get('data'), children=request.json.get('children'), metadata=request.json.get('metadata'), nullout=request.json.get('nullout'), grader_type=request.json.get('graderType'), publish=request.json.get('publish'), ) elif request.method in ('PUT', 'POST'): if 'duplicate_source_locator' in request.json: parent_locator = BlockUsageLocator(request.json['parent_locator']) duplicate_source_locator = BlockUsageLocator(request.json['duplicate_source_locator']) # _duplicate_item is dealing with locations to facilitate the recursive call for # duplicating children. parent_location = loc_mapper().translate_locator_to_location(parent_locator) duplicate_source_location = loc_mapper().translate_locator_to_location(duplicate_source_locator) dest_location = _duplicate_item( parent_location, duplicate_source_location, request.json.get('display_name') ) course_location = loc_mapper().translate_locator_to_location(BlockUsageLocator(parent_locator), get_course=True) dest_locator = loc_mapper().translate_location(course_location.course_id, dest_location, False, True) return JsonResponse({"locator": unicode(dest_locator)}) else: return _create_item(request) else: return HttpResponseBadRequest( "Only instance creation is supported without a package_id.", content_type="text/plain" )