Example #1
0
    def test_get_gated_content(self):
        """
        Verify staff bypasses gated content and student gets list of unfulfilled prerequisites.
        """

        staff = UserFactory(is_staff=True)
        student = UserFactory(is_staff=False)

        self.assertEqual(gating_api.get_gated_content(self.course, staff), [])
        self.assertEqual(gating_api.get_gated_content(self.course, student),
                         [])

        gating_api.add_prerequisite(self.course.id, self.seq1.location)
        gating_api.set_required_content(self.course.id, self.seq2.location,
                                        self.seq1.location, 100)
        milestone = milestones_api.get_course_content_milestones(
            self.course.id, self.seq2.location, 'requires')[0]

        self.assertEqual(gating_api.get_gated_content(self.course, staff), [])
        self.assertEqual(gating_api.get_gated_content(self.course, student),
                         [unicode(self.seq2.location)])

        milestones_api.add_user_milestone({'id': student.id}, milestone)  # pylint: disable=no-member

        self.assertEqual(gating_api.get_gated_content(self.course, student),
                         [])
Example #2
0
    def test_get_gated_content(self):
        """ Test test_get_gated_content """

        mock_user = MagicMock()
        mock_user.id.return_value = 1

        self.assertEqual(gating_api.get_gated_content(self.course, mock_user), [])

        gating_api.add_prerequisite(self.course.id, self.seq1.location)
        gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100)
        milestone = milestones_api.get_course_content_milestones(self.course.id, self.seq2.location, 'requires')[0]

        self.assertEqual(gating_api.get_gated_content(self.course, mock_user), [unicode(self.seq2.location)])

        milestones_api.add_user_milestone({'id': mock_user.id}, milestone)

        self.assertEqual(gating_api.get_gated_content(self.course, mock_user), [])
Example #3
0
 def _verify_section_not_gated(self):
     """
     Verify whether the section is gated and accessible to the user.
     """
     gated_content = gating_api.get_gated_content(self.course, self.effective_user)
     if gated_content:
         if unicode(self.section.location) in gated_content:
             raise Http404
Example #4
0
 def _verify_section_not_gated(self):
     """
     Verify whether the section is gated and accessible to the user.
     """
     gated_content = gating_api.get_gated_content(self.course,
                                                  self.effective_user)
     if gated_content:
         if unicode(self.section.location) in gated_content:
             raise Http404
Example #5
0
    def test_get_gated_content(self):
        """
        Verify staff bypasses gated content and student gets list of unfulfilled prerequisites.
        """

        staff = UserFactory(is_staff=True)
        student = UserFactory(is_staff=False)

        self.assertEqual(gating_api.get_gated_content(self.course, staff), [])
        self.assertEqual(gating_api.get_gated_content(self.course, student), [])

        gating_api.add_prerequisite(self.course.id, self.seq1.location)
        gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100)
        milestone = milestones_api.get_course_content_milestones(self.course.id, self.seq2.location, 'requires')[0]

        self.assertEqual(gating_api.get_gated_content(self.course, staff), [])
        self.assertEqual(gating_api.get_gated_content(self.course, student), [unicode(self.seq2.location)])

        milestones_api.add_user_milestone({'id': student.id}, milestone)  # pylint: disable=no-member

        self.assertEqual(gating_api.get_gated_content(self.course, student), [])
Example #6
0
    def test_get_gated_content(self):
        """
        Verify staff bypasses gated content and student gets list of unfulfilled prerequisites.
        """

        staff = UserFactory(is_staff=True)
        student = UserFactory(is_staff=False)

        assert gating_api.get_gated_content(self.course, staff) == []
        assert gating_api.get_gated_content(self.course, student) == []

        gating_api.add_prerequisite(self.course.id, self.seq1.location)
        gating_api.set_required_content(self.course.id, self.seq2.location, self.seq1.location, 100)
        milestone = milestones_api.get_course_content_milestones(self.course.id, self.seq2.location, 'requires')[0]

        assert gating_api.get_gated_content(self.course, staff) == []
        assert gating_api.get_gated_content(self.course, student) == [str(self.seq2.location)]

        milestones_api.add_user_milestone({'id': student.id}, milestone)

        assert gating_api.get_gated_content(self.course, student) == []
def prepare_sections_with_grade(request, course):
    '''
    Create sections with grade details.

    Return format:
    {
        'sections': [
            {
                'display_name': name,
                # in case of cohorts or any other accessibility settings
                'hidden': hidden,
                'url_name': url_name,
                'units': UNITS,
                'rank': rank,
                'badge': bagde status,
                'points': grade points,
                'podium': podium status,
                'week': section_index + 1,
            },
        ],
    }

    where UNITS is a list
    [
        {
            'display_name': name,
            'position': unit position in section,
            'css_class': css class,
        }
        , ...
    ]

    sections with name 'hidden' are skipped.

    NOTE: assumes that if we got this far, user has access to course.  Returns
    [] if this is not the case.
    '''
    # Set the student to request user
    student = request.user

    # Get the field data cache
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course.id, student, course, depth=2,
    )
    
    # Get the course module
    with modulestore().bulk_operations(course.id):
        course_module = get_module_for_descriptor(
            student, request, course, field_data_cache, course.id, course=course
        )
        if course_module is None:
            return []

    # Get the field data cache
    staff_user = User.objects.filter(is_staff=1)[0]
    staff_field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course.id, staff_user, course, depth=2,
    )

    # Get the course module
    with modulestore().bulk_operations(course.id):
        staff_course_module = get_module_for_descriptor(
            staff_user, request, course, staff_field_data_cache, course.id, course=course
        )

    # staff accessible chapters
    staff_chapters = staff_course_module.get_display_items()

    # find the passing grade for the course
    nonzero_cutoffs = [cutoff for cutoff in course.grade_cutoffs.values() if cutoff > 0]
    success_cutoff = min(nonzero_cutoffs) if nonzero_cutoffs else 0

    # find the course progress
    progress = get_course_progress(student, course.id)

    # prepare a list of discussions participated by user
    discussions_participated = get_discussions_participated(
        request,
        course.id.to_deprecated_string(),
        student.id
    )

    # get courseware summary
    with outer_atomic():
        field_data_cache = grades.field_data_cache_for_grading(course, student)
        scores_client = ScoresClient.from_field_data_cache(field_data_cache)

    courseware_summary = grades.progress_summary(
        student, request, course, field_data_cache=field_data_cache, scores_client=scores_client
    )

    section_grades = {}
    for section in courseware_summary:
        earned = 0
        total = 0
        for sub_section in section['sections']:
            earned += sub_section['section_total'].earned
            total += sub_section['section_total'].possible

        section_score = earned / total if earned > 0 and total > 0 else 0
        section_grades[section['url_name']] = {
            'earned': earned,
            'total': total,
            'css_class': ('text-red', 'text-green')[int(section_score >= 0.6)] if total > 0 else ''
        }

    # Check for content which needs to be completed
    # before the rest of the content is made available
    required_content = milestones_helpers.get_required_content(course, student)

    # Check for gated content
    gated_content = gating_api.get_gated_content(course, student)

    # The user may not actually have to complete the entrance exam, if one is required
    if not user_must_complete_entrance_exam(request, student, course):
        required_content = [content for content in required_content if not content == course.entrance_exam_id]

    # define inner function
    def create_module(descriptor):
        '''creates an XModule instance given a descriptor'''
        return get_module_for_descriptor(
            student, request, descriptor, field_data_cache, course.id, course=course
        )

    with outer_atomic():
        submissions_scores = sub_api.get_scores(
            course.id.to_deprecated_string(),
            anonymous_id_for_user(student, course.id)
        )
        max_scores_cache = grades.MaxScoresCache.create_for_course(course)
        max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations)


    sections = list()
    student_chapters = course_module.get_display_items()
    urlname_chapters = {}
    for student_chap in student_chapters:
        urlname_chapters.update({student_chap.url_name:student_chap})
    final_chapters = OrderedDict()
    for chapter_index, chapter in enumerate(staff_chapters):
        fin_chap = urlname_chapters.get(chapter.url_name)
        if fin_chap:
            final_chapters.update({str(chapter_index+1):{'hidden': False, 'chapter':fin_chap}})
        else:
            final_chapters.update({str(chapter_index+1):{'hidden':True}})

    for section_index, chapter_info in final_chapters.items():
        # Mark as hidden and Skip the current chapter if a hide flag is tripped
        if chapter_info['hidden']:
            sections.append({
                'hidden': True,
                'week': "WEEK {week}: ".format(week=section_index),
                'points': {
                    'total': 0,
                    'earned': 0,
                    'css_class': 'text-disabled'
                },
            })
            continue

        chapter = chapter_info['chapter']
        # get the points
        section_points = section_grades.get(chapter.url_name, {})

        units = list()
        for sequential in chapter.get_display_items():
            # Set hidden status of the sequential if it is gated/hidden from the user
            hidden = (
                gated_content and unicode(sequential.location) in gated_content or
                sequential.hide_from_toc
            )

            if hidden:
                continue

            for index, unit in enumerate(sequential.get_display_items()):
                css_class = 'dark-gray'
                if unit.graded:
                    total_excercises = 0
                    attempted_excercises = 0
                    unit_max_score = 0
                    unit_score = 0
                    for component in unit.get_display_items():
                        if component.category == 'problem':
                            if component.graded:
                                total_excercises += 1
                                attempted_excercises += is_attempted_internal(
                                    str(component.location), progress
                                )
                                (correct, total) = grades.get_score(
                                    student,
                                    component,
                                    create_module,
                                    scores_client,
                                    submissions_scores,
                                    max_scores_cache,
                                )
                                unit_max_score += total
                                unit_score += correct

                    if total_excercises:
                        css_class = 'blue'
                        if attempted_excercises == total_excercises:
                            css_class = 'green'
                            if unit_max_score and unit_score / unit_max_score < success_cutoff:
                                css_class = 'red'

                position = index + 1  # For jumping to the unit directly
                unit_context = {
                    'display_name': unit.display_name_with_default_escaped,
                    'position': position,
                    'css_class': css_class,
                    'courseware_url': reverse(
                        'courseware_position',
                        args=[
                            course.id,
                            chapter.url_name,
                            sequential.url_name,
                            position
                        ]
                    )
                }
                units.append(unit_context)

        competency = None
        if int(section_points.get('total')):
            competency = int(section_points.get('earned')) == int(section_points.get('total'))
        section_context = {
            'display_name': chapter.display_name_with_default_escaped,
            'url_name': chapter.url_name,
            'hidden': False,
            'rank': 1,
            'competency': competency,
            'points': {
                'total': int(section_points.get('total')),
                'earned': int(section_points.get('earned')),
                'css_class': section_points.get('css_class')
            },
            'participation': discussions_participated.get(chapter.url_name),
            'units': units,
            'week': "WEEK {week}: ".format(week=section_index),
        }
        sections.append(section_context)

    return sections
Example #8
0
def toc_for_course(user, request, course, active_chapter, active_section, field_data_cache):
    '''
    Create a table of contents from the module store

    Return format:
    { 'chapters': [
            {'display_name': name, 'url_name': url_name, 'sections': SECTIONS, 'active': bool},
        ],
        'previous_of_active_section': {..},
        'next_of_active_section': {..}
    }

    where SECTIONS is a list
    [ {'display_name': name, 'url_name': url_name,
       'format': format, 'due': due, 'active' : bool, 'graded': bool}, ...]

    where previous_of_active_section and next_of_active_section have information on the
    next/previous sections of the active section.

    active is set for the section and chapter corresponding to the passed
    parameters, which are expected to be url_names of the chapter+section.
    Everything else comes from the xml, or defaults to "".

    chapters with name 'hidden' are skipped.

    NOTE: assumes that if we got this far, user has access to course.  Returns
    None if this is not the case.

    field_data_cache must include data from the course module and 2 levels of its descendants
    '''

    with modulestore().bulk_operations(course.id):
        course_module = get_module_for_descriptor(
            user, request, course, field_data_cache, course.id, course=course
        )
        if course_module is None:
            return None, None, None

        toc_chapters = list()
        chapters = course_module.get_display_items()

        # Check for content which needs to be completed
        # before the rest of the content is made available
        required_content = milestones_helpers.get_required_content(course, user)

        # Check for gated content
        gated_content = gating_api.get_gated_content(course, user)

        # The user may not actually have to complete the entrance exam, if one is required
        if not user_must_complete_entrance_exam(request, user, course):
            required_content = [content for content in required_content if not content == course.entrance_exam_id]

        previous_of_active_section, next_of_active_section = None, None
        last_processed_section, last_processed_chapter = None, None
        found_active_section = False
        for chapter in chapters:
            # Only show required content, if there is required content
            # chapter.hide_from_toc is read-only (bool)
            display_id = slugify(chapter.display_name_with_default_escaped)
            local_hide_from_toc = False
            if required_content:
                if unicode(chapter.location) not in required_content:
                    local_hide_from_toc = True

            # Skip the current chapter if a hide flag is tripped
            if chapter.hide_from_toc or local_hide_from_toc:
                continue

            sections = list()
            for section in chapter.get_display_items():
                # skip the section if it is gated/hidden from the user
                if gated_content and unicode(section.location) in gated_content:
                    continue
                if section.hide_from_toc:
                    continue

                is_section_active = (chapter.url_name == active_chapter and section.url_name == active_section)
                if is_section_active:
                    found_active_section = True

                section_context = {
                    'display_name': section.display_name_with_default_escaped,
                    'url_name': section.url_name,
                    'format': section.format if section.format is not None else '',
                    'due': section.due,
                    'active': is_section_active,
                    'graded': section.graded,
                }
                _add_timed_exam_info(user, course, section, section_context)

                # update next and previous of active section, if applicable
                if is_section_active:
                    if last_processed_section:
                        previous_of_active_section = last_processed_section.copy()
                        previous_of_active_section['chapter_url_name'] = last_processed_chapter.url_name
                elif found_active_section and not next_of_active_section:
                    next_of_active_section = section_context.copy()
                    next_of_active_section['chapter_url_name'] = chapter.url_name

                sections.append(section_context)
                last_processed_section = section_context
                last_processed_chapter = chapter

            toc_chapters.append({
                'display_name': chapter.display_name_with_default_escaped,
                'display_id': display_id,
                'url_name': chapter.url_name,
                'sections': sections,
                'active': chapter.url_name == active_chapter
            })
        return {
            'chapters': toc_chapters,
            'previous_of_active_section': previous_of_active_section,
            'next_of_active_section': next_of_active_section,
        }
Example #9
0
def _progress_summary(student, course, course_structure=None):
    """
    Unwrapped version of "progress_summary".

    This pulls a summary of all problems in the course.

    Returns
    - courseware_summary is a summary of all sections with problems in the course.
    It is organized as an array of chapters, each containing an array of sections,
    each containing an array of scores. This contains information for graded and
    ungraded problems, and is good for displaying a course summary with due dates,
    etc.
    - None if the student does not have access to load the course module.

    Arguments:
        student: A User object for the student to grade
        course: A Descriptor containing the course to grade

    """
    if course_structure is None:
        course_structure = get_course_blocks(student, course.location)
    if not len(course_structure):
        return None
    scorable_locations = [block_key for block_key in course_structure if possibly_scored(block_key)]

    with outer_atomic():
        scores_client = ScoresClient.create_for_locations(course.id, student.id, scorable_locations)

    # We need to import this here to avoid a circular dependency of the form:
    # XBlock --> submissions --> Django Rest Framework error strings -->
    # Django translation --> ... --> courseware --> submissions
    from submissions import api as sub_api  # installed from the edx-submissions repository
    with outer_atomic():
        submissions_scores = sub_api.get_scores(
            unicode(course.id), anonymous_id_for_user(student, course.id)
        )

    # Check for gated content
    gated_content = gating_api.get_gated_content(course, student)

    chapters = []
    locations_to_weighted_scores = {}

    for chapter_key in course_structure.get_children(course_structure.root_block_usage_key):
        chapter = course_structure[chapter_key]
        sections = []
        for section_key in course_structure.get_children(chapter_key):
            if unicode(section_key) in gated_content:
                continue

            section = course_structure[section_key]

            graded = getattr(section, 'graded', False)
            scores = []

            for descendant_key in course_structure.post_order_traversal(
                    filter_func=possibly_scored,
                    start_node=section_key,
            ):
                descendant = course_structure[descendant_key]

                (correct, total) = get_score(
                    student,
                    descendant,
                    scores_client,
                    submissions_scores,
                )
                if correct is None and total is None:
                    continue

                weighted_location_score = Score(
                    correct,
                    total,
                    graded,
                    block_metadata_utils.display_name_with_default_escaped(descendant),
                    descendant.location
                )

                scores.append(weighted_location_score)
                locations_to_weighted_scores[descendant.location] = weighted_location_score

            escaped_section_name = block_metadata_utils.display_name_with_default_escaped(section)
            section_total, _ = graders.aggregate_scores(scores, escaped_section_name)

            sections.append({
                'display_name': escaped_section_name,
                'url_name': block_metadata_utils.url_name_for_block(section),
                'scores': scores,
                'section_total': section_total,
                'format': getattr(section, 'format', ''),
                'due': getattr(section, 'due', None),
                'graded': graded,
            })

        chapters.append({
            'course': course.display_name_with_default_escaped,
            'display_name': block_metadata_utils.display_name_with_default_escaped(chapter),
            'url_name': block_metadata_utils.url_name_for_block(chapter),
            'sections': sections
        })

    return ProgressSummary(chapters, locations_to_weighted_scores, course_structure.get_children)
Example #10
0
def _progress_summary(student,
                      request,
                      course,
                      field_data_cache=None,
                      scores_client=None):
    """
    Unwrapped version of "progress_summary".

    This pulls a summary of all problems in the course.

    Returns
    - courseware_summary is a summary of all sections with problems in the course.
    It is organized as an array of chapters, each containing an array of sections,
    each containing an array of scores. This contains information for graded and
    ungraded problems, and is good for displaying a course summary with due dates,
    etc.

    Arguments:
        student: A User object for the student to grade
        course: A Descriptor containing the course to grade

    If the student does not have access to load the course module, this function
    will return None.

    """
    with outer_atomic():
        if field_data_cache is None:
            field_data_cache = field_data_cache_for_grading(course, student)
        if scores_client is None:
            scores_client = ScoresClient.from_field_data_cache(
                field_data_cache)

        course_module = get_module_for_descriptor(student,
                                                  request,
                                                  course,
                                                  field_data_cache,
                                                  course.id,
                                                  course=course)
        if not course_module:
            return None

        course_module = getattr(course_module, '_x_module', course_module)

    # We need to import this here to avoid a circular dependency of the form:
    # XBlock --> submissions --> Django Rest Framework error strings -->
    # Django translation --> ... --> courseware --> submissions
    from submissions import api as sub_api  # installed from the edx-submissions repository
    with outer_atomic():
        submissions_scores = sub_api.get_scores(
            course.id.to_deprecated_string(),
            anonymous_id_for_user(student, course.id))

        max_scores_cache = MaxScoresCache.create_for_course(course)
        # For the moment, we have to get scorable_locations from field_data_cache
        # and not from scores_client, because scores_client is ignorant of things
        # in the submissions API. As a further refactoring step, submissions should
        # be hidden behind the ScoresClient.
        max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations)

    # Check for gated content
    gated_content = gating_api.get_gated_content(course, student)

    chapters = []
    locations_to_children = defaultdict(list)
    locations_to_weighted_scores = {}
    # Don't include chapters that aren't displayable (e.g. due to error)
    for chapter_module in course_module.get_display_items():
        # Skip if the chapter is hidden
        if chapter_module.hide_from_toc:
            continue

        sections = []
        for section_module in chapter_module.get_display_items():
            # Skip if the section is hidden
            with outer_atomic():
                if section_module.hide_from_toc or unicode(
                        section_module.location) in gated_content:
                    continue

                graded = section_module.graded
                scores = []

                module_creator = section_module.xmodule_runtime.get_module

                for module_descriptor in yield_dynamic_descriptor_descendants(
                        section_module, student.id, module_creator):
                    locations_to_children[module_descriptor.parent].append(
                        module_descriptor.location)
                    (correct, total) = get_score(
                        student,
                        module_descriptor,
                        module_creator,
                        scores_client,
                        submissions_scores,
                        max_scores_cache,
                    )
                    if correct is None and total is None:
                        continue

                    weighted_location_score = Score(
                        correct, total, graded,
                        module_descriptor.display_name_with_default_escaped,
                        module_descriptor.location)

                    scores.append(weighted_location_score)
                    locations_to_weighted_scores[
                        module_descriptor.location] = weighted_location_score

                scores.reverse()
                section_total, _ = graders.aggregate_scores(
                    scores, section_module.display_name_with_default_escaped)

                module_format = section_module.format if section_module.format is not None else ''
                sections.append({
                    'display_name':
                    section_module.display_name_with_default_escaped,
                    'url_name': section_module.url_name,
                    'scores': scores,
                    'section_total': section_total,
                    'format': module_format,
                    'due': section_module.due,
                    'graded': graded,
                })

        chapters.append({
            'course': course.display_name_with_default_escaped,
            'display_name': chapter_module.display_name_with_default_escaped,
            'url_name': chapter_module.url_name,
            'sections': sections
        })

    max_scores_cache.push_to_remote()

    return ProgressSummary(chapters, locations_to_weighted_scores,
                           locations_to_children)
Example #11
0
def toc_for_course(user, request, course, active_chapter, active_section, field_data_cache):
    '''
    Create a table of contents from the module store

    Return format:
    [ {'display_name': name, 'url_name': url_name,
       'sections': SECTIONS, 'active': bool}, ... ]

    where SECTIONS is a list
    [ {'display_name': name, 'url_name': url_name,
       'format': format, 'due': due, 'active' : bool, 'graded': bool}, ...]

    active is set for the section and chapter corresponding to the passed
    parameters, which are expected to be url_names of the chapter+section.
    Everything else comes from the xml, or defaults to "".

    chapters with name 'hidden' are skipped.

    NOTE: assumes that if we got this far, user has access to course.  Returns
    None if this is not the case.

    field_data_cache must include data from the course module and 2 levels of its descendents
    '''

    with modulestore().bulk_operations(course.id):
        course_module = get_module_for_descriptor(
            user, request, course, field_data_cache, course.id, course=course
        )
        if course_module is None:
            return None

        toc_chapters = list()
        chapters = course_module.get_display_items()

        # Check for content which needs to be completed
        # before the rest of the content is made available
        required_content = milestones_helpers.get_required_content(course, user)

        # Check for gated content
        gated_content = gating_api.get_gated_content(course, user)

        # The user may not actually have to complete the entrance exam, if one is required
        if not user_must_complete_entrance_exam(request, user, course):
            required_content = [content for content in required_content if not content == course.entrance_exam_id]

        for chapter in chapters:
            # Only show required content, if there is required content
            # chapter.hide_from_toc is read-only (boo)
            display_id = slugify(chapter.display_name_with_default_escaped)
            local_hide_from_toc = False
            if required_content:
                if unicode(chapter.location) not in required_content:
                    local_hide_from_toc = True

            # Skip the current chapter if a hide flag is tripped
            if chapter.hide_from_toc or local_hide_from_toc:
                continue

            sections = list()
            for section in chapter.get_display_items():

                active = (chapter.url_name == active_chapter and
                          section.url_name == active_section)

                # Skip the current section if it is gated
                if gated_content and unicode(section.location) in gated_content:
                    continue

                if not section.hide_from_toc:
                    section_context = {
                        'display_name': section.display_name_with_default_escaped,
                        'url_name': section.url_name,
                        'format': section.format if section.format is not None else '',
                        'due': section.due,
                        'active': active,
                        'graded': section.graded,
                    }

                    #
                    # Add in rendering context if exam is a timed exam (which includes proctored)
                    #

                    section_is_time_limited = (
                        getattr(section, 'is_time_limited', False) and
                        settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False)
                    )
                    if section_is_time_limited:
                        # We need to import this here otherwise Lettuce test
                        # harness fails. When running in 'harvest' mode, the
                        # test service appears to get into trouble with
                        # circular references (not sure which as edx_proctoring.api
                        # doesn't import anything from edx-platform). Odd thing
                        # is that running: manage.py lms runserver --settings=acceptance
                        # works just fine, it's really a combination of Lettuce and the
                        # 'harvest' management command
                        #
                        # One idea is that there is some coupling between
                        # lettuce and the 'terrain' Djangoapps projects in /common
                        # This would need more investigation
                        from edx_proctoring.api import get_attempt_status_summary

                        #
                        # call into edx_proctoring subsystem
                        # to get relevant proctoring information regarding this
                        # level of the courseware
                        #
                        # This will return None, if (user, course_id, content_id)
                        # is not applicable
                        #
                        timed_exam_attempt_context = None
                        try:
                            timed_exam_attempt_context = get_attempt_status_summary(
                                user.id,
                                unicode(course.id),
                                unicode(section.location)
                            )
                        except Exception, ex:  # pylint: disable=broad-except
                            # safety net in case something blows up in edx_proctoring
                            # as this is just informational descriptions, it is better
                            # to log and continue (which is safe) than to have it be an
                            # unhandled exception
                            log.exception(ex)

                        if timed_exam_attempt_context:
                            # yes, user has proctoring context about
                            # this level of the courseware
                            # so add to the accordion data context
                            section_context.update({
                                'proctoring': timed_exam_attempt_context,
                            })

                    sections.append(section_context)
            toc_chapters.append({
                'display_name': chapter.display_name_with_default_escaped,
                'display_id': display_id,
                'url_name': chapter.url_name,
                'sections': sections,
                'active': chapter.url_name == active_chapter
            })
        return toc_chapters
Example #12
0
def _progress_summary(student, request, course, field_data_cache=None, scores_client=None):
    """
    Unwrapped version of "progress_summary".

    This pulls a summary of all problems in the course.

    Returns
    - courseware_summary is a summary of all sections with problems in the course.
    It is organized as an array of chapters, each containing an array of sections,
    each containing an array of scores. This contains information for graded and
    ungraded problems, and is good for displaying a course summary with due dates,
    etc.

    Arguments:
        student: A User object for the student to grade
        course: A Descriptor containing the course to grade

    If the student does not have access to load the course module, this function
    will return None.

    """
    with outer_atomic():
        if field_data_cache is None:
            field_data_cache = field_data_cache_for_grading(course, student)
        if scores_client is None:
            scores_client = ScoresClient.from_field_data_cache(field_data_cache)

        course_module = get_module_for_descriptor(
            student, request, course, field_data_cache, course.id, course=course
        )
        if not course_module:
            return None

        course_module = getattr(course_module, '_x_module', course_module)

    # We need to import this here to avoid a circular dependency of the form:
    # XBlock --> submissions --> Django Rest Framework error strings -->
    # Django translation --> ... --> courseware --> submissions
    from submissions import api as sub_api  # installed from the edx-submissions repository
    with outer_atomic():
        submissions_scores = sub_api.get_scores(
            course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id)
        )

        max_scores_cache = MaxScoresCache.create_for_course(course)
        # For the moment, we have to get scorable_locations from field_data_cache
        # and not from scores_client, because scores_client is ignorant of things
        # in the submissions API. As a further refactoring step, submissions should
        # be hidden behind the ScoresClient.
        max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations)

    # Check for gated content
    gated_content = gating_api.get_gated_content(course, student)

    chapters = []
    locations_to_children = defaultdict(list)
    locations_to_weighted_scores = {}
    # Don't include chapters that aren't displayable (e.g. due to error)
    for chapter_module in course_module.get_display_items():
        # Skip if the chapter is hidden
        if chapter_module.hide_from_toc:
            continue

        sections = []
        for section_module in chapter_module.get_display_items():
            # Skip if the section is hidden
            with outer_atomic():
                if section_module.hide_from_toc or unicode(section_module.location) in gated_content:
                    continue

                graded = section_module.graded
                scores = []

                module_creator = section_module.xmodule_runtime.get_module

                for module_descriptor in yield_dynamic_descriptor_descendants(
                        section_module, student.id, module_creator
                ):
                    location_parent = module_descriptor.parent.replace(version=None, branch=None)
                    location_to_save = module_descriptor.location.replace(version=None, branch=None)
                    locations_to_children[location_parent].append(location_to_save)
                    (correct, total) = get_score(
                        student,
                        module_descriptor,
                        module_creator,
                        scores_client,
                        submissions_scores,
                        max_scores_cache,
                    )
                    if correct is None and total is None:
                        continue

                    weighted_location_score = Score(
                        correct,
                        total,
                        graded,
                        module_descriptor.display_name_with_default_escaped,
                        module_descriptor.location
                    )

                    scores.append(weighted_location_score)
                    locations_to_weighted_scores[location_to_save] = weighted_location_score

                scores.reverse()
                section_total, _ = graders.aggregate_scores(
                    scores, section_module.display_name_with_default_escaped)

                module_format = section_module.format if section_module.format is not None else ''
                sections.append({
                    'display_name': section_module.display_name_with_default_escaped,
                    'url_name': section_module.url_name,
                    'scores': scores,
                    'section_total': section_total,
                    'format': module_format,
                    'due': section_module.due,
                    'graded': graded,
                })

        chapters.append({
            'course': course.display_name_with_default_escaped,
            'display_name': chapter_module.display_name_with_default_escaped,
            'url_name': chapter_module.url_name,
            'sections': sections
        })

    max_scores_cache.push_to_remote()

    return ProgressSummary(chapters, locations_to_weighted_scores, locations_to_children)
Example #13
0
def summary(student, course, course_structure=None):
    """
    This pulls a summary of all problems in the course.

    Returns
    - courseware_summary is a summary of all sections with problems in the course.
    It is organized as an array of chapters, each containing an array of sections,
    each containing an array of scores. This contains information for graded and
    ungraded problems, and is good for displaying a course summary with due dates,
    etc.
    - None if the student does not have access to load the course module.

    Arguments:
        student: A User object for the student to grade
        course: A Descriptor containing the course to grade

    """
    if course_structure is None:
        course_structure = get_course_blocks(student, course.location)
    if not len(course_structure):
        return ProgressSummary()
    scorable_locations = [
        block_key for block_key in course_structure
        if possibly_scored(block_key)
    ]

    with outer_atomic():
        scores_client = ScoresClient.create_for_locations(
            course.id, student.id, scorable_locations)

    # We need to import this here to avoid a circular dependency of the form:
    # XBlock --> submissions --> Django Rest Framework error strings -->
    # Django translation --> ... --> courseware --> submissions
    from submissions import api as sub_api  # installed from the edx-submissions repository
    with outer_atomic():
        submissions_scores = sub_api.get_scores(
            unicode(course.id), anonymous_id_for_user(student, course.id))

    # Check for gated content
    gated_content = gating_api.get_gated_content(course, student)

    chapters = []
    locations_to_weighted_scores = {}

    for chapter_key in course_structure.get_children(
            course_structure.root_block_usage_key):
        chapter = course_structure[chapter_key]
        sections = []
        for section_key in course_structure.get_children(chapter_key):
            if unicode(section_key) in gated_content:
                continue

            section = course_structure[section_key]

            graded = getattr(section, 'graded', False)
            scores = []

            for descendant_key in course_structure.post_order_traversal(
                    filter_func=possibly_scored,
                    start_node=section_key,
            ):
                descendant = course_structure[descendant_key]

                (correct, total) = get_score(
                    student,
                    descendant,
                    scores_client,
                    submissions_scores,
                )
                if correct is None and total is None:
                    continue

                weighted_location_score = Score(
                    correct, total, graded,
                    block_metadata_utils.display_name_with_default_escaped(
                        descendant), descendant.location)

                scores.append(weighted_location_score)
                locations_to_weighted_scores[
                    descendant.location] = weighted_location_score

            escaped_section_name = block_metadata_utils.display_name_with_default_escaped(
                section)
            section_total, _ = graders.aggregate_scores(
                scores, escaped_section_name)

            sections.append({
                'display_name':
                escaped_section_name,
                'url_name':
                block_metadata_utils.url_name_for_block(section),
                'scores':
                scores,
                'section_total':
                section_total,
                'format':
                getattr(section, 'format', ''),
                'due':
                getattr(section, 'due', None),
                'graded':
                graded,
            })

        chapters.append({
            'course':
            course.display_name_with_default_escaped,
            'display_name':
            block_metadata_utils.display_name_with_default_escaped(chapter),
            'url_name':
            block_metadata_utils.url_name_for_block(chapter),
            'sections':
            sections
        })

    return ProgressSummary(chapters, locations_to_weighted_scores,
                           course_structure.get_children)