def test_visual_progress_happy_path_visual_switch_disabled(self): self._make_site_config(True) with waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, True): with waffle.waffle().override(waffle.ENABLE_VISUAL_PROGRESS, False): with override_waffle_flag(waffle.waffle_flag(), active=True): assert waffle.visual_progress_enabled( self.course_key) is True
def render_to_fragment(self, request, course_id=None, page_context=None, **kwargs): """ Renders the course outline as a fragment. """ course_key = CourseKey.from_string(course_id) course_overview = get_course_overview_with_access( request.user, 'load', course_key, check_if_enrolled=True) course_block_tree = get_course_outline_block_tree(request, course_id) if not course_block_tree: return None # TODO: EDUCATOR-2283 Remove 'show_visual_progress' from context # and remove the check for it in the HTML file show_visual_progress = ( completion_waffle.visual_progress_enabled(course_key) and self.user_enrolled_after_completion_collection( request.user, course_key)) context = { 'csrf': csrf(request)['csrf_token'], 'course': course_overview, 'blocks': course_block_tree, 'show_visual_progress': show_visual_progress } # TODO: EDUCATOR-2283 Remove this check when the waffle flag is turned on in production if course_experience_waffle.new_course_outline_enabled( course_key=course_key): resume_block = get_resume_block(course_block_tree) if not resume_block: self.mark_first_unit_to_resume(course_block_tree) xblock_display_names = self.create_xblock_id_and_name_dict( course_block_tree) gated_content = self.get_content_milestones(request, course_key) context['gated_content'] = gated_content context['xblock_display_names'] = xblock_display_names # TODO: EDUCATOR-2283 Rename this file to course-outline-fragment.html html = render_to_string( 'course_experience/course-outline-fragment-new.html', context) return Fragment(html) else: content_milestones = self.get_content_milestones_old( request, course_key) context['gated_content'] = content_milestones # TODO: EDUCATOR-2283 Remove this file html = render_to_string( 'course_experience/course-outline-fragment-old.html', context) return Fragment(html)
def render_to_fragment(self, request, course_id=None, page_context=None, **kwargs): """ Renders the course outline as a fragment. """ course_key = CourseKey.from_string(course_id) course_overview = get_course_overview_with_access(request.user, 'load', course_key, check_if_enrolled=True) course_block_tree = get_course_outline_block_tree(request, course_id) if not course_block_tree: return None # TODO: EDUCATOR-2283 Remove 'show_visual_progress' from context # and remove the check for it in the HTML file show_visual_progress = ( completion_waffle.visual_progress_enabled(course_key) and self.user_enrolled_after_completion_collection(request.user, course_key) ) context = { 'csrf': csrf(request)['csrf_token'], 'course': course_overview, 'blocks': course_block_tree, 'show_visual_progress': show_visual_progress } # TODO: EDUCATOR-2283 Remove this check when the waffle flag is turned on in production if course_experience_waffle.new_course_outline_enabled(course_key=course_key): xblock_display_names = self.create_xblock_id_and_name_dict(course_block_tree) gated_content = self.get_content_milestones(request, course_key) context['gated_content'] = gated_content context['xblock_display_names'] = xblock_display_names # TODO: EDUCATOR-2283 Rename this file to course-outline-fragment.html html = render_to_string('course_experience/course-outline-fragment-new.html', context) return Fragment(html) else: content_milestones = self.get_content_milestones_old(request, course_key) context['gated_content'] = content_milestones # TODO: EDUCATOR-2283 Remove this file html = render_to_string('course_experience/course-outline-fragment-old.html', context) return Fragment(html)
def get_course_outline_block_tree(request, course_id): """ Returns the root block of the course outline, with children as blocks. """ def populate_children(block, all_blocks): """ Replace each child id with the full block for the child. Given a block, replaces each id in its children array with the full representation of that child, which will be looked up by id in the passed all_blocks dict. Recursively do the same replacement for children of those children. """ children = block.get('children', []) for i in range(len(children)): child_id = block['children'][i] child_detail = populate_children(all_blocks[child_id], all_blocks) block['children'][i] = child_detail return block def set_last_accessed_default(block): """ Set default of False for resume_block on all blocks. """ block['resume_block'] = False block['complete'] = False for child in block.get('children', []): set_last_accessed_default(child) def mark_blocks_completed(block, user, course_key): """ Walk course tree, marking block completion. Mark 'most recent completed block as 'resume_block' """ last_completed_child_position = BlockCompletion.get_latest_block_completed( user, course_key) if last_completed_child_position: # Mutex w/ NOT 'course_block_completions' recurse_mark_complete( course_block_completions=BlockCompletion. get_course_completions(user, course_key), latest_completion=last_completed_child_position, block=block) def recurse_mark_complete(course_block_completions, latest_completion, block): """ Helper function to walk course tree dict, marking blocks as 'complete' and 'last_complete' If all blocks are complete, mark parent block complete mark parent blocks of 'last_complete' as 'last_complete' :param course_block_completions: dict[course_completion_object] = completion_value :param latest_completion: course_completion_object :param block: course_outline_root_block block object or child block :return: block: course_outline_root_block block object or child block """ block_key = block.serializer.instance if course_block_completions.get(block_key): block['complete'] = True if block_key == latest_completion.full_block_key: block['resume_block'] = True if block.get('children'): for idx in range(len(block['children'])): recurse_mark_complete(course_block_completions, latest_completion, block=block['children'][idx]) if block['children'][idx]['resume_block'] is True: block['resume_block'] = True if len([ child['complete'] for child in block['children'] if child['complete'] ]) == len(block['children']): block['complete'] = True def mark_last_accessed(user, course_key, block): """ Recursively marks the branch to the last accessed block. """ block_key = block.serializer.instance student_module_dict = get_student_module_as_dict( user, course_key, block_key) last_accessed_child_position = student_module_dict.get('position') if last_accessed_child_position and block.get('children'): block['resume_block'] = True if last_accessed_child_position <= len(block['children']): last_accessed_child_block = block['children'][ last_accessed_child_position - 1] last_accessed_child_block['resume_block'] = True mark_last_accessed(user, course_key, last_accessed_child_block) else: # We should be using an id in place of position for last accessed. # However, while using position, if the child block is no longer accessible # we'll use the last child. block['children'][-1]['resume_block'] = True course_key = CourseKey.from_string(course_id) course_usage_key = modulestore().make_course_usage_key(course_key) # Deeper query for course tree traversing/marking complete # and last completed block block_types_filter = [ 'course', 'chapter', 'sequential', 'vertical', 'html', 'problem', 'video', 'discussion', 'drag-and-drop-v2' ] all_blocks = get_blocks(request, course_usage_key, user=request.user, nav_depth=3, requested_fields=[ 'children', 'display_name', 'type', 'due', 'graded', 'special_exam_info', 'show_gated_sections', 'format' ], block_types_filter=block_types_filter) course_outline_root_block = all_blocks['blocks'].get( all_blocks['root'], None) if course_outline_root_block: populate_children(course_outline_root_block, all_blocks['blocks']) set_last_accessed_default(course_outline_root_block) if visual_progress_enabled(course_key=course_key): mark_blocks_completed(block=course_outline_root_block, user=request.user, course_key=course_key) else: mark_last_accessed(request.user, course_key, course_outline_root_block) return course_outline_root_block
def test_visual_progress_happy_path_with_site_config(self): self._make_site_config(True) with waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, True): with waffle.waffle().override(waffle.ENABLE_VISUAL_PROGRESS, True): assert waffle.visual_progress_enabled(self.course_key) is True
def test_visual_progress_gating_site_disabled(self): self._make_site_config(False) assert waffle.visual_progress_enabled(self.course_key) is False
def test_visual_progress_gating_tracking_disabled(self): with waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, False): assert waffle.visual_progress_enabled(self.course_key) is False
def get_course_outline_block_tree(request, course_id): """ Returns the root block of the course outline, with children as blocks. """ def populate_children(block, all_blocks): """ Replace each child id with the full block for the child. Given a block, replaces each id in its children array with the full representation of that child, which will be looked up by id in the passed all_blocks dict. Recursively do the same replacement for children of those children. """ children = block.get('children', []) for i in range(len(children)): child_id = block['children'][i] child_detail = populate_children(all_blocks[child_id], all_blocks) block['children'][i] = child_detail return block def set_last_accessed_default(block): """ Set default of False for resume_block on all blocks. """ block['resume_block'] = False block['complete'] = False for child in block.get('children', []): set_last_accessed_default(child) def mark_blocks_completed(block, user, course_key): """ Walk course tree, marking block completion. Mark 'most recent completed block as 'resume_block' """ last_completed_child_position = BlockCompletion.get_latest_block_completed(user, course_key) if last_completed_child_position: # Mutex w/ NOT 'course_block_completions' recurse_mark_complete( course_block_completions=BlockCompletion.get_course_completions(user, course_key), latest_completion=last_completed_child_position, block=block ) def recurse_mark_complete(course_block_completions, latest_completion, block): """ Helper function to walk course tree dict, marking blocks as 'complete' and 'last_complete' If all blocks are complete, mark parent block complete mark parent blocks of 'last_complete' as 'last_complete' :param course_block_completions: dict[course_completion_object] = completion_value :param latest_completion: course_completion_object :param block: course_outline_root_block block object or child block :return: block: course_outline_root_block block object or child block """ block_key = block.serializer.instance if course_block_completions.get(block_key): block['complete'] = True if block_key == latest_completion.full_block_key: block['resume_block'] = True if block.get('children'): for idx in range(len(block['children'])): recurse_mark_complete( course_block_completions, latest_completion, block=block['children'][idx] ) if block['children'][idx]['resume_block'] is True: block['resume_block'] = True if len([child['complete'] for child in block['children'] if child['complete']]) == len(block['children']): block['complete'] = True def mark_last_accessed(user, course_key, block): """ Recursively marks the branch to the last accessed block. """ block_key = block.serializer.instance student_module_dict = get_student_module_as_dict(user, course_key, block_key) last_accessed_child_position = student_module_dict.get('position') if last_accessed_child_position and block.get('children'): block['resume_block'] = True if last_accessed_child_position <= len(block['children']): last_accessed_child_block = block['children'][last_accessed_child_position - 1] last_accessed_child_block['resume_block'] = True mark_last_accessed(user, course_key, last_accessed_child_block) else: # We should be using an id in place of position for last accessed. # However, while using position, if the child block is no longer accessible # we'll use the last child. block['children'][-1]['resume_block'] = True course_key = CourseKey.from_string(course_id) course_usage_key = modulestore().make_course_usage_key(course_key) # Deeper query for course tree traversing/marking complete # and last completed block block_types_filter = [ 'course', 'chapter', 'sequential', 'vertical', 'html', 'problem', 'video', 'discussion' ] all_blocks = get_blocks( request, course_usage_key, user=request.user, nav_depth=3, requested_fields=[ 'children', 'display_name', 'type', 'due', 'graded', 'special_exam_info', 'show_gated_sections', 'format' ], block_types_filter=block_types_filter ) course_outline_root_block = all_blocks['blocks'].get(all_blocks['root'], None) if course_outline_root_block: populate_children(course_outline_root_block, all_blocks['blocks']) set_last_accessed_default(course_outline_root_block) if visual_progress_enabled(course_key=course_key): mark_blocks_completed( block=course_outline_root_block, user=request.user, course_key=course_key ) else: mark_last_accessed(request.user, course_key, course_outline_root_block) return course_outline_root_block