def component_handler(request, usage_key_string, handler, suffix=''): """ Dispatch an AJAX action to an xblock Args: usage_id: The usage-id of the block to dispatch to handler (str): The handler to execute suffix (str): The remainder of the url to be passed to the handler Returns: :class:`django.http.HttpResponse`: The response from the handler, converted to a django response """ usage_key = UsageKey.from_string(usage_key_string) descriptor = get_modulestore(usage_key).get_item(usage_key) # Let the module handle the AJAX req = django_to_webob_request(request) try: resp = descriptor.handle(handler, req, suffix) except NoSuchHandlerError: log.info("XBlock %s attempted to access missing handler %r", descriptor, handler, exc_info=True) raise Http404 # unintentional update to handle any side effects of handle call; so, request user didn't author # the change get_modulestore(usage_key).update_item(descriptor, None) return webob_to_django_response(resp)
def test_block_constructor(self): expected_org = 'mit.eecs' expected_offering = '6002x' expected_branch = 'published' expected_block_ref = 'HW3' testurn = 'edx:{}+{}+{}+{}+{}+{}+{}+{}'.format( expected_org, expected_offering, CourseLocator.BRANCH_PREFIX, expected_branch, BlockUsageLocator.BLOCK_TYPE_PREFIX, 'problem', BlockUsageLocator.BLOCK_PREFIX, 'HW3' ) testobj = UsageKey.from_string(testurn) self.check_block_locn_fields( testobj, org=expected_org, offering=expected_offering, branch=expected_branch, block_type='problem', block=expected_block_ref ) self.assertEqual(unicode(testobj), testurn) testobj = testobj.for_version(ObjectId()) agnostic = testobj.version_agnostic() self.assertIsNone(agnostic.version_guid) self.check_block_locn_fields(agnostic, org=expected_org, offering=expected_offering, branch=expected_branch, block=expected_block_ref)
def preview_handler(request, usage_key_string, handler, suffix=''): """ Dispatch an AJAX action to an xblock usage_key_string: The usage_key_string-id of the block to dispatch to, passed through `quote_slashes` handler: The handler to execute suffix: The remainder of the url to be passed to the handler """ usage_key = UsageKey.from_string(usage_key_string) descriptor = modulestore().get_item(usage_key) instance = _load_preview_module(request, descriptor) # Let the module handle the AJAX req = django_to_webob_request(request) try: resp = instance.handle(handler, req, suffix) except NoSuchHandlerError: log.exception("XBlock %s attempted to access missing handler %r", instance, handler) raise Http404 except NotFoundError: log.exception("Module indicating to user that request doesn't exist") raise Http404 except ProcessingError: log.warning("Module raised an error while processing AJAX request", exc_info=True) return HttpResponseBadRequest() except Exception: log.exception("error processing ajax call") raise return webob_to_django_response(resp)
def test_block_constructor_url_version_prefix(self): test_id_loc = '519665f6223ebd6980884f2b' testobj = UsageKey.from_string( 'edx:mit.eecs+6002x+{}+{}+{}+problem+{}+lab2'.format( CourseLocator.VERSION_PREFIX, test_id_loc, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX ) ) self.check_block_locn_fields( testobj, org='mit.eecs', offering='6002x', block_type='problem', block='lab2', version_guid=ObjectId(test_id_loc) ) agnostic = testobj.course_agnostic() self.check_block_locn_fields( agnostic, block='lab2', org=None, offering=None, version_guid=ObjectId(test_id_loc) ) self.assertIsNone(agnostic.offering) self.assertIsNone(agnostic.org)
def test_repr(self): testurn = u'edx:mit.eecs+6002x+{}+published+{}+problem+{}+HW3'.format( CourseLocator.BRANCH_PREFIX, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX) testobj = UsageKey.from_string(testurn) self.assertEqual( "BlockUsageLocator(CourseLocator(u'mit.eecs', u'6002x', u'published', None), u'problem', u'HW3')", repr(testobj))
def response_usage_key(self, response): """ Get the UsageKey from the response payload and verify that the status_code was 200. :param response: """ parsed = json.loads(response.content) self.assertEqual(response.status_code, 200) return UsageKey.from_string(parsed['locator'])
def get_tab_by_locator(tab_list, usage_key_string): """ Look for a tab with the specified locator. Returns the first matching tab. """ tab_location = UsageKey.from_string(usage_key_string) item = modulestore('direct').get_item(tab_location) static_tab = StaticTab( name=item.display_name, url_slug=item.location.name, ) return CourseTabList.get_tab_by_id(tab_list, static_tab.tab_id)
def test_block_constructor_url_kitchen_sink(self): test_id_loc = '519665f6223ebd6980884f2b' testobj = UsageKey.from_string( 'edx:mit.eecs+6002x+{}+draft+{}+{}+{}+problem+{}+lab2'.format( CourseLocator.BRANCH_PREFIX, CourseLocator.VERSION_PREFIX, test_id_loc, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX)) self.check_block_locn_fields(testobj, org='mit.eecs', offering='6002x', branch='draft', block='lab2', version_guid=ObjectId(test_id_loc))
def test_block_constructor_url_kitchen_sink(self): test_id_loc = '519665f6223ebd6980884f2b' testobj = UsageKey.from_string( 'edx:mit.eecs+6002x+{}+draft+{}+{}+{}+problem+{}+lab2'.format( CourseLocator.BRANCH_PREFIX, CourseLocator.VERSION_PREFIX, test_id_loc, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX ) ) self.check_block_locn_fields( testobj, org='mit.eecs', offering='6002x', branch='draft', block='lab2', version_guid=ObjectId(test_id_loc) )
def _get_item(request, data): """ Obtains from 'data' the locator for an item. Next, gets that item from the modulestore (allowing any errors to raise up). Finally, verifies that the user has access to the item. Returns the item. """ usage_key = UsageKey.from_string(data.get('locator')) # This is placed before has_course_access() to validate the location, # because has_course_access() raises r if location is invalid. item = modulestore().get_item(usage_key) if not has_course_access(request.user, usage_key.course_key): raise PermissionDenied() return item
def _create_item(request): """View for create items.""" usage_key = UsageKey.from_string(request.json['parent_locator']) category = request.json['category'] display_name = request.json.get('display_name') if not has_course_access(request.user, usage_key.course_key): raise PermissionDenied() parent = get_modulestore(category).get_item(usage_key) dest_usage_key = usage_key.replace(category=category, name=uuid4().hex) # get the metadata, display_name, and definition from the request metadata = {} data = None template_id = request.json.get('boilerplate') if template_id: clz = parent.runtime.load_block_type(category) if clz is not None: template = clz.get_template(template_id) if template is not None: metadata = template.get('metadata', {}) data = template.get('data') if display_name is not None: metadata['display_name'] = display_name get_modulestore(category).create_and_save_xmodule( dest_usage_key, definition_data=data, metadata=metadata, system=parent.runtime, ) # TODO replace w/ nicer accessor if not 'detached' in parent.runtime.load_block_type(category)._class_tags: parent.children.append(dest_usage_key) get_modulestore(parent.location).update_item(parent, request.user.id) return JsonResponse({ "locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key) })
def container_handler(request, usage_key_string): """ The restful handler for container xblock requests. GET html: returns the HTML page for editing a container json: not currently supported """ if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'): usage_key = UsageKey.from_string(usage_key_string) try: course, xblock, __ = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = _get_component_templates(course) ancestor_xblocks = [] parent = get_parent_xblock(xblock) while parent and parent.category != 'sequential': ancestor_xblocks.append(parent) parent = get_parent_xblock(parent) ancestor_xblocks.reverse() unit = ancestor_xblocks[0] if ancestor_xblocks else None unit_publish_state = compute_publish_state(unit) if unit else None return render_to_response( 'container.html', { 'context_course': course, # Needed only for display of menus at top of page. 'xblock': xblock, 'unit_publish_state': unit_publish_state, 'xblock_locator': usage_key, 'unit': None if not ancestor_xblocks else ancestor_xblocks[0], 'ancestor_xblocks': ancestor_xblocks, 'component_templates': json.dumps(component_templates), }) else: return HttpResponseBadRequest("Only supports html requests")
def _create_item(request): """View for create items.""" usage_key = UsageKey.from_string(request.json['parent_locator']) category = request.json['category'] display_name = request.json.get('display_name') if not has_course_access(request.user, usage_key.course_key): raise PermissionDenied() parent = get_modulestore(category).get_item(usage_key) dest_usage_key = usage_key.replace(category=category, name=uuid4().hex) # get the metadata, display_name, and definition from the request metadata = {} data = None template_id = request.json.get('boilerplate') if template_id: clz = parent.runtime.load_block_type(category) if clz is not None: template = clz.get_template(template_id) if template is not None: metadata = template.get('metadata', {}) data = template.get('data') if display_name is not None: metadata['display_name'] = display_name get_modulestore(category).create_and_save_xmodule( dest_usage_key, definition_data=data, metadata=metadata, system=parent.runtime, ) # TODO replace w/ nicer accessor if not 'detached' in parent.runtime.load_block_type(category)._class_tags: parent.children.append(dest_usage_key) get_modulestore(parent.location).update_item(parent, request.user.id) return JsonResponse({"locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key)})
def container_handler(request, usage_key_string): """ The restful handler for container xblock requests. GET html: returns the HTML page for editing a container json: not currently supported """ if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'): usage_key = UsageKey.from_string(usage_key_string) try: course, xblock, __ = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = _get_component_templates(course) ancestor_xblocks = [] parent = get_parent_xblock(xblock) while parent and parent.category != 'sequential': ancestor_xblocks.append(parent) parent = get_parent_xblock(parent) ancestor_xblocks.reverse() unit = ancestor_xblocks[0] if ancestor_xblocks else None unit_publish_state = compute_publish_state(unit) if unit else None return render_to_response('container.html', { 'context_course': course, # Needed only for display of menus at top of page. 'xblock': xblock, 'unit_publish_state': unit_publish_state, 'xblock_locator': usage_key, 'unit': None if not ancestor_xblocks else ancestor_xblocks[0], 'ancestor_xblocks': ancestor_xblocks, 'component_templates': json.dumps(component_templates), }) else: return HttpResponseBadRequest("Only supports html requests")
def unit_handler(request, usage_key_string): """ The restful handler for unit-specific requests. GET html: return html page for editing a unit json: not currently supported """ if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'): usage_key = UsageKey.from_string(usage_key_string) try: course, item, lms_link = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = _get_component_templates(course) xblocks = item.get_children() # TODO (cpennington): If we share units between courses, # this will need to change to check permissions correctly so as # to pick the correct parent subsection containing_subsection = get_parent_xblock(item) containing_section = get_parent_xblock(containing_subsection) # cdodge hack. We're having trouble previewing drafts via jump_to redirect # so let's generate the link url here # need to figure out where this item is in the list of children as the # preview will need this index = 1 for child in containing_subsection.get_children(): if child.location == item.location: break index = index + 1 preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE') preview_lms_link = ( u'//{preview_lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}' ).format(preview_lms_base=preview_lms_base, lms_base=settings.LMS_BASE, org=course.location.org, course=course.location.course, course_name=course.location.name, section=containing_section.location.name, subsection=containing_subsection.location.name, index=index) return render_to_response( 'unit.html', { 'context_course': course, 'unit': item, 'unit_usage_key': usage_key, 'child_usage_keys': [block.scope_ids.usage_id for block in xblocks], 'component_templates': json.dumps(component_templates), 'draft_preview_link': preview_lms_link, 'published_preview_link': lms_link, 'subsection': containing_subsection, 'release_date': (get_default_time_display(containing_subsection.start) if containing_subsection.start is not None else None), 'section': containing_section, 'new_unit_category': 'vertical', 'unit_state': compute_publish_state(item), 'published_date': (get_default_time_display(item.published_date) if item.published_date is not None else None), }) else: return HttpResponseBadRequest("Only supports html requests")
def unit_handler(request, usage_key_string): """ The restful handler for unit-specific requests. GET html: return html page for editing a unit json: not currently supported """ if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'): usage_key = UsageKey.from_string(usage_key_string) try: course, item, lms_link = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = _get_component_templates(course) xblocks = item.get_children() # TODO (cpennington): If we share units between courses, # this will need to change to check permissions correctly so as # to pick the correct parent subsection containing_subsection = get_parent_xblock(item) containing_section = get_parent_xblock(containing_subsection) # cdodge hack. We're having trouble previewing drafts via jump_to redirect # so let's generate the link url here # need to figure out where this item is in the list of children as the # preview will need this index = 1 for child in containing_subsection.get_children(): if child.location == item.location: break index = index + 1 preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE') preview_lms_link = ( u'//{preview_lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}' ).format( preview_lms_base=preview_lms_base, lms_base=settings.LMS_BASE, org=course.location.org, course=course.location.course, course_name=course.location.name, section=containing_section.location.name, subsection=containing_subsection.location.name, index=index ) return render_to_response('unit.html', { 'context_course': course, 'unit': item, 'unit_usage_key': usage_key, 'child_usage_keys': [block.scope_ids.usage_id for block in xblocks], 'component_templates': json.dumps(component_templates), 'draft_preview_link': preview_lms_link, 'published_preview_link': lms_link, 'subsection': containing_subsection, 'release_date': ( get_default_time_display(containing_subsection.start) if containing_subsection.start is not None else None ), 'section': containing_section, 'new_unit_category': 'vertical', 'unit_state': compute_publish_state(item), 'published_date': ( get_default_time_display(item.published_date) if item.published_date is not None else None ), }) else: return HttpResponseBadRequest("Only supports html requests")
def _get_usage_key(self, resp): """ Returns the usage key from the response returned by a create operation. """ usage_key_string = json.loads(resp.content).get('locator') return UsageKey.from_string(usage_key_string)
def xblock_handler(request, usage_key_string): """ 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 unicode representation of the UsageKeys 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 usage_key_string 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 (unicode representation of a UsageKey) for the created xblock (minus children) is returned. """ if usage_key_string: usage_key = UsageKey.from_string(usage_key_string) if not has_course_access(request.user, usage_key.course_key): raise PermissionDenied() if request.method == 'GET': accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if '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(usage_key)) # TODO: pass fields to _get_module_info and only return those rsp = _get_module_info(usage_key) 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(usage_key, delete_children, delete_all_versions, request.user) else: # Since we have a usage_key, we are updating an existing xblock. return _save_item( request, usage_key, 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_usage_key = UsageKey.from_string(request.json['parent_locator']) duplicate_source_usage_key = UsageKey.from_string(request.json['duplicate_source_locator']) dest_usage_key = _duplicate_item( parent_usage_key, duplicate_source_usage_key, request.json.get('display_name'), request.user, ) return JsonResponse({"locator": unicode(dest_usage_key)}) else: return _create_item(request) else: return HttpResponseBadRequest( "Only instance creation is supported without a usage key.", content_type="text/plain" )
def subsection_handler(request, usage_key_string): """ The restful handler for subsection-specific requests. GET html: return html page for editing a subsection json: not currently supported """ if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'): usage_key = UsageKey.from_string(usage_key_string) try: course, item, lms_link = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() preview_link = get_lms_link_for_item(usage_key, preview=True) # make sure that location references a 'sequential', otherwise return # BadRequest if item.location.category != 'sequential': return HttpResponseBadRequest() parent = get_parent_xblock(item) # remove all metadata from the generic dictionary that is presented in a # more normalized UI. We only want to display the XBlocks fields, not # the fields from any mixins that have been added fields = getattr(item, 'unmixed_class', item.__class__).fields policy_metadata = dict( (field.name, field.read_from(item)) for field in fields.values() if field.name not in ['display_name', 'start', 'due', 'format'] and field.scope == Scope.settings) can_view_live = False subsection_units = item.get_children() for unit in subsection_units: state = compute_publish_state(unit) if state in (PublishState.public, PublishState.draft): can_view_live = True break return render_to_response( 'edit_subsection.html', { 'subsection': item, 'context_course': course, 'new_unit_category': 'vertical', 'lms_link': lms_link, 'preview_link': preview_link, 'course_graders': json.dumps( CourseGradingModel.fetch(usage_key.course_key).graders), 'parent_item': parent, 'locator': usage_key, 'policy_metadata': policy_metadata, 'subsection_units': subsection_units, 'can_view_live': can_view_live }) else: return HttpResponseBadRequest("Only supports html requests")
def test_repr(self): testurn = u'edx:mit.eecs+6002x+{}+published+{}+problem+{}+HW3'.format( CourseLocator.BRANCH_PREFIX, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX ) testobj = UsageKey.from_string(testurn) self.assertEqual("BlockUsageLocator(CourseLocator(u'mit.eecs', u'6002x', u'published', None), u'problem', u'HW3')", repr(testobj))
def _save_item(request, usage_key, data=None, children=None, metadata=None, nullout=None, grader_type=None, publish=None): """ Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata. nullout means to truly set the field to None whereas nones in metadata mean to unset them (so they revert to default). """ store = get_modulestore(usage_key) try: existing_item = store.get_item(usage_key) except ItemNotFoundError: if usage_key.category in CREATE_IF_NOT_FOUND: # New module at this location, for pages that are not pre-created. # Used for course info handouts. store.create_and_save_xmodule(usage_key) existing_item = store.get_item(usage_key) else: raise except InvalidLocationError: log.error("Can't find item by location.") return JsonResponse({"error": "Can't find item by location: " + unicode(usage_key)}, 404) old_metadata = own_metadata(existing_item) if publish: if publish == 'make_private': _xmodule_recurse( existing_item, lambda i: modulestore().unpublish(i.location), ignore_exception=ItemNotFoundError ) elif publish == 'create_draft': # This recursively clones the existing item location to a draft location (the draft is # implicit, because modulestore is a Draft modulestore) _xmodule_recurse( existing_item, lambda i: modulestore().convert_to_draft(i.location), ignore_exception=DuplicateItemError ) if data: # TODO Allow any scope.content fields not just "data" (exactly like the get below this) existing_item.data = data else: data = existing_item.get_explicitly_set_fields_by_scope(Scope.content) if children is not None: children_usage_keys = [ UsageKey.from_string(child) for child in children ] existing_item.children = children_usage_keys # also commit any metadata which might have been passed along if nullout is not None or metadata is not None: # the postback is not the complete metadata, as there's system metadata which is # not presented to the end-user for editing. So let's use the original (existing_item) and # 'apply' the submitted metadata, so we don't end up deleting system metadata. if nullout is not None: for metadata_key in nullout: setattr(existing_item, metadata_key, None) # update existing metadata with submitted metadata (which can be partial) # IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If # the intent is to make it None, use the nullout field if metadata is not None: for metadata_key, value in metadata.items(): field = existing_item.fields[metadata_key] if value is None: field.delete_from(existing_item) else: try: value = field.from_json(value) except ValueError: return JsonResponse({"error": "Invalid data"}, 400) field.write_to(existing_item, value) if existing_item.category == 'video': manage_video_subtitles_save(existing_item, request.user, old_metadata, generate_translation=True) # commit to datastore store.update_item(existing_item, request.user.id) result = { 'id': unicode(usage_key), 'data': data, 'metadata': own_metadata(existing_item) } if grader_type is not None: result.update(CourseGradingModel.update_section_grader_type(existing_item, grader_type, request.user)) # Make public after updating the xblock, in case the caller asked # for both an update and a publish. if publish and publish == 'make_public': def _publish(block): # This is super gross, but prevents us from publishing something that # we shouldn't. Ideally, all modulestores would have a consistant # interface for publishing. However, as of now, only the DraftMongoModulestore # does, so we have to check for the attribute explicitly. store = get_modulestore(block.location) store.publish(block.location, request.user.id) _xmodule_recurse( existing_item, _publish ) # Note that children aren't being returned until we have a use case. return JsonResponse(result)
def subsection_handler(request, usage_key_string): """ The restful handler for subsection-specific requests. GET html: return html page for editing a subsection json: not currently supported """ if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'): usage_key = UsageKey.from_string(usage_key_string) try: course, item, lms_link = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() preview_link = get_lms_link_for_item(usage_key, preview=True) # make sure that location references a 'sequential', otherwise return # BadRequest if item.location.category != 'sequential': return HttpResponseBadRequest() parent = get_parent_xblock(item) # remove all metadata from the generic dictionary that is presented in a # more normalized UI. We only want to display the XBlocks fields, not # the fields from any mixins that have been added fields = getattr(item, 'unmixed_class', item.__class__).fields policy_metadata = dict( (field.name, field.read_from(item)) for field in fields.values() if field.name not in ['display_name', 'start', 'due', 'format'] and field.scope == Scope.settings ) can_view_live = False subsection_units = item.get_children() for unit in subsection_units: state = compute_publish_state(unit) if state in (PublishState.public, PublishState.draft): can_view_live = True break return render_to_response( 'edit_subsection.html', { 'subsection': item, 'context_course': course, 'new_unit_category': 'vertical', 'lms_link': lms_link, 'preview_link': preview_link, 'course_graders': json.dumps(CourseGradingModel.fetch(usage_key.course_key).graders), 'parent_item': parent, 'locator': usage_key, 'policy_metadata': policy_metadata, 'subsection_units': subsection_units, 'can_view_live': can_view_live } ) else: return HttpResponseBadRequest("Only supports html requests")
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 == 'student_view' and xblock_has_own_studio_page(xblock): context = { 'runtime_type': 'studio', 'container_view': False, 'read_only': is_read_only, 'root_xblock': xblock, } # 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_context': context, 'xblock': xblock, 'locator': usage_key, }) return JsonResponse({ 'html': html, 'resources': [], }) 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: fragment.content = render_to_string('component.html', { 'xblock_context': context, '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)