def test_creation(self): """ The user that creates a library should have instructor (admin) and staff permissions """ # self.library has been auto-created by the staff user. self.assertTrue(has_studio_write_access(self.user, self.lib_key)) self.assertTrue(has_studio_read_access(self.user, self.lib_key)) # Make sure the user was actually assigned the instructor role and not just using is_staff superpowers: self.assertTrue(CourseInstructorRole(self.lib_key).has_user(self.user)) # Now log out and ensure we are forbidden from creating a library: self.client.logout() self._assert_cannot_create_library(expected_code=302) # 302 redirect to login expected # Now check that logged-in users without CourseCreator role cannot create libraries self._login_as_non_staff_user(logout_first=False) with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}): self._assert_cannot_create_library(expected_code=403) # 403 user is not CourseCreator # Now check that logged-in users with CourseCreator role can create libraries add_user_with_status_granted(self.user, self.non_staff_user) with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}): lib_key2 = self._create_library(library="lib2", display_name="Test Library 2") library2 = modulestore().get_library(lib_key2) self.assertIsNotNone(library2)
def import_from_blockstore(self, dest_block, blockstore_block_id): """ Imports a block from a blockstore-based learning context (usually a content library) into modulestore, as a new child of dest_block. Any existing children of dest_block are replaced. This is only used by LibrarySourcedBlock. It should verify first that the number of block IDs is reasonable. """ dest_key = dest_block.scope_ids.usage_id if not isinstance(dest_key, BlockUsageLocator): raise TypeError( "Destination {} should be a modulestore course.".format( dest_key)) if self.user_id is None: raise ValueError( "Cannot check user permissions - LibraryTools user_id is None") dest_course_key = dest_key.context_key user = User.objects.get(id=self.user_id) if not has_studio_write_access(user, dest_course_key): raise PermissionDenied() # Read the source block; this will also confirm that user has permission to read it. orig_block = load_block(UsageKey.from_string(blockstore_block_id), user) with self.store.bulk_operations(dest_course_key): new_block_id = self._import_block(orig_block, dest_key) # Remove any existing children that are no longer used for old_child_id in set(dest_block.children) - set([new_block_id]): self.store.delete_item(old_child_id, self.user_id) # If this was called from a handler, it will save dest_block at the end, so we must update # dest_block.children to avoid it saving the old value of children and deleting the new ones. dest_block.children = self.store.get_item(dest_key).children
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 _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 _get_course_and_check_access(course_key, user, depth=0): """ Internal method used to calculate and return the locator and course module for the view functions in this file. """ if not has_studio_write_access(user, course_key): raise PermissionDenied() course_module = modulestore().get_course(course_key, depth=depth) return course_module
def library_blocks_view(library, user, response_format): """ The main view of a course's content library. Shows all the XBlocks in the library, and allows adding/editing/deleting them. Can be called with response_format="json" to get a JSON-formatted list of the XBlocks in the library along with library metadata. Assumes that read permissions have been checked before calling this. """ assert isinstance(library.location.library_key, LibraryLocator) assert isinstance(library.location, LibraryUsageLocator) children = library.children if response_format == "json": # The JSON response for this request is short and sweet: prev_version = library.runtime.course_entry.structure[ 'previous_version'] return JsonResponse({ "display_name": library.display_name, "library_id": unicode(library.location.library_key), "version": unicode(library.runtime.course_entry.course_key.version), "previous_version": unicode(prev_version) if prev_version else None, "blocks": [unicode(x) for x in children], }) can_edit = has_studio_write_access(user, library.location.library_key) xblock_info = create_xblock_info(library, include_ancestor_info=False, graders=[]) component_templates = get_component_templates( library, library=True) if can_edit else [] return render_to_response( 'library.html', { 'can_edit': can_edit, 'context_library': library, 'component_templates': json.dumps(component_templates), 'xblock_info': xblock_info, 'templates': CONTAINER_TEMPATES, 'lib_users_url': reverse_library_url('manage_library_users', unicode(library.location.library_key)), })
def validate_user(self, request, course_id): """Validate user has studio access to this course""" try: course_key = CourseKey.from_string(course_id) if not has_studio_write_access(request.user, course_key): msg = "Access Denied. User: {} does not have instructor rights"\ " in this course"\ .format(request.user.username) raise ValidationError(msg, 403) # Something wrong with CourseKey except InvalidKeyError as e: msg = "Course: {} not found".format(course_id) raise ValidationError(msg, 404)
def publish_notes(self, data, suffix=''): """Make a note public only for the teacher """ student = self.__get_current_user() timecoded = KNote.objects.get(pk=data.get("pk")) if ((timecoded.timecoded_comment.user.pk == self.scope_ids.user_id) and (has_studio_write_access(student, self.scope_ids.usage_id.course_key))): is_public = False if (data.get("public") == "true"): is_public = True timecoded.is_public = data.get("public") timecoded.save() return {'result': 'success'} else : return {'error': 'bad credential'}
def format_library_for_view(library, this_user=None): """ Return a dict of the data which the view requires for each library """ if this_user: user = this_user else: user = request.user return { 'display_name': library.display_name, 'library_key': unicode(library.location.library_key), 'url': reverse_library_url('library_handler', unicode(library.location.library_key)), 'org': library.display_org_with_default, 'number': library.display_number_with_default, 'can_edit': has_studio_write_access(user, library.location.library_key), }
def test_creation(self): """ The user that creates a library should have instructor (admin) and staff permissions """ # self.library has been auto-created by the staff user. self.assertTrue(has_studio_write_access(self.user, self.lib_key)) self.assertTrue(has_studio_read_access(self.user, self.lib_key)) # Make sure the user was actually assigned the instructor role and not just using is_staff superpowers: self.assertTrue(CourseInstructorRole(self.lib_key).has_user(self.user)) # Now log out and ensure we are forbidden from creating a library: self.client.logout() self._assert_cannot_create_library(expected_code=302) # 302 redirect to login expected # Now create a non-staff user with no permissions: self._login_as_non_staff_user(logout_first=False) self.assertFalse(CourseCreatorRole().has_user(self.non_staff_user)) # Now check that logged-in users without any permissions cannot create libraries with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}): self._assert_cannot_create_library()
def test_creation(self): """ The user that creates a library should have instructor (admin) and staff permissions """ # self.library has been auto-created by the staff user. self.assertTrue(has_studio_write_access(self.user, self.lib_key)) self.assertTrue(has_studio_read_access(self.user, self.lib_key)) # Make sure the user was actually assigned the instructor role and not just using is_staff superpowers: self.assertTrue(CourseInstructorRole(self.lib_key).has_user(self.user)) # Now log out and ensure we are forbidden from creating a library: self.client.logout() self._assert_cannot_create_library(expected_code=302) # 302 redirect to login expected # Now create a non-staff user with no permissions: self._login_as_non_staff_user(logout_first=False) self.assertFalse(CourseCreatorRole().has_user(self.non_staff_user)) # Now check that logged-in users without any permissions cannot create libraries with patch.dict("django.conf.settings.FEATURES", {"ENABLE_CREATOR_GROUP": True}): self._assert_cannot_create_library()
def library_blocks_view(library, user, response_format): """ The main view of a course's content library. Shows all the XBlocks in the library, and allows adding/editing/deleting them. Can be called with response_format="json" to get a JSON-formatted list of the XBlocks in the library along with library metadata. Assumes that read permissions have been checked before calling this. """ assert isinstance(library.location.library_key, LibraryLocator) assert isinstance(library.location, LibraryUsageLocator) children = library.children if response_format == "json": # The JSON response for this request is short and sweet: prev_version = library.runtime.course_entry.structure['previous_version'] return JsonResponse({ "display_name": library.display_name, "library_id": unicode(library.location.library_key), "version": unicode(library.runtime.course_entry.course_key.version), "previous_version": unicode(prev_version) if prev_version else None, "blocks": [unicode(x) for x in children], }) can_edit = has_studio_write_access(user, library.location.library_key) xblock_info = create_xblock_info(library, include_ancestor_info=False, graders=[]) component_templates = get_component_templates(library, library=True) if can_edit else [] return render_to_response('library.html', { 'can_edit': can_edit, 'context_library': library, 'component_templates': json.dumps(component_templates), 'xblock_info': xblock_info, 'templates': CONTAINER_TEMPATES, 'lib_users_url': reverse_library_url('manage_library_users', unicode(library.location.library_key)), })
def transcript_delete_handler(request, course_key_string, edx_video_id, language_code): """ View to delete a transcript file. Arguments: request: A WSGI request object course_key_string: Course key identifying a course. edx_video_id: edX video identifier whose transcript need to be deleted. language_code: transcript's language code. Returns - A 404 if the user does not have required permisions - A 200 if transcript is deleted without any error(s) """ # Check whether the feature is available for this course. course_key = CourseKey.from_string(course_key_string) # User needs to have studio write access for this course. if not has_studio_write_access(request.user, course_key): return HttpResponseNotFound() delete_video_transcript(video_id=edx_video_id, language_code=language_code) return JsonResponse(status=200)
def can_write(self, course_key): """ Does the user have read access to the given course/library? """ return has_studio_write_access(self._request.user, course_key)
def _create_item(request): """View for create items.""" usage_key = usage_key_with_run(request.json['parent_locator']) if not has_studio_write_access(request.user, usage_key.course_key): raise PermissionDenied() category = request.json['category'] display_name = request.json.get('display_name') 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' ) store = modulestore() with store.bulk_operations(usage_key.course_key): parent = store.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 # Entrance Exams: Chapter module positioning child_position = None if settings.FEATURES.get('ENTRANCE_EXAMS', False): is_entrance_exam = request.json.get('is_entrance_exam', False) if category == 'chapter' and is_entrance_exam: metadata['is_entrance_exam'] = is_entrance_exam metadata['in_entrance_exam'] = True # Inherited metadata, all children will have it child_position = 0 # TODO need to fix components that are sending definition_data as strings, instead of as dicts # For now, migrate them into dicts here. if isinstance(data, basestring): data = {'data': data} created_block = store.create_child( request.user.id, usage_key, dest_usage_key.block_type, block_id=dest_usage_key.block_id, definition_data=data, metadata=metadata, runtime=parent.runtime, position=child_position ) # Entrance Exams: Grader assignment if settings.FEATURES.get('ENTRANCE_EXAMS', False): course = store.get_course(usage_key.course_key) if hasattr(course, 'entrance_exam_enabled') and course.entrance_exam_enabled: if category == 'sequential' and request.json.get('parent_locator') == course.entrance_exam_id: grader = { "type": "Entrance Exam", "min_count": 0, "drop_count": 0, "short_label": "Entrance", "weight": 0 } grading_model = CourseGradingModel.update_grader_from_json( course.id, grader, request.user ) CourseGradingModel.update_section_grader_type( created_block, grading_model['type'], request.user ) # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so # if we add one then we need to also add it to the policy information (i.e. metadata) # we should remove this once we can break this reference from the course to static tabs if category == 'static_tab': display_name = display_name or _("Empty") # Prevent name being None course = store.get_course(dest_usage_key.course_key) course.tabs.append( StaticTab( name=display_name, url_slug=dest_usage_key.name, ) ) store.update_item(course, request.user.id) return JsonResponse( {"locator": unicode(created_block.location), "courseKey": unicode(created_block.location.course_key)} )
def test_no_global_admin_write_access(self): """ Test that global admins have no write access """ self.assertFalse(has_studio_write_access(self.global_admin, self.ccx_course_key))
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 test_no_staff_write_access(self): """ Test that course staff have no write access """ self.assertFalse(has_studio_write_access(self.staff, self.ccx_course_key))
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 student_view(self, context): """ Show all knotes for the current user and the owner(s) which are in public state. """ student = self.__get_current_user() comment = KNoteList.objects.get_or_create_note_list(student, self.__get_xblock_key()) """Find all knotes ordered by seconds""" timecoded_data_set = self.__list_notes() timecoded_data_array = [] for timecoded_data in timecoded_data_set: """Convert Knote objects (python) to Knote objects (Javascript) """ obj = {"time": timecoded_data.seconds, "value":timecoded_data.content, "user": self.scope_ids.user_id , "public": timecoded_data.is_public, "mine": (self.scope_ids.user_id == timecoded_data.timecoded_comment.user.pk) , "id": timecoded_data.id} timecoded_data_array.append(obj) # Load the HTML fragment from within the package and fill in the template html_str = pkg_resources.resource_string(__name__, "static/html/videoknotes.html") frag = Fragment(unicode(html_str).format(self=self, href=self.href, comment_id=comment.pk)) css_str = pkg_resources.resource_string(__name__, "static/css/style.css") frag.add_css(unicode(css_str)) frag.add_javascript_url("http://api.dmcdn.net/all.js") frag.add_javascript_url("https://www.youtube.com/iframe_api") javascript_array = ["static/js/core/KNotesListener.js", "static/js/core/KNote.js", "static/js/core/KNotesIterator.js", "static/js/core/KNotesList.js", "static/js/players/PlayerFactory.js", "static/js/players/DailymotionAdapter.js", "static/js/players/YoutubeAdapter.js", "static/js/core/KNotesView.js", "static/js/core/KNotesPlugin.js", "static/vendors/swfobject.js"] for element in javascript_array: js_str = pkg_resources.resource_string(__name__, element) frag.add_javascript(unicode(js_str)) js_str = pkg_resources.resource_string(__name__, "static/js/videoknotes.js") frag.add_javascript(unicode(js_str)) frag.initialize_js('VideoKNotesBlock', {"video" : self.href, "notes" : timecoded_data_array, "can_publish" : has_studio_write_access(student, self.scope_ids.usage_id.course_key)}) return frag
def test_no_staff_write_access(self): """ Test that course staff have no write access """ self.assertFalse( has_studio_write_access(self.staff, self.ccx_course_key))
def test_no_global_admin_write_access(self): """ Test that global admins have no write access """ self.assertFalse( has_studio_write_access(self.global_admin, self.ccx_course_key))