""" Views to show a course outline. """ import datetime import re from completion import waffle as completion_waffle from django.contrib.auth.models import User from django.template.context_processors import csrf from django.template.loader import render_to_string from opaque_keys.edx.keys import CourseKey from pytz import UTC from waffle.models import Switch from web_fragments.fragment import Fragment from lms.djangoapps.courseware.courses import get_course_overview_with_access from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from student.models import CourseEnrollment from util.milestones_helpers import get_course_content_milestones from xmodule.course_module import COURSE_VISIBILITY_PUBLIC from xmodule.modulestore.django import modulestore from ..utils import get_course_outline_block_tree, get_resume_block DEFAULT_COMPLETION_TRACKING_START = datetime.datetime(2018, 1, 24, tzinfo=UTC) class CourseOutlineFragmentView(EdxFragmentView): """ Course outline fragment to be shown in the unified course view. """ def render_to_fragment(self, request, course_id, user_is_enrolled=True, **kwargs): # pylint: disable=arguments-differ """ 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=user_is_enrolled ) course = modulestore().get_course(course_key) course_block_tree = get_course_outline_block_tree( request, course_id, request.user if user_is_enrolled else None ) if not course_block_tree: return None context = { 'csrf': csrf(request)['csrf_token'], 'course': course_overview, 'due_date_display_format': course.due_date_display_format, 'blocks': course_block_tree, 'enable_links': user_is_enrolled or course.course_visibility == COURSE_VISIBILITY_PUBLIC, } resume_block = get_resume_block(course_block_tree) if user_is_enrolled else None 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 page_context = kwargs.get('page_context', None) if page_context: context['self_paced'] = page_context.get('pacing_type', 'instructor_paced') == 'self_paced' html = render_to_string('course_experience/course-outline-fragment.html', context) return Fragment(html) def create_xblock_id_and_name_dict(self, course_block_tree, xblock_display_names=None): """ Creates a dictionary mapping xblock IDs to their names, using a course block tree. """ if xblock_display_names is None: xblock_display_names = {} if not course_block_tree.get('authorization_denial_reason'): if course_block_tree.get('id'): xblock_display_names[course_block_tree['id']] = course_block_tree['display_name'] if course_block_tree.get('children'): for child in course_block_tree['children']: self.create_xblock_id_and_name_dict(child, xblock_display_names) return xblock_display_names def get_content_milestones(self, request, course_key): """ Returns dict of subsections with prerequisites and whether the prerequisite has been completed or not """ def _get_key_of_prerequisite(namespace): return re.sub('.gating', '', namespace) all_course_milestones = get_course_content_milestones(course_key) uncompleted_prereqs = { milestone['content_id'] for milestone in get_course_content_milestones(course_key, user_id=request.user.id) } gated_content = { milestone['content_id']: { 'completed_prereqs': milestone['content_id'] not in uncompleted_prereqs, 'prerequisite': _get_key_of_prerequisite(milestone['namespace']) } for milestone in all_course_milestones } return gated_content def user_enrolled_after_completion_collection(self, user, course_key): """ Checks that the user has enrolled in the course after 01/24/2018, the date that the completion API began data collection. If the user has enrolled in the course before this date, they may see incomplete collection data. This is a temporary check until all active enrollments are created after the date. """ user = User.objects.get(username=user) try: user_enrollment = CourseEnrollment.objects.get( user=user, course_id=course_key, is_active=True ) return user_enrollment.created > self._completion_data_collection_start() except CourseEnrollment.DoesNotExist: return False def _completion_data_collection_start(self): """ Returns the date that the ENABLE_COMPLETION_TRACKING waffle switch was enabled. """ # pylint: disable=protected-access switch_name = completion_waffle.waffle()._namespaced_name(completion_waffle.ENABLE_COMPLETION_TRACKING) try: return Switch.objects.get(name=switch_name).created except Switch.DoesNotExist: return DEFAULT_COMPLETION_TRACKING_START def mark_first_unit_to_resume(self, block_node): children = block_node.get('children') if children: children[0]['resume_block'] = True self.mark_first_unit_to_resume(children[0])