def _create_item(request): """View for create items.""" parent_locator = request.json['parent_locator'] usage_key = usage_key_with_run(parent_locator) if not has_studio_write_access(request.user, usage_key.course_key): raise PermissionDenied() category = request.json['category'] if isinstance(usage_key, LibraryUsageLocator): # Only these categories are supported at this time. if category not in ['html', 'problem', 'video']: return HttpResponseBadRequest( "Category '%s' not supported for Libraries" % category, content_type='text/plain') created_block = create_xblock( parent_locator=parent_locator, user=request.user, category=category, display_name=request.json.get('display_name'), boilerplate=request.json.get('boilerplate')) return JsonResponse({ "locator": unicode(created_block.location), "courseKey": unicode(created_block.location.course_key) })
def xblock_outline_handler(request, usage_key_string): """ The restful handler for requests for XBlock information about the block and its children. This is used by the course outline in particular to construct the tree representation of a course. """ usage_key = usage_key_with_run(usage_key_string) if not has_studio_read_access(request.user, usage_key.course_key): raise PermissionDenied() response_format = request.REQUEST.get("format", "html") if response_format == "json" or "application/json" in request.META.get("HTTP_ACCEPT", "application/json"): store = modulestore() with store.bulk_operations(usage_key.course_key): root_xblock = store.get_item(usage_key, depth=None) return JsonResponse( create_xblock_info( root_xblock, include_child_info=True, course_outline=True, include_children_predicate=lambda xblock: not xblock.category == "vertical", ) ) else: return Http404
def _create_item(request): """View for create items.""" parent_locator = request.json['parent_locator'] usage_key = usage_key_with_run(parent_locator) if not has_studio_write_access(request.user, usage_key.course_key): raise PermissionDenied() category = request.json['category'] if isinstance(usage_key, LibraryUsageLocator): # Only these categories are supported at this time. if category not in ['html', 'problem', 'video']: return HttpResponseBadRequest( "Category '%s' not supported for Libraries" % category, content_type='text/plain' ) created_block = create_xblock( parent_locator=parent_locator, user=request.user, category=category, display_name=request.json.get('display_name'), boilerplate=request.json.get('boilerplate') ) return JsonResponse( {"locator": unicode(created_block.location), "courseKey": unicode(created_block.location.course_key)} )
def xblock_container_handler(request, usage_key_string): """ The restful handler for requests for XBlock information about the block and its children. This is used by the container page in particular to get additional information about publish state and ancestor state. """ usage_key = usage_key_with_run(usage_key_string) if not has_studio_read_access(request.user, usage_key.course_key): raise PermissionDenied() response_format = request.REQUEST.get('format', 'html') if response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'): with modulestore().bulk_operations(usage_key.course_key): response = _get_module_info( _get_xblock(usage_key, request.user), include_ancestor_info=True, include_publishing_info=True ) return JsonResponse(response) else: return Http404
def xblock_outline_handler(request, usage_key_string): """ The restful handler for requests for XBlock information about the block and its children. This is used by the course outline in particular to construct the tree representation of a course. """ usage_key = usage_key_with_run(usage_key_string) if not has_studio_read_access(request.user, usage_key.course_key): raise PermissionDenied() response_format = request.REQUEST.get('format', 'html') if response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'): store = modulestore() root_xblock = store.get_item(usage_key) return JsonResponse(create_xblock_info( root_xblock, include_child_info=True, course_outline=True, include_children_predicate=lambda xblock: not xblock.category == 'vertical' )) else: return Http404
def _save_xblock(user, xblock, data=None, children_strings=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 = modulestore() # Perform all xblock changes within a (single-versioned) transaction with store.bulk_operations(xblock.location.course_key): # Don't allow updating an xblock and discarding changes in a single operation (unsupported by UI). if publish == "discard_changes": store.revert_to_published(xblock.location, user.id) # Returning the same sort of result that we do for other save operations. In the future, # we may want to return the full XBlockInfo. return JsonResponse({'id': unicode(xblock.location)}) old_metadata = own_metadata(xblock) old_content = xblock.get_explicitly_set_fields_by_scope(Scope.content) if data: # TODO Allow any scope.content fields not just "data" (exactly like the get below this) xblock.data = data else: data = old_content['data'] if 'data' in old_content else None if children_strings is not None: children = [] for child_string in children_strings: children.append(usage_key_with_run(child_string)) # if new children have been added, remove them from their old parents new_children = set(children) - set(xblock.children) for new_child in new_children: old_parent_location = store.get_parent_location(new_child) if old_parent_location: old_parent = store.get_item(old_parent_location) old_parent.children.remove(new_child) old_parent = _update_with_callback(old_parent, user) else: # the Studio UI currently doesn't present orphaned children, so assume this is an error return JsonResponse( { "error": "Invalid data, possibly caused by concurrent authors." }, 400) # make sure there are no old children that became orphans # In a single-author (no-conflict) scenario, all children in the persisted list on the server should be # present in the updated list. If there are any children that have been dropped as part of this update, # then that would be an error. # # We can be even more restrictive in a multi-author (conflict), by returning an error whenever # len(old_children) > 0. However, that conflict can still be "merged" if the dropped child had been # re-parented. Hence, the check for the parent in the any statement below. # # Note that this multi-author conflict error should not occur in modulestores (such as Split) that support # atomic write transactions. In Split, if there was another author who moved one of the "old_children" # into another parent, then that child would have been deleted from this parent on the server. However, # this is error could occur in modulestores (such as Draft) that do not support atomic write-transactions old_children = set(xblock.children) - set(children) if any( store.get_parent_location(old_child) == xblock.location for old_child in old_children): # since children are moved as part of a single transaction, orphans should not be created return JsonResponse( { "error": "Invalid data, possibly caused by concurrent authors." }, 400) # set the children on the xblock xblock.children = children # 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(xblock, 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 = xblock.fields[metadata_key] if value is None: field.delete_from(xblock) else: try: value = field.from_json(value) except ValueError as verr: reason = _("Invalid data") if verr.message: reason = _("Invalid data ({details})").format( details=verr.message) return JsonResponse({"error": reason}, 400) field.write_to(xblock, value) # update the xblock and call any xblock callbacks xblock = _update_with_callback(xblock, user, old_metadata, old_content) # for static tabs, their containing course also records their display name if xblock.location.category == 'static_tab': course = store.get_course(xblock.location.course_key) # find the course's reference to this tab and update the name. static_tab = CourseTabList.get_tab_by_slug(course.tabs, xblock.location.name) # only update if changed if static_tab and static_tab['name'] != xblock.display_name: static_tab['name'] = xblock.display_name store.update_item(course, user.id) result = { 'id': unicode(xblock.location), 'data': data, 'metadata': own_metadata(xblock) } if grader_type is not None: result.update( CourseGradingModel.update_section_grader_type( xblock, grader_type, user)) # If publish is set to 'republish' and this item is not in direct only categories and has previously been published, # then this item should be republished. This is used by staff locking to ensure that changing the draft # value of the staff lock will also update the published version, but only at the unit level. if publish == 'republish' and xblock.category not in DIRECT_ONLY_CATEGORIES: if modulestore().has_published_version(xblock): publish = 'make_public' # Make public after updating the xblock, in case the caller asked for both an update and a publish. # Used by Bok Choy tests and by republishing of staff locks. if publish == 'make_public': modulestore().publish(xblock.location, user.id) # Note that children aren't being returned until we have a use case. return JsonResponse(result, encoder=EdxJSONEncoder)
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_handler(request, usage_key_string): """ The restful handler for xblock requests. DELETE json: delete this xblock instance from the course. 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 or PATCH 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: 'make_public': publish the content 'republish': publish this item *only* if it was previously published 'discard_changes' - reverts to the last published version Note: If 'discard_changes', the other fields will not be used; that is, it is not possible to update and discard changes in a single operation. 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 = usage_key_with_run(usage_key_string) access_check = has_studio_read_access if request.method == 'GET' else has_studio_write_access if not access_check(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 with modulestore().bulk_operations(usage_key.course_key): response = _get_module_info( _get_xblock(usage_key, request.user)) return JsonResponse(response) else: return HttpResponse(status=406) elif request.method == 'DELETE': _delete_item(usage_key, request.user) return JsonResponse() else: # Since we have a usage_key, we are updating an existing xblock. return _save_xblock( request.user, _get_xblock(usage_key, request.user), data=request.json.get('data'), children_strings=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 = usage_key_with_run( request.json['parent_locator']) duplicate_source_usage_key = usage_key_with_run( request.json['duplicate_source_locator']) source_course = duplicate_source_usage_key.course_key dest_course = parent_usage_key.course_key if (not has_studio_write_access(request.user, dest_course) or not has_studio_read_access(request.user, source_course)): raise PermissionDenied() dest_usage_key = _duplicate_item( parent_usage_key, duplicate_source_usage_key, request.user, request.json.get('display_name'), ) return JsonResponse({ "locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key) }) else: return _create_item(request) else: return HttpResponseBadRequest( "Only instance creation is supported without a usage key.", content_type="text/plain")
def xblock_handler(request, usage_key_string): """ The restful handler for xblock requests. DELETE json: delete this xblock instance from the course. 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 or PATCH 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: 'make_public': publish the content 'republish': publish this item *only* if it was previously published 'discard_changes' - reverts to the last published version Note: If 'discard_changes', the other fields will not be used; that is, it is not possible to update and discard changes in a single operation. 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 = usage_key_with_run(usage_key_string) access_check = has_studio_read_access if request.method == 'GET' else has_studio_write_access if not access_check(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 with modulestore().bulk_operations(usage_key.course_key): response = _get_module_info(_get_xblock(usage_key, request.user)) return JsonResponse(response) else: return HttpResponse(status=406) elif request.method == 'DELETE': _delete_item(usage_key, request.user) return JsonResponse() else: # Since we have a usage_key, we are updating an existing xblock. return _save_xblock( request.user, _get_xblock(usage_key, request.user), data=request.json.get('data'), children_strings=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 = usage_key_with_run(request.json['parent_locator']) duplicate_source_usage_key = usage_key_with_run(request.json['duplicate_source_locator']) source_course = duplicate_source_usage_key.course_key dest_course = parent_usage_key.course_key if ( not has_studio_write_access(request.user, dest_course) or not has_studio_read_access(request.user, source_course) ): raise PermissionDenied() dest_usage_key = _duplicate_item( parent_usage_key, duplicate_source_usage_key, request.user, request.json.get('display_name'), ) return JsonResponse({"locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key)}) else: return _create_item(request) else: return HttpResponseBadRequest( "Only instance creation is supported without a usage key.", content_type="text/plain" )
def _save_xblock(user, xblock, data=None, children_strings=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 = modulestore() # Perform all xblock changes within a (single-versioned) transaction with store.bulk_operations(xblock.location.course_key): # Don't allow updating an xblock and discarding changes in a single operation (unsupported by UI). if publish == "discard_changes": store.revert_to_published(xblock.location, user.id) # Returning the same sort of result that we do for other save operations. In the future, # we may want to return the full XBlockInfo. return JsonResponse({'id': unicode(xblock.location)}) old_metadata = own_metadata(xblock) old_content = xblock.get_explicitly_set_fields_by_scope(Scope.content) if data: # TODO Allow any scope.content fields not just "data" (exactly like the get below this) xblock.data = data else: data = old_content['data'] if 'data' in old_content else None if children_strings is not None: children = [] for child_string in children_strings: children.append(usage_key_with_run(child_string)) # if new children have been added, remove them from their old parents new_children = set(children) - set(xblock.children) for new_child in new_children: old_parent_location = store.get_parent_location(new_child) if old_parent_location: old_parent = store.get_item(old_parent_location) old_parent.children.remove(new_child) old_parent = _update_with_callback(old_parent, user) else: # the Studio UI currently doesn't present orphaned children, so assume this is an error return JsonResponse({"error": "Invalid data, possibly caused by concurrent authors."}, 400) # make sure there are no old children that became orphans # In a single-author (no-conflict) scenario, all children in the persisted list on the server should be # present in the updated list. If there are any children that have been dropped as part of this update, # then that would be an error. # # We can be even more restrictive in a multi-author (conflict), by returning an error whenever # len(old_children) > 0. However, that conflict can still be "merged" if the dropped child had been # re-parented. Hence, the check for the parent in the any statement below. # # Note that this multi-author conflict error should not occur in modulestores (such as Split) that support # atomic write transactions. In Split, if there was another author who moved one of the "old_children" # into another parent, then that child would have been deleted from this parent on the server. However, # this is error could occur in modulestores (such as Draft) that do not support atomic write-transactions old_children = set(xblock.children) - set(children) if any( store.get_parent_location(old_child) == xblock.location for old_child in old_children ): # since children are moved as part of a single transaction, orphans should not be created return JsonResponse({"error": "Invalid data, possibly caused by concurrent authors."}, 400) # set the children on the xblock xblock.children = children # 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(xblock, 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 = xblock.fields[metadata_key] if value is None: field.delete_from(xblock) else: try: value = field.from_json(value) except ValueError as verr: reason = _("Invalid data") if verr.message: reason = _("Invalid data ({details})").format(details=verr.message) return JsonResponse({"error": reason}, 400) field.write_to(xblock, value) # update the xblock and call any xblock callbacks xblock = _update_with_callback(xblock, user, old_metadata, old_content) # for static tabs, their containing course also records their display name if xblock.location.category == 'static_tab': course = store.get_course(xblock.location.course_key) # find the course's reference to this tab and update the name. static_tab = CourseTabList.get_tab_by_slug(course.tabs, xblock.location.name) # only update if changed if static_tab and static_tab['name'] != xblock.display_name: static_tab['name'] = xblock.display_name store.update_item(course, user.id) result = { 'id': unicode(xblock.location), 'data': data, 'metadata': own_metadata(xblock) } if grader_type is not None: result.update(CourseGradingModel.update_section_grader_type(xblock, grader_type, user)) # If publish is set to 'republish' and this item is not in direct only categories and has previously been published, # then this item should be republished. This is used by staff locking to ensure that changing the draft # value of the staff lock will also update the published version, but only at the unit level. if publish == 'republish' and xblock.category not in DIRECT_ONLY_CATEGORIES: if modulestore().has_published_version(xblock): publish = 'make_public' # Make public after updating the xblock, in case the caller asked for both an update and a publish. # Used by Bok Choy tests and by republishing of staff locks. if publish == 'make_public': modulestore().publish(xblock.location, user.id) # Note that children aren't being returned until we have a use case. return JsonResponse(result, encoder=EdxJSONEncoder)
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)