def xblock_view_handler(request, usage_key_string, view_name): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ usage_key = usage_key_with_run(usage_key_string) if not has_course_access(request.user, usage_key.course_key): raise PermissionDenied() accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/json' in accept_header: store = modulestore() xblock = store.get_item(usage_key) container_views = ['container_preview', 'reorderable_container_child_preview'] # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly xblock.runtime.wrappers.append(partial( wrap_xblock, 'StudioRuntime', usage_id_serializer=unicode, request_token=request_token(request), )) if view_name == STUDIO_VIEW: try: fragment = xblock.render(STUDIO_VIEW) # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=w0703 log.debug("unable to render studio_view for %r", xblock, exc_info=True) fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)})) store.update_item(xblock, request.user.id) elif view_name in (PREVIEW_VIEWS + container_views): is_pages_view = view_name == STUDENT_VIEW # Only the "Pages" view uses student view in Studio # Determine the items to be shown as reorderable. Note that the view # 'reorderable_container_child_preview' is only rendered for xblocks that # are being shown in a reorderable container, so the xblock is automatically # added to the list. reorderable_items = set() if view_name == 'reorderable_container_child_preview': reorderable_items.add(xblock.location) # Set up the context to be passed to each XBlock's render method. context = { 'is_pages_view': is_pages_view, # This setting disables the recursive wrapping of xblocks 'is_unit_page': is_unit(xblock), 'root_xblock': xblock if (view_name == 'container_preview') else None, 'reorderable_items': reorderable_items } fragment = get_preview_fragment(request, xblock, context) # Note that the container view recursively adds headers into the preview fragment, # so only the "Pages" view requires that this extra wrapper be included. if is_pages_view: fragment.content = render_to_string('component.html', { 'xblock_context': context, 'xblock': xblock, 'locator': usage_key, 'preview': fragment.content, 'label': xblock.display_name or xblock.scope_ids.block_type, }) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': fragment.content, 'resources': hashed_resources.items() }) else: return HttpResponse(status=406)
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ def safe_get_username(user_id): """ Guard against bad user_ids, like the infamous "**replace_user**". Note that this will ignore our special known IDs (ModuleStoreEnum.UserID). We should consider adding special handling for those values. :param user_id: the user id to get the username of :return: username, or None if the user does not exist or user_id is None """ if user_id: try: return User.objects.get(id=user_id).username except: # pylint: disable=bare-except pass return None is_xblock_unit = is_unit(xblock, parent_xblock) has_changes = modulestore().has_changes(xblock) if graders is None: graders = CourseGradingModel.fetch(xblock.location.course_key).graders # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and (course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, ) else: child_info = None # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None if xblock.category != 'course': visibility_state = _compute_visibility_state(xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version(xblock) xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_date) if xblock.published_date else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": release_date, "visibility_state": visibility_state, "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, } if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock(xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False # Currently, 'edited_by', 'published_by', and 'release_date_from' are only used by the # container page when rendering a unit. Since they are expensive to compute, only include them for units # that are not being rendered on the course outline. if is_xblock_unit and not course_outline: xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by) xblock_info["published_by"] = safe_get_username(xblock.published_by) xblock_info["currently_visible_to_students"] = is_currently_visible_to_students(xblock) if release_date: xblock_info["release_date_from"] = _get_release_date_from(xblock) if visibility_state == VisibilityState.staff_only: xblock_info["staff_lock_from"] = _get_staff_lock_from(xblock) else: xblock_info["staff_lock_from"] = None if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([child["staff_only_message"] for child in child_info["children"]]) else: xblock_info["staff_only_message"] = False return xblock_info
def xblock_view_handler(request, usage_key_string, view_name): """ The restful handler for requests for rendered xblock views. Returns a json object containing two keys: html: The rendered html of the view resources: A list of tuples where the first element is the resource hash, and the second is the resource description """ usage_key = usage_key_with_run(usage_key_string) if not has_course_author_access(request.user, usage_key.course_key): raise PermissionDenied() accept_header = request.META.get('HTTP_ACCEPT', 'application/json') if 'application/json' in accept_header: store = modulestore() xblock = store.get_item(usage_key) container_views = [ 'container_preview', 'reorderable_container_child_preview' ] # wrap the generated fragment in the xmodule_editor div so that the javascript # can bind to it correctly xblock.runtime.wrappers.append( partial( wrap_xblock, 'StudioRuntime', usage_id_serializer=unicode, request_token=request_token(request), )) if view_name == STUDIO_VIEW: try: fragment = xblock.render(STUDIO_VIEW) # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=broad-except log.debug("unable to render studio_view for %r", xblock, exc_info=True) fragment = Fragment( render_to_string('html_error.html', {'message': str(exc)})) elif view_name in (PREVIEW_VIEWS + container_views): is_pages_view = view_name == STUDENT_VIEW # Only the "Pages" view uses student view in Studio # Determine the items to be shown as reorderable. Note that the view # 'reorderable_container_child_preview' is only rendered for xblocks that # are being shown in a reorderable container, so the xblock is automatically # added to the list. reorderable_items = set() if view_name == 'reorderable_container_child_preview': reorderable_items.add(xblock.location) # Set up the context to be passed to each XBlock's render method. context = { 'is_pages_view': is_pages_view, # This setting disables the recursive wrapping of xblocks 'is_unit_page': is_unit(xblock), 'root_xblock': xblock if (view_name == 'container_preview') else None, 'reorderable_items': reorderable_items } fragment = get_preview_fragment(request, xblock, context) # Note that the container view recursively adds headers into the preview fragment, # so only the "Pages" view requires that this extra wrapper be included. if is_pages_view: fragment.content = render_to_string( 'component.html', { 'xblock_context': context, 'xblock': xblock, 'locator': usage_key, 'preview': fragment.content, 'label': xblock.display_name or xblock.scope_ids.block_type, }) else: raise Http404 hashed_resources = OrderedDict() for resource in fragment.resources: hashed_resources[hash_resource(resource)] = resource return JsonResponse({ 'html': fragment.content, 'resources': hashed_resources.items() }) else: return HttpResponse(status=406)
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ def safe_get_username(user_id): """ Guard against bad user_ids, like the infamous "**replace_user**". Note that this will ignore our special known IDs (ModuleStoreEnum.UserID). We should consider adding special handling for those values. :param user_id: the user id to get the username of :return: username, or None if the user does not exist or user_id is None """ if user_id: try: return User.objects.get(id=user_id).username except: # pylint: disable=bare-except pass return None is_xblock_unit = is_unit(xblock, parent_xblock) # this should not be calculated for Sections and Subsections on Unit page has_changes = modulestore().has_changes(xblock) if ( is_xblock_unit or course_outline) else None if graders is None: graders = CourseGradingModel.fetch(xblock.location.course_key).graders # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and ( course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, ) else: child_info = None # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set release_date = get_default_time_display( xblock.start) if xblock.start != DEFAULT_START_DATE else None if xblock.category != 'course': visibility_state = _compute_visibility_state( xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version(xblock) xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_on) if xblock.published_on else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": release_date, "visibility_state": visibility_state, "lti_enabled": xblock.fields['lti_enabled'].is_set_on(xblock), "lti_url": "", "lti_key": "", "lti_secret": "", "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, } if settings.FEATURES.get('ENABLE_AS_LTI_TOOL_PROVIDER', False): store = modulestore() course = store.get_course(xblock.location.course_key) if course.lti_enabled: course_id = xblock.location.course_key usr = int(xblock.subtree_edited_by) existing_components = LTIComponent.objects.filter( course_id=course_id, module_id=xblock.location, user_id=usr) if xblock.fields['lti_enabled'].is_set_on(xblock): if len(existing_components) == 0: key = ''.join( random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(8)) secret = ''.join( random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(16)) lti_component = LTIComponent(user_id=usr, course_id=course_id, module_id=str( xblock.location), key=key, secret=secret) lti_component.save() else: key = existing_components[0].key secret = existing_components[0].secret xblock_info["lti_url"] = str(xblock.location) xblock_info["lti_key"] = key xblock_info["lti_secret"] = secret else: if len(existing_components) > 0: for existing_component in existing_components: existing_components.delete() else: xblock_info["lti_url"] = "disabled" xblock_info["lti_key"] = "disabled" xblock_info["lti_secret"] = "disabled" else: xblock_info["lti_url"] = "disabled" xblock_info["lti_key"] = "disabled" xblock_info["lti_secret"] = "disabled" if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info( xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock( xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False # Currently, 'edited_by', 'published_by', and 'release_date_from' are only used by the # container page when rendering a unit. Since they are expensive to compute, only include them for units # that are not being rendered on the course outline. if is_xblock_unit and not course_outline: xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by) xblock_info["published_by"] = safe_get_username(xblock.published_by) xblock_info[ "currently_visible_to_students"] = is_currently_visible_to_students( xblock) if release_date: xblock_info["release_date_from"] = _get_release_date_from(xblock) if visibility_state == VisibilityState.staff_only: xblock_info["staff_lock_from"] = _get_staff_lock_from(xblock) else: xblock_info["staff_lock_from"] = None if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([ child["staff_only_message"] for child in child_info["children"] ]) else: xblock_info["staff_only_message"] = False return xblock_info
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'): try: usage_key = UsageKey.from_string(usage_key_string) except InvalidKeyError: # Raise Http404 on invalid 'usage_key_string' raise Http404 with modulestore().bulk_operations(usage_key.course_key): try: course, xblock, lms_link, preview_lms_link = _get_item_in_course( request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = get_component_templates(course) ancestor_xblocks = [] parent = get_parent_xblock(xblock) action = request.GET.get('action', 'view') is_unit_page = is_unit(xblock) unit = xblock if is_unit_page else None is_first = True while parent: if unit is None and is_unit(parent): unit = parent elif parent.category != 'sequential': current_block = { 'block': parent, 'children': parent.get_children(), 'is_last': is_first } is_first = False ancestor_xblocks.append(current_block) parent = get_parent_xblock(parent) ancestor_xblocks.reverse() assert unit is not None, "Could not determine unit page" subsection = get_parent_xblock(unit) assert subsection is not None, "Could not determine parent subsection from unit " + six.text_type( unit.location) section = get_parent_xblock(subsection) assert section is not None, "Could not determine ancestor section from unit " + six.text_type( unit.location) # for the sequence navigator prev_url, next_url = get_sibling_urls(subsection) # these are quoted here because they'll end up in a query string on the page, # and quoting with mako will trigger the xss linter... prev_url = quote_plus(prev_url) if prev_url else None next_url = quote_plus(next_url) if next_url else None # Fetch the XBlock info for use by the container page. Note that it includes information # about the block's ancestors and siblings for use by the Unit Outline. xblock_info = create_xblock_info( xblock, include_ancestor_info=is_unit_page) if is_unit_page: add_container_page_publishing_info(xblock, xblock_info) # need to figure out where this item is in the list of children as the # preview will need this index = 1 for child in subsection.get_children(): if child.location == unit.location: break index += 1 return render_to_response( 'container.html', { 'language_code': request.LANGUAGE_CODE, 'context_course': course, # Needed only for display of menus at top of page. 'action': action, 'xblock': xblock, 'xblock_locator': xblock.location, 'unit': unit, 'is_unit_page': is_unit_page, 'subsection': subsection, 'section': section, 'position': index, 'prev_url': prev_url, 'next_url': next_url, 'new_unit_category': 'vertical', 'outline_url': '{url}?format=concise'.format( url=reverse_course_url('course_handler', course.id)), 'ancestor_xblocks': ancestor_xblocks, 'component_templates': component_templates, 'xblock_info': xblock_info, 'draft_preview_link': preview_lms_link, 'published_preview_link': lms_link, 'templates': CONTAINER_TEMPLATES }) else: return HttpResponseBadRequest("Only supports HTML requests")
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'): try: usage_key = UsageKey.from_string(usage_key_string) except InvalidKeyError: # Raise Http404 on invalid 'usage_key_string' raise Http404 with modulestore().bulk_operations(usage_key.course_key): try: course, xblock, lms_link, preview_lms_link = _get_item_in_course( request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = get_component_templates(course) ancestor_xblocks = [] parent = get_parent_xblock(xblock) action = request.GET.get('action', 'view') is_unit_page = is_unit(xblock) unit = xblock if is_unit_page else None while parent and parent.category != 'course': if unit is None and is_unit(parent): unit = parent ancestor_xblocks.append(parent) parent = get_parent_xblock(parent) ancestor_xblocks.reverse() assert unit is not None, "Could not determine unit page" subsection = get_parent_xblock(unit) assert subsection is not None, "Could not determine parent subsection from unit " + unicode( unit.location) section = get_parent_xblock(subsection) assert section is not None, "Could not determine ancestor section from unit " + unicode( unit.location) # Fetch the XBlock info for use by the container page. Note that it includes information # about the block's ancestors and siblings for use by the Unit Outline. xblock_info = create_xblock_info( xblock, include_ancestor_info=is_unit_page) if is_unit_page: add_container_page_publishing_info(xblock, xblock_info) # need to figure out where this item is in the list of children as the # preview will need this index = 1 for child in subsection.get_children(): if child.location == unit.location: break index += 1 return render_to_response( 'container.html', { 'context_course': course, # Needed only for display of menus at top of page. 'action': action, 'xblock': xblock, 'xblock_locator': xblock.location, 'unit': unit, 'is_unit_page': is_unit_page, 'subsection': subsection, 'section': section, 'new_unit_category': 'vertical', 'ancestor_xblocks': ancestor_xblocks, 'component_templates': component_templates, 'xblock_info': xblock_info, 'draft_preview_link': preview_lms_link, 'published_preview_link': lms_link, 'templates': CONTAINER_TEMPLATES }) else: return HttpResponseBadRequest("Only supports HTML requests")
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None, user=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ is_library_block = isinstance(xblock.location, LibraryUsageLocator) is_xblock_unit = is_unit(xblock, parent_xblock) # this should not be calculated for Sections and Subsections on Unit page or for library blocks has_changes = None if (is_xblock_unit or course_outline) and not is_library_block: has_changes = modulestore().has_changes(xblock) if graders is None: if not is_library_block: graders = CourseGradingModel.fetch(xblock.location.course_key).graders else: graders = [] # Filter the graders data as needed graders = _filter_entrance_exam_grader(graders) # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and (course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, user=user ) else: child_info = None if xblock.category != 'course': visibility_state = _compute_visibility_state(xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version(xblock) if not is_library_block else None # defining the default value 'True' for delete, drag and add new child actions in xblock_actions for each xblock. xblock_actions = {'deletable': True, 'draggable': True, 'childAddable': True} explanatory_message = None # is_entrance_exam is inherited metadata. if xblock.category == 'chapter' and getattr(xblock, "is_entrance_exam", None): # Entrance exam section should not be deletable, draggable and not have 'New Subsection' button. xblock_actions['deletable'] = xblock_actions['childAddable'] = xblock_actions['draggable'] = False if parent_xblock is None: parent_xblock = get_parent_xblock(xblock) # Translators: The {pct_sign} here represents the percent sign, i.e., '%' # in many languages. This is used to avoid Transifex's misinterpreting of # '% o'. The percent sign is also translatable as a standalone string. explanatory_message = _('Students must score {score}{pct_sign} or higher to access course materials.').format( score=int(parent_xblock.entrance_exam_minimum_score_pct * 100), # Translators: This is the percent sign. It will be used to represent # a percent value out of 100, e.g. "58%" means "58/100". pct_sign=_('%')) xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_on) if published and xblock.published_on else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": _get_release_date(xblock, user), "visibility_state": visibility_state, "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, "actions": xblock_actions, "explanatory_message": explanatory_message } # Entrance exam subsection should be hidden. in_entrance_exam is inherited metadata, all children will have it. if xblock.category == 'sequential' and getattr(xblock, "in_entrance_exam", False): xblock_info["is_header_visible"] = False if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock(xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([child["staff_only_message"] for child in child_info["children"]]) else: xblock_info["staff_only_message"] = False return xblock_info
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ is_library_block = isinstance(xblock.location, LibraryUsageLocator) is_xblock_unit = is_unit(xblock, parent_xblock) # this should not be calculated for Sections and Subsections on Unit page or for library blocks has_changes = None if (is_xblock_unit or course_outline) and not is_library_block: has_changes = modulestore().has_changes(xblock) if graders is None: if not is_library_block: graders = CourseGradingModel.fetch( xblock.location.course_key).graders else: graders = [] # Filter the graders data as needed graders = _filter_entrance_exam_grader(graders) # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and ( course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, ) else: child_info = None if xblock.category != 'course': visibility_state = _compute_visibility_state( xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version( xblock) if not is_library_block else None # defining the default value 'True' for delete, drag and add new child actions in xblock_actions for each xblock. xblock_actions = { 'deletable': True, 'draggable': True, 'childAddable': True } explanatory_message = None # is_entrance_exam is inherited metadata. if xblock.category == 'chapter' and getattr(xblock, "is_entrance_exam", None): # Entrance exam section should not be deletable, draggable and not have 'New Subsection' button. xblock_actions['deletable'] = xblock_actions[ 'childAddable'] = xblock_actions['draggable'] = False if parent_xblock is None: parent_xblock = get_parent_xblock(xblock) explanatory_message = _( 'Students must score {score}% or higher to access course materials.' ).format(score=int(parent_xblock.entrance_exam_minimum_score_pct * 100)) xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_on) if published and xblock.published_on else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": _get_release_date(xblock), "visibility_state": visibility_state, "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, "actions": xblock_actions, "explanatory_message": explanatory_message } # Entrance exam subsection should be hidden. in_entrance_exam is inherited metadata, all children will have it. if xblock.category == 'sequential' and getattr(xblock, "in_entrance_exam", False): xblock_info["is_header_visible"] = False if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info( xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock( xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([ child["staff_only_message"] for child in child_info["children"] ]) else: xblock_info["staff_only_message"] = False return xblock_info
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'): try: usage_key = UsageKey.from_string(usage_key_string) except InvalidKeyError: # Raise Http404 on invalid 'usage_key_string' raise Http404 with modulestore().bulk_operations(usage_key.course_key): try: course, xblock, lms_link, preview_lms_link = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = get_component_templates(course) ancestor_xblocks = [] parent = get_parent_xblock(xblock) action = request.REQUEST.get('action', 'view') is_unit_page = is_unit(xblock) unit = xblock if is_unit_page else None while parent and parent.category != 'course': if unit is None and is_unit(parent): unit = parent ancestor_xblocks.append(parent) parent = get_parent_xblock(parent) ancestor_xblocks.reverse() assert unit is not None, "Could not determine unit page" subsection = get_parent_xblock(unit) assert subsection is not None, "Could not determine parent subsection from unit " + unicode(unit.location) section = get_parent_xblock(subsection) assert section is not None, "Could not determine ancestor section from unit " + unicode(unit.location) # Fetch the XBlock info for use by the container page. Note that it includes information # about the block's ancestors and siblings for use by the Unit Outline. xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page) if is_unit_page: add_container_page_publishing_info(xblock, xblock_info) # need to figure out where this item is in the list of children as the # preview will need this index = 1 for child in subsection.get_children(): if child.location == unit.location: break index += 1 return render_to_response('container.html', { 'context_course': course, # Needed only for display of menus at top of page. 'action': action, 'xblock': xblock, 'xblock_locator': xblock.location, 'unit': unit, 'is_unit_page': is_unit_page, 'subsection': subsection, 'section': section, 'new_unit_category': 'vertical', 'ancestor_xblocks': ancestor_xblocks, 'component_templates': json.dumps(component_templates), 'xblock_info': xblock_info, 'draft_preview_link': preview_lms_link, 'published_preview_link': lms_link, 'keywords_supported': get_keywords_supported(), 'templates': CONTAINER_TEMPATES }) else: return HttpResponseBadRequest("Only supports HTML requests")
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, lms_link = _get_item_in_course(request, usage_key) except ItemNotFoundError: return HttpResponseBadRequest() component_templates = get_component_templates(course) ancestor_xblocks = [] parent = get_parent_xblock(xblock) action = request.REQUEST.get('action', 'view') is_unit_page = is_unit(xblock) unit = xblock if is_unit_page else None while parent and parent.category != 'course': if unit is None and is_unit(parent): unit = parent ancestor_xblocks.append(parent) parent = get_parent_xblock(parent) ancestor_xblocks.reverse() assert unit is not None, "Could not determine unit page" subsection = get_parent_xblock(unit) assert subsection is not None, "Could not determine parent subsection from unit " + unicode(unit.location) section = get_parent_xblock(subsection) assert section is not None, "Could not determine ancestor section from unit " + unicode(unit.location) # Fetch the XBlock info for use by the container page. Note that it includes information # about the block's ancestors and siblings for use by the Unit Outline. xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page) # Create the link for preview. preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE') # need to figure out where this item is in the list of children as the # preview will need this index = 1 for child in subsection.get_children(): if child.location == unit.location: break index += 1 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=section.location.name, subsection=subsection.location.name, index=index ) return render_to_response('container.html', { 'context_course': course, # Needed only for display of menus at top of page. 'action': action, 'xblock': xblock, 'xblock_locator': xblock.location, 'unit': unit, 'is_unit_page': is_unit_page, 'subsection': subsection, 'section': section, 'new_unit_category': 'vertical', 'ancestor_xblocks': ancestor_xblocks, 'component_templates': json.dumps(component_templates), 'xblock_info': xblock_info, 'draft_preview_link': preview_lms_link, 'published_preview_link': lms_link, }) else: return HttpResponseBadRequest("Only supports HTML requests")
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ def safe_get_username(user_id): """ Guard against bad user_ids, like the infamous "**replace_user**". Note that this will ignore our special known IDs (ModuleStoreEnum.UserID). We should consider adding special handling for those values. :param user_id: the user id to get the username of :return: username, or None if the user does not exist or user_id is None """ if user_id: try: return User.objects.get(id=user_id).username except: # pylint: disable=bare-except pass return None is_xblock_unit = is_unit(xblock, parent_xblock) is_unit_with_changes = is_xblock_unit and modulestore().has_changes(xblock) if graders is None: graders = CourseGradingModel.fetch(xblock.location.course_key).graders # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and ( course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, ) else: child_info = None # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set release_date = get_default_time_display( xblock.start) if xblock.start != DEFAULT_START_DATE else None published = modulestore().compute_publish_state( xblock) != PublishState.private xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_date) if xblock.published_date else None, 'studio_url': xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": release_date, "visibility_state": _compute_visibility_state(xblock, child_info, is_unit_with_changes) if not xblock.category == 'course' else None, "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), } if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info( xblock, course_outline) if child_info: xblock_info['child_info'] = child_info # Currently, 'edited_by', 'published_by', and 'release_date_from', and 'has_changes' are only used by the # container page when rendering a unit. Since they are expensive to compute, only include them for units # that are not being rendered on the course outline. if is_xblock_unit and not course_outline: xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by) xblock_info["published_by"] = safe_get_username(xblock.published_by) xblock_info[ "currently_visible_to_students"] = is_currently_visible_to_students( xblock) xblock_info['has_changes'] = is_unit_with_changes if release_date: xblock_info["release_date_from"] = _get_release_date_from(xblock) return xblock_info
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ is_library_block = isinstance(xblock.location, LibraryUsageLocator) is_xblock_unit = is_unit(xblock, parent_xblock) # this should not be calculated for Sections and Subsections on Unit page or for library blocks has_changes = None if (is_xblock_unit or course_outline) and not is_library_block: has_changes = modulestore().has_changes(xblock) if graders is None: if not is_library_block: graders = CourseGradingModel.fetch(xblock.location.course_key).graders else: graders = [] # Filter the graders data as needed graders = _filter_entrance_exam_grader(graders) # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and (course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, ) else: child_info = None if xblock.category != 'course': visibility_state = _compute_visibility_state(xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version(xblock) if not is_library_block else None #instead of adding a new feature directly into xblock-info, we should add them into override_type. override_type = {} if getattr(xblock, "is_entrance_exam", None): override_type['is_entrance_exam'] = xblock.is_entrance_exam xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_on) if published and xblock.published_on else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": _get_release_date(xblock), "visibility_state": visibility_state, "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, "override_type": override_type, } if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock(xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([child["staff_only_message"] for child in child_info["children"]]) else: xblock_info["staff_only_message"] = False return xblock_info
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 create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None): """ Creates the information needed for client-side XBlockInfo. If data or metadata are not specified, their information will not be added (regardless of whether or not the xblock actually has data or metadata). There are three optional boolean parameters: include_ancestor_info - if true, ancestor info is added to the response include_child_info - if true, direct child info is included in the response course_outline - if true, the xblock is being rendered on behalf of the course outline. There are certain expensive computations that do not need to be included in this case. In addition, an optional include_children_predicate argument can be provided to define whether or not a particular xblock should have its children included. """ is_library_block = isinstance(xblock.location, LibraryUsageLocator) is_xblock_unit = is_unit(xblock, parent_xblock) # this should not be calculated for Sections and Subsections on Unit page or for library blocks has_changes = None if (is_xblock_unit or course_outline) and not is_library_block: has_changes = modulestore().has_changes(xblock) if graders is None: if not is_library_block: graders = CourseGradingModel.fetch( xblock.location.course_key).graders else: graders = [] # Filter the graders data as needed graders = _filter_entrance_exam_grader(graders) # Compute the child info first so it can be included in aggregate information for the parent should_visit_children = include_child_info and ( course_outline and not is_xblock_unit or not course_outline) if should_visit_children and xblock.has_children: child_info = _create_xblock_child_info( xblock, course_outline, graders, include_children_predicate=include_children_predicate, ) else: child_info = None if xblock.category != 'course': visibility_state = _compute_visibility_state( xblock, child_info, is_xblock_unit and has_changes) else: visibility_state = None published = modulestore().has_published_version( xblock) if not is_library_block else None #instead of adding a new feature directly into xblock-info, we should add them into override_type. override_type = {} if getattr(xblock, "is_entrance_exam", None): override_type['is_entrance_exam'] = xblock.is_entrance_exam xblock_info = { "id": unicode(xblock.location), "display_name": xblock.display_name_with_default, "category": xblock.category, "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None, "published": published, "published_on": get_default_time_display(xblock.published_on) if published and xblock.published_on else None, "studio_url": xblock_studio_url(xblock, parent_xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": _get_release_date(xblock), "visibility_state": visibility_state, "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock), "start": xblock.fields['start'].to_json(xblock.start), "graded": xblock.graded, "due_date": get_default_time_display(xblock.due), "due": xblock.fields['due'].to_json(xblock.due), "format": xblock.format, "course_graders": json.dumps([grader.get('type') for grader in graders]), "has_changes": has_changes, "override_type": override_type, } if data is not None: xblock_info["data"] = data if metadata is not None: xblock_info["metadata"] = metadata if include_ancestor_info: xblock_info['ancestor_info'] = _create_xblock_ancestor_info( xblock, course_outline) if child_info: xblock_info['child_info'] = child_info if visibility_state == VisibilityState.staff_only: xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock( xblock, parent_xblock) else: xblock_info["ancestor_has_staff_lock"] = False if course_outline: if xblock_info["has_explicit_staff_lock"]: xblock_info["staff_only_message"] = True elif child_info and child_info["children"]: xblock_info["staff_only_message"] = all([ child["staff_only_message"] for child in child_info["children"] ]) else: xblock_info["staff_only_message"] = False return xblock_info