Esempio n. 1
0
    def test_subsection_access_changed(self):
        """
        Tests retrieving a subsection grade before and after losing access
        to a block in the subsection.
        """
        # submit answers
        self.submit_question_answer('p1', {'2_1': 'choice_choice_2'})
        self.submit_question_answer('p2', {'2_1': 'choice_choice_2'})

        # check initial subsection grade
        course_structure = get_course_blocks(self.request.user, self.course.location)
        subsection_grade_factory = SubsectionGradeFactory(self.request.user, self.course, course_structure)
        grade = subsection_grade_factory.create(self.sequence, read_only=True)
        self.assertEqual(grade.graded_total.earned, 4.0)
        self.assertEqual(grade.graded_total.possible, 4.0)

        # set a block in the subsection to be visible to staff only
        with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred):
            problem_2 = self.store.get_item(self.problem_2.location)
            problem_2.visible_to_staff_only = True
            self.store.update_item(problem_2, self.instructor.id)
            self.store.publish(self.course.location, self.instructor.id)
        course_structure = get_course_blocks(self.student, self.course.location)

        # ensure that problem_2 is not accessible for the student
        self.assertNotIn(problem_2.location, course_structure)

        # make sure we can still get the subsection grade
        subsection_grade_factory = SubsectionGradeFactory(self.student, self.course, course_structure)
        grade = subsection_grade_factory.create(self.sequence, read_only=True)
        self.assertEqual(grade.graded_total.earned, 4.0)
        self.assertEqual(grade.graded_total.possible, 4.0)
Esempio n. 2
0
 def setUp(self):
     super(TestMultipleProblemTypesSubsectionScores, self).setUp()
     password = u'test'
     self.student = UserFactory.create(is_staff=False, username=u'test_student', password=password)
     self.client.login(username=self.student.username, password=password)
     self.request = get_mock_request(self.student)
     self.course_structure = get_course_blocks(self.student, self.course.location)
Esempio n. 3
0
def recalculate_subsection_grade_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the SCORE_CHANGED signal and trigger an update.
    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of SCORE_CHANGED):
       - points_possible: Maximum score available for the exercise
       - points_earned: Score obtained by the user
       - user: User object
       - course_id: Unicode string representing the course
       - usage_id: Unicode string indicating the courseware instance
    """
    student = kwargs['user']
    course_key = CourseLocator.from_string(kwargs['course_id'])
    if not PersistentGradesEnabledFlag.feature_enabled(course_key):
        return

    scored_block_usage_key = UsageKey.from_string(kwargs['usage_id']).replace(course_key=course_key)
    collected_block_structure = get_course_in_cache(course_key)
    course = get_course_by_id(course_key, depth=0)

    subsections_to_update = collected_block_structure.get_transformer_block_field(
        scored_block_usage_key,
        GradesTransformer,
        'subsections',
        set()
    )
    for subsection_usage_key in subsections_to_update:
        transformed_subsection_structure = get_course_blocks(
            student,
            subsection_usage_key,
            collected_block_structure=collected_block_structure,
        )
        SubsectionGradeFactory(student).update(subsection_usage_key, transformed_subsection_structure, course)
Esempio n. 4
0
def get_subsection_completion_percentage(subsection_usage_key, user):
    """
    Computes completion percentage for a subsection in a given course for a user
    Arguments:
        subsection_usage_key: key of subsection
        user: The user whose completion percentage needs to be computed
    Returns:
        User's completion percentage for given subsection
    """
    subsection_completion_percentage = 0.0
    try:
        subsection_structure = get_course_blocks(user, subsection_usage_key)
        if any(subsection_structure):
            completable_blocks = [
                block for block in subsection_structure
                if block.block_type not in ['chapter', 'sequential', 'vertical']
            ]
            if not completable_blocks:
                return 0
            subsection_completion_total = 0
            course_block_completions = BlockCompletion.get_course_completions(user, subsection_usage_key.course_key)
            for block in completable_blocks:
                if course_block_completions.get(block):
                    subsection_completion_total += course_block_completions.get(block)
            subsection_completion_percentage = min(
                100 * (subsection_completion_total / float(len(completable_blocks))), 100
            )

    except ItemNotFoundError as err:
        log.warning("Could not find course_block for subsection=%s error=%s", subsection_usage_key, err)

    return subsection_completion_percentage
Esempio n. 5
0
 def setUp(self):
     super(GradeTestBase, self).setUp()
     self.request = get_request_for_user(UserFactory())
     self.client.login(username=self.request.user.username, password="******")
     self.subsection_grade_factory = SubsectionGradeFactory(self.request.user)
     self.course_structure = get_course_blocks(self.request.user, self.course.location)
     CourseEnrollment.enroll(self.request.user, self.course.id)
 def get_course_blocks(self, user):
     """ fetch cached blocks for course - retain for subsequent use """
     course_key = self.get_course_key()
     if course_key not in self._course_blocks:
         root_block_usage_key = self.get_module_store().make_course_usage_key(course_key)
         self._course_blocks[course_key] = get_course_blocks(user, root_block_usage_key)
     return self._course_blocks[course_key]
Esempio n. 7
0
def task_evaluate_subsection_completion_milestones(course_id, block_id, user_id):
    """
    Updates users' milestones related to completion of a subsection.
     Args:
        course_id(str): Course id which triggered a completion event
        block_id(str): Id of the completed block
        user_id(int): Id of the user who completed a block
    """
    store = modulestore()
    course_key = CourseKey.from_string(course_id)
    with store.bulk_operations(course_key):
        course = store.get_course(course_key)
        if not course or not course.enable_subsection_gating:
            log.debug(
                u"Gating: ignoring evaluation of completion milestone because it disabled for course [%s]", course_id
            )
        else:
            try:
                user = User.objects.get(id=user_id)
                course_structure = get_course_blocks(user, store.make_course_usage_key(course_key))
                completed_block_usage_key = UsageKey.from_string(block_id).map_into_course(course.id)
                subsection_block = _get_subsection_of_block(completed_block_usage_key, course_structure)
                subsection = course_structure[subsection_block]
                log.debug(
                    u"Gating: Evaluating completion milestone for subsection [%s] and user [%s]",
                    unicode(subsection.location), user.id
                )
                gating_api.evaluate_prerequisite(course, subsection, user)
            except KeyError:
                log.error(u"Gating: Given prerequisite subsection [%s] not found in course structure", block_id)
Esempio n. 8
0
def _update_subsection_grades(course_key, scored_block_usage_key, only_if_higher, user_id, score_deleted):
    """
    A helper function to update subsection grades in the database
    for each subsection containing the given block, and to signal
    that those subsection grades were updated.
    """
    student = User.objects.get(id=user_id)
    store = modulestore()
    with store.bulk_operations(course_key):
        course_structure = get_course_blocks(student, store.make_course_usage_key(course_key))
        subsections_to_update = course_structure.get_transformer_block_field(
            scored_block_usage_key,
            GradesTransformer,
            'subsections',
            set(),
        )

        course = store.get_course(course_key, depth=0)
        subsection_grade_factory = SubsectionGradeFactory(student, course, course_structure)

        for subsection_usage_key in subsections_to_update:
            if subsection_usage_key in course_structure:
                subsection_grade = subsection_grade_factory.update(
                    course_structure[subsection_usage_key],
                    only_if_higher,
                    score_deleted
                )
                SUBSECTION_SCORE_CHANGED.send(
                    sender=None,
                    course=course,
                    course_structure=course_structure,
                    user=student,
                    subsection_grade=subsection_grade,
                )
Esempio n. 9
0
    def _verify_grades(self, raw_earned, raw_possible, weight, expected_score):
        """
        Verifies the computed grades are as expected.
        """
        with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred):
            # pylint: disable=no-member
            for problem in self.problems:
                problem.weight = weight
                self.store.update_item(problem, self.user.id)
            self.store.publish(self.course.location, self.user.id)

        course_structure = get_course_blocks(self.request.user, self.course.location)

        # answer all problems
        for problem in self.problems:
            answer_problem(self.course, self.request, problem, score=raw_earned, max_value=raw_possible)

        # get grade
        subsection_grade = SubsectionGradeFactory(
            self.request.user, self.course, course_structure
        ).update(self.sequential)

        # verify all problem grades
        for problem in self.problems:
            problem_score = subsection_grade.locations_to_scores[problem.location]
            self.assertEquals(problem_score, expected_score)

        # verify subsection grades
        self.assertEquals(subsection_grade.all_total.earned, expected_score.earned * len(self.problems))
        self.assertEquals(subsection_grade.all_total.possible, expected_score.possible * len(self.problems))
Esempio n. 10
0
def get_subsection_grade_percentage(subsection_usage_key, user):
    """
    Computes grade percentage for a subsection in a given course for a user

    Arguments:
        subsection_usage_key: key of subsection
        user: The user whose grade needs to be computed

    Returns:
        User's grade percentage for given subsection
    """
    subsection_grade_percentage = 0.0
    try:
        subsection_structure = get_course_blocks(user, subsection_usage_key)
        if any(subsection_structure):
            subsection_grade_factory = SubsectionGradeFactory(user, course_structure=subsection_structure)
            if subsection_usage_key in subsection_structure:
                # this will force a recalculation of the subsection grade
                subsection_grade = subsection_grade_factory.update(
                    subsection_structure[subsection_usage_key], persist_grade=False
                )
                subsection_grade_percentage = subsection_grade.percent_graded * 100.0
    except ItemNotFoundError as err:
        log.warning("Could not find course_block for subsection=%s error=%s", subsection_usage_key, err)
    return subsection_grade_percentage
Esempio n. 11
0
 def create(self, course):
     """
     Returns the CourseGrade object for the given student and course.
     """
     course_structure = get_course_blocks(self.student, course.location)
     return self._get_saved_grade(course, course_structure) or self._compute_and_update_grade(
         course, course_structure
     )
Esempio n. 12
0
 def structure(self):
     if self._structure is None:
         self._structure = get_course_blocks(
             self.user,
             self.location,
             collected_block_structure=self._collected_block_structure,
         )
     return self._structure
Esempio n. 13
0
 def setUp(self):
     super(GradeTestBase, self).setUp()
     self.request = get_mock_request(UserFactory())
     self.client.login(username=self.request.user.username, password="******")
     self._set_grading_policy()
     self.course_structure = get_course_blocks(self.request.user, self.course.location)
     self.course_data = CourseData(self.request.user, structure=self.course_structure)
     self.subsection_grade_factory = SubsectionGradeFactory(self.request.user, self.course, self.course_structure)
     CourseEnrollment.enroll(self.request.user, self.course.id)
 def test_course_version_collected_in_split(self):
     with self.store.default_store(ModuleStoreEnum.Type.split):
         blocks = self.build_course_with_problems()
     block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers)
     self.assertIsNotNone(block_structure.get_xblock_field(blocks[u'course'].location, u'course_version'))
     self.assertEqual(
         block_structure.get_xblock_field(blocks[u'problem'].location, u'course_version'),
         block_structure.get_xblock_field(blocks[u'course'].location, u'course_version')
     )
Esempio n. 15
0
    def test_transform_gives_zero_for_ordinary_block(self):
        course = CourseFactory.create()
        block = ItemFactory.create(category='html', parent=course)
        block_structure = get_course_blocks(
            self.user, course.location, self.transformers
        )

        self._assert_block_has_proper_completion_value(
            block_structure, block.location, 0.0
        )
Esempio n. 16
0
    def test_transform_gives_none_for_excluded(self):
        course = CourseFactory.create()
        block = ItemFactory.create(category='excluded', parent=course)
        block_structure = get_course_blocks(
            self.user, course.location, self.transformers
        )

        self._assert_block_has_proper_completion_value(
            block_structure, block.location, None
        )
Esempio n. 17
0
def get_blocks(
    request,
    usage_key,
    user=None,
    depth=None,
    nav_depth=None,
    requested_fields=None,
    block_counts=None,
    student_view_data=None,
    return_type="dict",
):
    """
    Return a serialized representation of the course blocks.

    Arguments:
        request (HTTPRequest): Used for calling django reverse.
        usage_key (UsageKey): Identifies the root block of interest.
        user (User): Optional user object for whom the blocks are being
            retrieved. If None, blocks are returned regardless of access checks.
        depth (integer or None): Identifies the depth of the tree to return
            starting at the root block.  If None, the entire tree starting at
            the root is returned.
        nav_depth (integer): Optional parameter that indicates how far deep to
            traverse into the block hierarchy before bundling all the
            descendants for navigation.
        requested_fields (list): Optional list of names of additional fields
            to return for each block.  Supported fields are listed in
            transformers.SUPPORTED_FIELDS.
        block_counts (list): Optional list of names of block types for which to
            return an aggregate count of blocks.
        student_view_data (list): Optional list of names of block types for
            which blocks to return their student_view_data.
        return_type (string): Possible values are 'dict' or 'list'. Indicates
            the format for returning the blocks.
    """
    # create ordered list of transformers, adding BlocksAPITransformer at end.
    transformers = BlockStructureTransformers()
    if user is not None:
        transformers += COURSE_BLOCK_ACCESS_TRANSFORMERS + [ProctoredExamTransformer()]
    transformers += [BlocksAPITransformer(block_counts, student_view_data, depth, nav_depth)]

    # transform
    blocks = get_course_blocks(user, usage_key, transformers)

    # serialize
    serializer_context = {"request": request, "block_structure": blocks, "requested_fields": requested_fields or []}

    if return_type == "dict":
        serializer = BlockDictSerializer(blocks, context=serializer_context, many=False)
    else:
        serializer = BlockSerializer(blocks, context=serializer_context, many=True)

    # return serialized data
    return serializer.data
Esempio n. 18
0
    def create(self, course, read_only=False):
        """
        Returns the CourseGrade object for the given student and course.

        If read_only is True, doesn't save any updates to the grades.
        """
        course_structure = get_course_blocks(self.student, course.location)
        return (
            self._get_saved_grade(course, course_structure) or
            self._compute_and_update_grade(course, course_structure, read_only)
        )
Esempio n. 19
0
def compute_is_prereq_met(content_id, user_id, recalc_on_unmet=False):
    """
    Returns true if the prequiste has been met for a given milestone.
    Will recalculate the subsection grade if specified and prereq unmet

    Arguments:
        content_id (BlockUsageLocator): BlockUsageLocator for the content
        user_id: The id of the user
        recalc_on_unmet: Recalculate the grade if prereq has not yet been met

    Returns:
        tuple: True|False,
        prereq_meta_info = { 'url': prereq_url|None, 'display_name': prereq_name|None}
    """
    course_key = content_id.course_key

    # if unfullfilled milestones exist it means prereq has not been met
    unfulfilled_milestones = milestones_helpers.get_course_content_milestones(
        course_key,
        content_id,
        'requires',
        user_id
    )

    prereq_met = not unfulfilled_milestones
    prereq_meta_info = {'url': None, 'display_name': None}

    if prereq_met or not recalc_on_unmet:
        return prereq_met, prereq_meta_info

    milestone = unfulfilled_milestones[0]
    student = User.objects.get(id=user_id)
    store = modulestore()

    with store.bulk_operations(course_key):
        subsection_usage_key = UsageKey.from_string(_get_gating_block_id(milestone))
        subsection = store.get_item(subsection_usage_key)
        prereq_meta_info = {
            'url': reverse('jump_to', kwargs={'course_id': course_key, 'location': subsection_usage_key}),
            'display_name': subsection.display_name
        }

        try:
            subsection_structure = get_course_blocks(student, subsection_usage_key)
            if any(subsection_structure):
                subsection_grade_factory = SubsectionGradeFactory(student, course_structure=subsection_structure)
                if subsection_usage_key in subsection_structure:
                    # this will force a recalcuation of the subsection grade
                    subsection_grade = subsection_grade_factory.update(subsection_structure[subsection_usage_key], persist_grade=False)
                    prereq_met = update_milestone(milestone, subsection_grade, milestone, user_id)
        except ItemNotFoundError as err:
            log.warning("Could not find course_block for subsection=%s error=%s", subsection_usage_key, err)

    return prereq_met, prereq_meta_info
Esempio n. 20
0
    def _get_score_with_alterations(self, alterations):
        """
        Given a dict of alterations to the default_problem_metadata, return
        the score when one correct problem (out of two) is submitted.
        """
        metadata = self._get_altered_metadata(alterations)

        add_xml_block_from_file(u'problem', u'capa.xml', parent=self.vert1, metadata=metadata)
        course_structure = get_course_blocks(self.student, self.course.location)

        self.submit_question_answer(u'problem', {u'2_1': u'Correct'})
        return self._get_fresh_subsection_score(course_structure, self.seq1)
Esempio n. 21
0
    def test_score_submission_for_capa_problems(self):
        add_xml_block_from_file(u'problem', u'capa.xml', parent=self.vert1, metadata=self.default_problem_metadata)
        course_structure = get_course_blocks(self.student, self.course.location)

        score = self._get_fresh_subsection_score(course_structure, self.seq1)
        self.assertEqual(score.all_total.earned, 0.0)
        self.assertEqual(score.all_total.possible, 2.5)

        self.submit_question_answer(u'problem', {u'2_1': u'Correct'})
        score = self._get_fresh_subsection_score(course_structure, self.seq1)
        self.assertEqual(score.all_total.earned, 1.25)
        self.assertEqual(score.all_total.possible, 2.5)
Esempio n. 22
0
 def test_subsection_grade_feature_gating(self, feature_flag, course_setting):
     # Grades are only saved if the feature flag and the advanced setting are
     # both set to True.
     grade_factory = SubsectionGradeFactory(self.request.user)
     course_structure = get_course_blocks(self.request.user, self.course.location)
     with patch(
         'lms.djangoapps.grades.new.subsection_grade._pretend_to_save_subsection_grades'
     ) as mock_save_grades:
         with patch.dict(settings.FEATURES, {'ENABLE_SUBSECTION_GRADES_SAVED': feature_flag}):
             with patch.object(self.course, 'enable_subsection_grades_saved', new=course_setting):
                 grade_factory.create(self.sequence, course_structure, self.course)
     self.assertEqual(mock_save_grades.called, feature_flag and course_setting)
Esempio n. 23
0
def _update_subsection_grades(
        course_key,
        scored_block_usage_key,
        only_if_higher,
        course_id,
        user_id,
        usage_id,
        expected_modified_time,
        score_deleted,
):
    """
    A helper function to update subsection grades in the database
    for each subsection containing the given block, and to signal
    that those subsection grades were updated.
    """
    student = User.objects.get(id=user_id)
    course_structure = get_course_blocks(student, modulestore().make_course_usage_key(course_key))
    subsections_to_update = course_structure.get_transformer_block_field(
        scored_block_usage_key,
        GradesTransformer,
        'subsections',
        set(),
    )

    course = modulestore().get_course(course_key, depth=0)
    subsection_grade_factory = SubsectionGradeFactory(student, course, course_structure)

    try:
        for subsection_usage_key in subsections_to_update:
            if subsection_usage_key in course_structure:
                subsection_grade = subsection_grade_factory.update(
                    course_structure[subsection_usage_key],
                    only_if_higher,
                )
                SUBSECTION_SCORE_CHANGED.send(
                    sender=recalculate_subsection_grade,
                    course=course,
                    course_structure=course_structure,
                    user=student,
                    subsection_grade=subsection_grade,
                )

    except DatabaseError as exc:
        raise _retry_recalculate_subsection_grade(
            user_id,
            course_id,
            usage_id,
            only_if_higher,
            expected_modified_time,
            score_deleted,
            exc,
        )
Esempio n. 24
0
 def test_modulestore_performance(self, store_type, expected_mongo_queries):
     """
     Test that a constant number of mongo calls are made regardless of how
     many grade-related blocks are in the course.
     """
     course = [
         {
             u'org': u'GradesTestOrg',
             u'course': u'GB101',
             u'run': u'cannonball',
             u'metadata': {u'format': u'homework'},
             u'#type': u'course',
             u'#ref': u'course',
             u'#children': [],
         },
     ]
     for problem_number in xrange(random.randrange(10, 20)):
         course[0][u'#children'].append(
             {
                 u'metadata': {
                     u'graded': True,
                     u'weight': 1,
                     u'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc),
                 },
                 u'#type': u'problem',
                 u'#ref': u'problem_{}'.format(problem_number),
                 u'data': u'''
                     <problem>
                         <numericalresponse answer="{number}">
                             <textline label="1*{number}" />
                         </numericalresponse>
                     </problem>'''.format(number=problem_number),
             }
         )
     with self.store.default_store(store_type):
         blocks = self.build_course(course)
     clear_course_from_cache(blocks[u'course'].id)
     with check_mongo_calls(expected_mongo_queries):
         get_course_blocks(self.student, blocks[u'course'].location, self.transformers)
 def _validate_grading_policy_hash(self, course_location, grading_policy_hash):
     """
     Helper to retrieve the course at the given course_location and
     assert that its hashed grading policy (from the grades transformer)
     is as expected.
     """
     block_structure = get_course_blocks(self.student, course_location, self.transformers)
     self.assert_collected_transformer_block_fields(
         block_structure,
         course_location,
         self.TRANSFORMER_CLASS_TO_TEST,
         grading_policy_hash=grading_policy_hash,
     )
Esempio n. 26
0
    def test_full_string(self):
        empty_structure = get_course_blocks(self.user, self.course.location)
        self.assertFalse(empty_structure)

        # full_string retrieves value from collected_structure when structure is empty.
        course_data = CourseData(
            self.user, structure=empty_structure, collected_block_structure=self.collected_structure,
        )
        self.assertIn(u'Course: course_key: {}, version:'.format(self.course.id), course_data.full_string())

        # full_string returns minimal value when structures aren't readily available.
        course_data = CourseData(self.user, course_key=self.course.id)
        self.assertIn(u'empty course structure', course_data.full_string())
Esempio n. 27
0
    def setUp(self):
        """
        Set up test course
        """
        super(TestGetModuleScore, self).setUp()

        self.request = get_mock_request(UserFactory())
        self.client.login(username=self.request.user.username, password="******")
        CourseEnrollment.enroll(self.request.user, self.course.id)

        self.course_structure = get_course_blocks(self.request.user, self.course.location)

        # warm up the score cache to allow accurate query counts, even if tests are run in random order
        get_module_score(self.request.user, self.course, self.seq1)
 def test_graded_at_problem(self, graded):
     problem_metadata = {
         u'has_score': True,
     }
     if graded is not None:
         problem_metadata[u'graded'] = graded
     blocks = self.build_course_with_problems(metadata=problem_metadata)
     block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers)
     self.assert_collected_transformer_block_fields(
         block_structure,
         blocks[u'problem'].location,
         self.TRANSFORMER_CLASS_TO_TEST,
         explicit_graded=graded,
     )
    def test_grades_collected_basic(self):

        blocks = self.build_course_with_problems()
        block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers)

        self.assert_collected_xblock_fields(
            block_structure,
            blocks[u'problem'].location,
            weight=self.problem_metadata[u'weight'],
            graded=self.problem_metadata[u'graded'],
            has_score=True,
            due=self.problem_metadata[u'due'],
            format=None,
        )
Esempio n. 30
0
 def get_blocks_and_check_against_expected(self, user, expected_blocks):
     """
     Calls the course API as the specified user and checks the
     output against a specified set of expected blocks.
     """
     block_structure = get_course_blocks(
         user,
         self.course.location,
         self.transformers,
     )
     self.assertEqual(
         set(block_structure.get_block_keys()),
         set(self.get_block_key_set(self.blocks, *expected_blocks)),
     )
Esempio n. 31
0
File: api.py Progetto: saadow123/1
def get_subsection_grade_percentage(subsection_usage_key, user):
    """
    Computes grade percentage for a subsection in a given course for a user

    Arguments:
        subsection_usage_key: key of subsection
        user: The user whose grade needs to be computed

    Returns:
        User's grade percentage for given subsection
    """
    try:
        subsection_structure = get_course_blocks(user, subsection_usage_key)
        if any(subsection_structure):
            subsection_grade_factory = SubsectionGradeFactory(
                user, course_structure=subsection_structure)
            if subsection_usage_key in subsection_structure:
                subsection_grade = subsection_grade_factory.update(
                    subsection_structure[subsection_usage_key])
                return _get_subsection_percentage(subsection_grade)
    except ItemNotFoundError as err:
        log.warning(u"Could not find course_block for subsection=%s error=%s",
                    subsection_usage_key, err)
    return 0.0
Esempio n. 32
0
    def test_collecting_staff_only_problem(self):
        # Demonstrate that the problem data can by collected by the SystemUser
        # even if the block has access restrictions placed on it.
        problem_metadata = {
            u'graded': True,
            u'weight': 1,
            u'due': datetime.datetime(2016, 10, 16, 0, 4, 0, tzinfo=pytz.utc),
            u'visible_to_staff_only': True,
        }

        blocks = self.build_course_with_problems(metadata=problem_metadata)
        block_structure = get_course_blocks(self.student,
                                            blocks[u'course'].location,
                                            self.transformers)

        self.assert_collected_xblock_fields(
            block_structure,
            blocks[u'problem'].location,
            weight=problem_metadata[u'weight'],
            graded=problem_metadata[u'graded'],
            has_score=True,
            due=problem_metadata[u'due'],
            format=None,
        )
Esempio n. 33
0
    def create(self,
               student,
               course,
               collected_block_structure=None,
               read_only=True):
        """
        Returns the CourseGrade object for the given student and course.

        If read_only is True, doesn't save any updates to the grades.
        Raises a PermissionDenied if the user does not have course access.
        """
        course_structure = get_course_blocks(
            student,
            course.location,
            collected_block_structure=collected_block_structure,
        )

        # if user does not have access to this course, throw an exception
        if not self._user_has_access_to_course(course_structure):
            raise PermissionDenied("User does not have access to this course")

        return (self._get_saved_grade(student, course, course_structure)
                or self._compute_and_update_grade(student, course,
                                                  course_structure, read_only))
Esempio n. 34
0
def _update_subsection_grades(course_key, scored_block_usage_key,
                              only_if_higher, user_id):
    """
    A helper function to update subsection grades in the database
    for each subsection containing the given block, and to signal
    that those subsection grades were updated.
    """
    student = User.objects.get(id=user_id)
    store = modulestore()
    with store.bulk_operations(course_key):
        course_structure = get_course_blocks(
            student, store.make_course_usage_key(course_key))
        subsections_to_update = course_structure.get_transformer_block_field(
            scored_block_usage_key,
            GradesTransformer,
            'subsections',
            set(),
        )

        course = store.get_course(course_key, depth=0)
        subsection_grade_factory = SubsectionGradeFactory(
            student, course, course_structure)

        for subsection_usage_key in subsections_to_update:
            if subsection_usage_key in course_structure:
                subsection_grade = subsection_grade_factory.update(
                    course_structure[subsection_usage_key],
                    only_if_higher,
                )
                SUBSECTION_SCORE_CHANGED.send(
                    sender=None,
                    course=course,
                    course_structure=course_structure,
                    user=student,
                    subsection_grade=subsection_grade,
                )
Esempio n. 35
0
def get_subsection_completion_percentage(subsection_usage_key, user):
    """
    Computes completion percentage for a subsection in a given course for a user
    Arguments:
        subsection_usage_key: key of subsection
        user: The user whose completion percentage needs to be computed
    Returns:
        User's completion percentage for given subsection
    """
    subsection_completion_percentage = 0.0
    try:
        subsection_structure = get_course_blocks(user, subsection_usage_key)
        if any(subsection_structure):
            completable_blocks = [
                block for block in subsection_structure if block.block_type
                not in ['chapter', 'sequential', 'vertical']
            ]
            if not completable_blocks:
                return 0
            subsection_completion_total = 0
            course_block_completions = BlockCompletion.get_course_completions(
                user, subsection_usage_key.course_key)
            for block in completable_blocks:
                if course_block_completions.get(block):
                    subsection_completion_total += course_block_completions.get(
                        block)
            subsection_completion_percentage = min(
                100 *
                (subsection_completion_total / float(len(completable_blocks))),
                100)

    except ItemNotFoundError as err:
        log.warning("Could not find course_block for subsection=%s error=%s",
                    subsection_usage_key, err)

    return subsection_completion_percentage
Esempio n. 36
0
 def setUp(self):
     super(TestVariedMetadata, self).setUp()
     self.course = CourseFactory.create()
     self.chapter = ItemFactory.create(
         parent=self.course,
         category="chapter",
         display_name="Test Chapter"
     )
     self.sequence = ItemFactory.create(
         parent=self.chapter,
         category='sequential',
         display_name="Test Sequential 1",
         graded=True
     )
     self.vertical = ItemFactory.create(
         parent=self.sequence,
         category='vertical',
         display_name='Test Vertical 1'
     )
     self.problem_xml = u'''
         <problem url_name="capa-optionresponse">
           <optionresponse>
             <optioninput options="('Correct', 'Incorrect')" correct="Correct"></optioninput>
             <optioninput options="('Correct', 'Incorrect')" correct="Correct"></optioninput>
           </optionresponse>
         </problem>
     '''
     self.request = get_mock_request(UserFactory())
     self.client.login(username=self.request.user.username, password="******")
     CourseEnrollment.enroll(self.request.user, self.course.id)
     course_structure = get_course_blocks(self.request.user, self.course.location)
     self.subsection_factory = SubsectionGradeFactory(
         self.request.user,
         course_structure=course_structure,
         course=self.course,
     )
Esempio n. 37
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)

        if not course_home_mfe_progress_tab_is_active(course_key):
            raise Http404

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_attribute('course_id', course_key_string)
        monitoring_utils.set_custom_attribute('user_id', request.user.id)
        monitoring_utils.set_custom_attribute('is_staff',
                                              request.user.is_staff)

        _, request.user = setup_masquerade(request,
                                           course_key,
                                           staff_access=has_access(
                                               request.user, 'staff',
                                               course_key),
                                           reset_masquerade_data=True)

        course = get_course_with_access(request.user,
                                        'load',
                                        course_key,
                                        check_if_enrolled=True)

        enrollment_mode, _ = CourseEnrollment.enrollment_mode_for_user(
            request.user, course_key)

        # The block structure is used for both the course_grade and has_scheduled content fields
        # So it is called upfront and reused for optimization purposes
        collected_block_structure = get_block_structure_manager(
            course_key).get_collected()
        course_grade = CourseGradeFactory().read(
            request.user, collected_block_structure=collected_block_structure)

        # Get has_scheduled_content data
        transformers = BlockStructureTransformers()
        transformers += [start_date.StartDateTransformer()]
        usage_key = collected_block_structure.root_block_usage_key
        course_blocks = get_course_blocks(
            request.user,
            usage_key,
            transformers=transformers,
            collected_block_structure=collected_block_structure,
            include_has_scheduled_content=True)
        has_scheduled_content = course_blocks.get_xblock_field(
            usage_key, 'has_scheduled_content')

        # Get user_has_passing_grade data
        user_has_passing_grade = False
        if not request.user.is_anonymous:
            user_grade = course_grade.percent
            user_has_passing_grade = user_grade >= course.lowest_passing_grade

        descriptor = modulestore().get_course(course_key)
        grading_policy = descriptor.grading_policy
        verification_status = IDVerificationService.user_status(request.user)
        verification_link = None
        if verification_status['status'] is None or verification_status[
                'status'] == 'expired':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        elif verification_status['status'] == 'must_reverify':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        verification_data = {
            'link': verification_link,
            'status': verification_status['status'],
            'status_date': verification_status['status_date'],
        }

        data = {
            'end':
            course.end,
            'user_has_passing_grade':
            user_has_passing_grade,
            'certificate_data':
            get_cert_data(request.user, course, enrollment_mode, course_grade),
            'completion_summary':
            get_course_blocks_completion_summary(course_key, request.user),
            'course_grade':
            course_grade,
            'has_scheduled_content':
            has_scheduled_content,
            'section_scores':
            course_grade.chapter_grades.values(),
            'enrollment_mode':
            enrollment_mode,
            'grading_policy':
            grading_policy,
            'studio_url':
            get_studio_url(course, 'settings/grading'),
            'verification_data':
            verification_data,
        }
        context = self.get_serializer_context()
        context['staff_access'] = bool(
            has_access(request.user, 'staff', course))
        context['course_key'] = course_key
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
Esempio n. 38
0
def get_blocks(
    request,
    usage_key,
    user=None,
    depth=None,
    nav_depth=None,
    requested_fields=None,
    block_counts=None,
    student_view_data=None,
    return_type='dict',
):
    """
    Return a serialized representation of the course blocks.

    Arguments:
        request (HTTPRequest): Used for calling django reverse.
        usage_key (UsageKey): Identifies the starting block of interest.
        user (User): Optional user object for whom the blocks are being
            retrieved. If None, blocks are returned regardless of access checks.
        depth (integer or None): Identifies the depth of the tree to return
            starting at the root block.  If None, the entire tree starting at
            the root is returned.
        nav_depth (integer): Optional parameter that indicates how far deep to
            traverse into the block hierarchy before bundling all the
            descendants for navigation.
        requested_fields (list): Optional list of names of additional fields
            to return for each block.  Supported fields are listed in
            transformers.SUPPORTED_FIELDS.
        block_counts (list): Optional list of names of block types for which to
            return an aggregate count of blocks.
        student_view_data (list): Optional list of names of block types for
            which blocks to return their student_view_data.
        return_type (string): Possible values are 'dict' or 'list'. Indicates
            the format for returning the blocks.
    """
    # create ordered list of transformers, adding BlocksAPITransformer at end.
    transformers = BlockStructureTransformers()
    if user is not None:
        transformers += COURSE_BLOCK_ACCESS_TRANSFORMERS + [
            ProctoredExamTransformer()
        ]
    transformers += [
        BlocksAPITransformer(block_counts, student_view_data, depth, nav_depth)
    ]

    # transform
    blocks = get_course_blocks(user, usage_key, transformers)

    # serialize
    serializer_context = {
        'request': request,
        'block_structure': blocks,
        'requested_fields': requested_fields or [],
    }

    if return_type == 'dict':
        serializer = BlockDictSerializer(blocks,
                                         context=serializer_context,
                                         many=False)
    else:
        serializer = BlockSerializer(blocks,
                                     context=serializer_context,
                                     many=True)

    # return serialized data
    return serializer.data
Esempio n. 39
0
 def setUp(self):
     super(TestCourseGradeLogging, self).setUp()
     self.course = CourseFactory.create()
     with self.store.bulk_operations(self.course.id):
         self.chapter = ItemFactory.create(
             parent=self.course,
             category="chapter",
             display_name="Test Chapter"
         )
         self.sequence = ItemFactory.create(
             parent=self.chapter,
             category='sequential',
             display_name="Test Sequential 1",
             graded=True
         )
         self.sequence_2 = ItemFactory.create(
             parent=self.chapter,
             category='sequential',
             display_name="Test Sequential 2",
             graded=True
         )
         self.sequence_3 = ItemFactory.create(
             parent=self.chapter,
             category='sequential',
             display_name="Test Sequential 3",
             graded=False
         )
         self.vertical = ItemFactory.create(
             parent=self.sequence,
             category='vertical',
             display_name='Test Vertical 1'
         )
         self.vertical_2 = ItemFactory.create(
             parent=self.sequence_2,
             category='vertical',
             display_name='Test Vertical 2'
         )
         self.vertical_3 = ItemFactory.create(
             parent=self.sequence_3,
             category='vertical',
             display_name='Test Vertical 3'
         )
         problem_xml = MultipleChoiceResponseXMLFactory().build_xml(
             question_text='The correct answer is Choice 2',
             choices=[False, False, True, False],
             choice_names=['choice_0', 'choice_1', 'choice_2', 'choice_3']
         )
         self.problem = ItemFactory.create(
             parent=self.vertical,
             category="problem",
             display_name="test_problem_1",
             data=problem_xml
         )
         self.problem_2 = ItemFactory.create(
             parent=self.vertical_2,
             category="problem",
             display_name="test_problem_2",
             data=problem_xml
         )
         self.problem_3 = ItemFactory.create(
             parent=self.vertical_3,
             category="problem",
             display_name="test_problem_3",
             data=problem_xml
         )
     self.request = get_mock_request(UserFactory())
     self.client.login(username=self.request.user.username, password="******")
     self.course_structure = get_course_blocks(self.request.user, self.course.location)
     self.subsection_grade_factory = SubsectionGradeFactory(self.request.user, self.course, self.course_structure)
     CourseEnrollment.enroll(self.request.user, self.course.id)
Esempio n. 40
0
def get_course_assignments(course_key, user, include_access=False):  # lint-amnesty, pylint: disable=too-many-statements
    """
    Returns a list of assignment (at the subsection/sequential level) due dates for the given course.

    Each returned object is a namedtuple with fields: title, url, date, contains_gated_content, complete, past_due,
    assignment_type
    """
    if not user.id:
        return []
    store = modulestore()
    course_usage_key = store.make_course_usage_key(course_key)
    block_data = get_course_blocks(user,
                                   course_usage_key,
                                   allow_start_dates_in_future=True,
                                   include_completion=True)

    now = datetime.now(pytz.UTC)
    assignments = []
    for section_key in block_data.get_children(course_usage_key):  # lint-amnesty, pylint: disable=too-many-nested-blocks
        for subsection_key in block_data.get_children(section_key):
            due = block_data.get_xblock_field(subsection_key, 'due')
            graded = block_data.get_xblock_field(subsection_key, 'graded',
                                                 False)
            if due and graded:
                first_component_block_id = get_first_component_of_block(
                    subsection_key, block_data)
                contains_gated_content = include_access and block_data.get_xblock_field(
                    subsection_key, 'contains_gated_content', False)
                title = block_data.get_xblock_field(subsection_key,
                                                    'display_name',
                                                    _('Assignment'))

                assignment_type = block_data.get_xblock_field(
                    subsection_key, 'format', None)

                url = None
                start = block_data.get_xblock_field(subsection_key, 'start')
                assignment_released = not start or start < now
                if assignment_released:
                    url = reverse('jump_to', args=[course_key, subsection_key])

                complete = is_block_structure_complete_for_assignments(
                    block_data, subsection_key)
                past_due = not complete and due < now
                assignments.append(
                    _Assignment(subsection_key, title, url, due,
                                contains_gated_content, complete, past_due,
                                assignment_type, None,
                                first_component_block_id))

            # Load all dates for ORA blocks as separate assignments
            descendents = block_data.get_children(subsection_key)
            while descendents:
                descendent = descendents.pop()
                descendents.extend(block_data.get_children(descendent))
                if block_data.get_xblock_field(descendent, 'category',
                                               None) == 'openassessment':
                    graded = block_data.get_xblock_field(
                        descendent, 'graded', False)
                    has_score = block_data.get_xblock_field(
                        descendent, 'has_score', False)
                    weight = block_data.get_xblock_field(
                        descendent, 'weight', 1)
                    if not (graded and has_score and
                            (weight is None or weight > 0)):
                        continue

                    all_assessments = [{
                        'name':
                        'submission',
                        'due':
                        block_data.get_xblock_field(descendent,
                                                    'submission_due'),
                        'start':
                        block_data.get_xblock_field(descendent,
                                                    'submission_start'),
                        'required':
                        True
                    }]

                    valid_assessments = block_data.get_xblock_field(
                        descendent, 'valid_assessments')
                    if valid_assessments:
                        all_assessments.extend(valid_assessments)

                    assignment_type = block_data.get_xblock_field(
                        descendent, 'format', None)
                    complete = is_block_structure_complete_for_assignments(
                        block_data, descendent)

                    block_title = block_data.get_xblock_field(
                        descendent, 'title', _('Open Response Assessment'))

                    for assessment in all_assessments:
                        due = parse_date(assessment.get('due')).replace(tzinfo=pytz.UTC) if assessment.get('due') else None  # lint-amnesty, pylint: disable=line-too-long
                        if due is None:
                            continue

                        assessment_name = assessment.get('name')
                        if assessment_name is None:
                            continue

                        if assessment_name == 'self-assessment':
                            assessment_type = _("Self Assessment")
                        elif assessment_name == 'peer-assessment':
                            assessment_type = _("Peer Assessment")
                        elif assessment_name == 'staff-assessment':
                            assessment_type = _("Staff Assessment")
                        elif assessment_name == 'submission':
                            assessment_type = _("Submission")
                        else:
                            assessment_type = assessment_name
                        title = f"{block_title} ({assessment_type})"
                        url = ''
                        start = parse_date(assessment.get('start')).replace(tzinfo=pytz.UTC) if assessment.get('start') else None  # lint-amnesty, pylint: disable=line-too-long
                        assignment_released = not start or start < now
                        if assignment_released:
                            url = reverse('jump_to',
                                          args=[course_key, descendent])

                        past_due = not complete and due and due < now
                        first_component_block_id = str(descendent)
                        assignments.append(
                            _Assignment(
                                descendent,
                                title,
                                url,
                                due,
                                False,
                                complete,
                                past_due,
                                assignment_type,
                                _("Open Response Assessment due dates are set by your instructor and can't be shifted."
                                  ),
                                first_component_block_id,
                            ))

    return assignments
def get_blocks(
        request,
        usage_key,
        user=None,
        depth=None,
        nav_depth=None,
        requested_fields=None,
        block_counts=None,
        student_view_data=None,
        return_type='dict',
        block_types_filter=None,
        hide_access_denials=False,
        allow_start_dates_in_future=False,
):
    """
    Return a serialized representation of the course blocks.

    Arguments:
        request (HTTPRequest): Used for calling django reverse.
        usage_key (UsageKey): Identifies the starting block of interest.
        user (User): Optional user object for whom the blocks are being
            retrieved. If None, blocks are returned regardless of access checks.
        depth (integer or None): Identifies the depth of the tree to return
            starting at the root block.  If None, the entire tree starting at
            the root is returned.
        nav_depth (integer): Optional parameter that indicates how far deep to
            traverse into the block hierarchy before bundling all the
            descendants for navigation.
        requested_fields (list): Optional list of names of additional fields
            to return for each block.  Supported fields are listed in
            transformers.SUPPORTED_FIELDS.
        block_counts (list): Optional list of names of block types for which to
            return an aggregate count of blocks.
        student_view_data (list): Optional list of names of block types for
            which blocks to return their student_view_data.
        return_type (string): Possible values are 'dict' or 'list'. Indicates
            the format for returning the blocks.
        block_types_filter (list): Optional list of block type names used to filter
            the final result of returned blocks.
        hide_access_denials (bool): When True, filter out any blocks that were
            denied access to the user, even if they have access denial messages
            attached.
        allow_start_dates_in_future (bool): When True, will allow blocks to be
            returned that can bypass the StartDateTransformer's filter to show
            blocks with start dates in the future.
    """

    if HIDE_ACCESS_DENIALS_FLAG.is_enabled():
        hide_access_denials = True

    # create ordered list of transformers, adding BlocksAPITransformer at end.
    transformers = BlockStructureTransformers()
    if requested_fields is None:
        requested_fields = []
    include_completion = 'completion' in requested_fields
    include_effort_estimation = (EffortEstimationTransformer.EFFORT_TIME in requested_fields or
                                 EffortEstimationTransformer.EFFORT_ACTIVITIES in requested_fields)
    include_gated_sections = 'show_gated_sections' in requested_fields
    include_has_scheduled_content = 'has_scheduled_content' in requested_fields
    include_special_exams = 'special_exam_info' in requested_fields

    if user is not None:
        transformers += course_blocks_api.get_course_block_access_transformers(user)
        transformers += [
            MilestonesAndSpecialExamsTransformer(
                include_special_exams=include_special_exams,
                include_gated_sections=include_gated_sections
            ),
            HiddenContentTransformer()
        ]

    # Note: A change to the BlockCompletionTransformer (https://github.com/edx/edx-platform/pull/27622/)
    # will be introducing a bug if hide_access_denials is True.  I'm accepting this risk because in
    # the AccessDeniedMessageFilterTransformer, there is note about deleting it and I believe it is
    # technically deprecated functionality. The only use case where hide_access_denials is True
    # (outside of explicitly setting the temporary waffle flag) is in lms/djangoapps/course_api/blocks/urls.py
    # for a v1 api that I also believe should have been deprecated and removed. When this code is removed,
    # please also remove this comment. Thanks!
    if hide_access_denials:
        transformers += [AccessDeniedMessageFilterTransformer()]

    if include_effort_estimation:
        transformers += [EffortEstimationTransformer()]

    transformers += [
        BlocksAPITransformer(
            block_counts,
            student_view_data,
            depth,
            nav_depth
        )
    ]

    # transform
    blocks = course_blocks_api.get_course_blocks(
        user,
        usage_key,
        transformers,
        allow_start_dates_in_future=allow_start_dates_in_future,
        include_completion=include_completion,
        include_has_scheduled_content=include_has_scheduled_content
    )

    # filter blocks by types
    if block_types_filter:
        block_keys_to_remove = []
        for block_key in blocks:
            block_type = blocks.get_xblock_field(block_key, 'category')
            if block_type not in block_types_filter:
                block_keys_to_remove.append(block_key)
        for block_key in block_keys_to_remove:
            blocks.remove_block(block_key, keep_descendants=True)

    # serialize
    serializer_context = {
        'request': request,
        'block_structure': blocks,
        'requested_fields': requested_fields or [],
    }

    if return_type == 'dict':
        serializer = BlockDictSerializer(blocks, context=serializer_context, many=False)
    else:
        serializer = BlockSerializer(blocks, context=serializer_context, many=True)

    # return serialized data
    return serializer.data
Esempio n. 42
0
def get_course_blocks_backend(*args, **kwargs):
    """ Real backend to get course_blocks """
    return get_course_blocks(*args, **kwargs)
Esempio n. 43
0
 def test_course_version_not_collected_in_old_mongo(self):
     blocks = self.build_course_with_problems()
     block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers)
     self.assertIsNone(block_structure.get_xblock_field(blocks[u'course'].location, u'course_version'))
Esempio n. 44
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)
        student_id = kwargs.get('student_id')

        if not course_home_mfe_progress_tab_is_active(course_key):
            raise Http404

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_attribute('course_id', course_key_string)
        monitoring_utils.set_custom_attribute('user_id', request.user.id)
        monitoring_utils.set_custom_attribute('is_staff',
                                              request.user.is_staff)
        is_staff = bool(has_access(request.user, 'staff', course_key))

        student = self._get_student_user(request, course_key, student_id,
                                         is_staff)
        username = get_enterprise_learner_generic_name(
            request) or student.username

        course = get_course_with_access(student,
                                        'load',
                                        course_key,
                                        check_if_enrolled=False)

        course_overview = CourseOverview.get_from_id(course_key)
        enrollment = CourseEnrollment.get_enrollment(student, course_key)
        enrollment_mode = getattr(enrollment, 'mode', None)

        if not (enrollment and enrollment.is_active) and not is_staff:
            return Response('User not enrolled.', status=401)

        # The block structure is used for both the course_grade and has_scheduled content fields
        # So it is called upfront and reused for optimization purposes
        collected_block_structure = get_block_structure_manager(
            course_key).get_collected()
        course_grade = CourseGradeFactory().read(
            student, collected_block_structure=collected_block_structure)

        # recalculate course grade from visible grades (stored grade was calculated over all grades, visible or not)
        course_grade.update(visible_grades_only=True,
                            has_staff_access=is_staff)

        # Get has_scheduled_content data
        transformers = BlockStructureTransformers()
        transformers += [
            start_date.StartDateTransformer(),
            ContentTypeGateTransformer()
        ]
        usage_key = collected_block_structure.root_block_usage_key
        course_blocks = get_course_blocks(
            student,
            usage_key,
            transformers=transformers,
            collected_block_structure=collected_block_structure,
            include_has_scheduled_content=True)
        has_scheduled_content = course_blocks.get_xblock_field(
            usage_key, 'has_scheduled_content')

        # Get user_has_passing_grade data
        user_has_passing_grade = False
        if not student.is_anonymous:
            user_grade = course_grade.percent
            user_has_passing_grade = user_grade >= course.lowest_passing_grade

        descriptor = modulestore().get_course(course_key)
        grading_policy = descriptor.grading_policy
        verification_status = IDVerificationService.user_status(student)
        verification_link = None
        if verification_status['status'] is None or verification_status[
                'status'] == 'expired':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        elif verification_status['status'] == 'must_reverify':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        verification_data = {
            'link': verification_link,
            'status': verification_status['status'],
            'status_date': verification_status['status_date'],
        }

        access_expiration = get_access_expiration_data(request.user,
                                                       course_overview)

        data = {
            'access_expiration':
            access_expiration,
            'certificate_data':
            get_cert_data(student, course, enrollment_mode, course_grade),
            'completion_summary':
            get_course_blocks_completion_summary(course_key, student),
            'course_grade':
            course_grade,
            'credit_course_requirements':
            credit_course_requirements(course_key, student),
            'end':
            course.end,
            'enrollment_mode':
            enrollment_mode,
            'grading_policy':
            grading_policy,
            'has_scheduled_content':
            has_scheduled_content,
            'section_scores':
            list(course_grade.chapter_grades.values()),
            'studio_url':
            get_studio_url(course, 'settings/grading'),
            'username':
            username,
            'user_has_passing_grade':
            user_has_passing_grade,
            'verification_data':
            verification_data,
        }
        context = self.get_serializer_context()
        context['staff_access'] = is_staff
        context['course_blocks'] = course_blocks
        context['course_key'] = course_key
        # course_overview and enrollment will be used by VerifiedModeSerializer
        context['course_overview'] = course_overview
        context['enrollment'] = enrollment
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
Esempio n. 45
0
def get_blocks(
    request,
    usage_key,
    user=None,
    depth=None,
    nav_depth=None,
    requested_fields=None,
    block_counts=None,
    student_view_data=None,
    return_type='dict',
    block_types_filter=None,
):
    """
    Return a serialized representation of the course blocks.

    Arguments:
        request (HTTPRequest): Used for calling django reverse.
        usage_key (UsageKey): Identifies the starting block of interest.
        user (User): Optional user object for whom the blocks are being
            retrieved. If None, blocks are returned regardless of access checks.
        depth (integer or None): Identifies the depth of the tree to return
            starting at the root block.  If None, the entire tree starting at
            the root is returned.
        nav_depth (integer): Optional parameter that indicates how far deep to
            traverse into the block hierarchy before bundling all the
            descendants for navigation.
        requested_fields (list): Optional list of names of additional fields
            to return for each block.  Supported fields are listed in
            transformers.SUPPORTED_FIELDS.
        block_counts (list): Optional list of names of block types for which to
            return an aggregate count of blocks.
        student_view_data (list): Optional list of names of block types for
            which blocks to return their student_view_data.
        return_type (string): Possible values are 'dict' or 'list'. Indicates
            the format for returning the blocks.
        block_types_filter (list): Optional list of block type names used to filter
            the final result of returned blocks.
    """
    # create ordered list of transformers, adding BlocksAPITransformer at end.
    transformers = BlockStructureTransformers()
    if requested_fields is None:
        requested_fields = []
    include_completion = 'completion' in requested_fields
    include_special_exams = 'special_exam_info' in requested_fields
    include_gated_sections = 'show_gated_sections' in requested_fields

    if user is not None:
        transformers += course_blocks_api.get_course_block_access_transformers(
            user)
        transformers += [
            MilestonesAndSpecialExamsTransformer(
                include_special_exams=include_special_exams,
                include_gated_sections=include_gated_sections)
        ]
        transformers += [HiddenContentTransformer()]
    transformers += [
        BlocksAPITransformer(block_counts, student_view_data, depth, nav_depth)
    ]

    if include_completion:
        transformers += [BlockCompletionTransformer()]

    # transform
    blocks = course_blocks_api.get_course_blocks(user, usage_key, transformers)

    # filter blocks by types
    if block_types_filter:
        block_keys_to_remove = []
        for block_key in blocks:
            block_type = blocks.get_xblock_field(block_key, 'category')
            if block_type not in block_types_filter:
                block_keys_to_remove.append(block_key)
        for block_key in block_keys_to_remove:
            blocks.remove_block(block_key, keep_descendants=True)

    # serialize
    serializer_context = {
        'request': request,
        'block_structure': blocks,
        'requested_fields': requested_fields or [],
    }

    if return_type == 'dict':
        serializer = BlockDictSerializer(blocks,
                                         context=serializer_context,
                                         many=False)
    else:
        serializer = BlockSerializer(blocks,
                                     context=serializer_context,
                                     many=True)

    # return serialized data
    return serializer.data
Esempio n. 46
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)
        course_usage_key = modulestore().make_course_usage_key(course_key)

        if not course_home_mfe_outline_tab_is_active(course_key):
            raise Http404

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_metric('course_id', course_key_string)
        monitoring_utils.set_custom_metric('user_id', request.user.id)
        monitoring_utils.set_custom_metric('is_staff', request.user.is_staff)

        course = get_course_with_access(request.user,
                                        'load',
                                        course_key,
                                        check_if_enrolled=False)

        _masquerade, request.user = setup_masquerade(
            request,
            course_key,
            staff_access=has_access(request.user, 'staff', course_key),
            reset_masquerade_data=True,
        )

        course_overview = CourseOverview.get_from_id(course_key)
        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
        allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(
            course_key)
        allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC
        is_enrolled = enrollment and enrollment.is_active
        is_staff = has_access(request.user, 'staff', course_key)
        show_enrolled = is_enrolled or is_staff

        show_handouts = show_enrolled or allow_public
        handouts_html = get_course_info_section(
            request, request.user, course, 'handouts') if show_handouts else ''

        # TODO: TNL-7185 Legacy: Refactor to return the offer & expired data and format the message in the MFE
        offer_html = generate_offer_html(request.user, course_overview)
        course_expired_html = generate_course_expired_message(
            request.user, course_overview)

        welcome_message_html = None
        if get_course_tag(request.user, course_key, PREFERENCE_KEY) != 'False':
            if LATEST_UPDATE_FLAG.is_enabled(course_key):
                welcome_message_html = LatestUpdateFragmentView(
                ).latest_update_html(request, course)
            else:
                welcome_message_html = WelcomeMessageFragmentView(
                ).welcome_message_html(request, course)

        enroll_alert = {
            'can_enroll': True,
            'extra_text': None,
        }
        if not show_enrolled:
            if CourseMode.is_masters_only(course_key):
                enroll_alert['can_enroll'] = False
                enroll_alert['extra_text'] = _(
                    'Please contact your degree administrator or '
                    'edX Support if you have questions.')
            elif course.invitation_only:
                enroll_alert['can_enroll'] = False

        course_tools = CourseToolsPluginManager.get_enabled_course_tools(
            request, course_key)
        date_blocks = get_course_date_blocks(course,
                                             request.user,
                                             request,
                                             num_assignments=1)

        # User locale settings
        user_timezone_locale = user_timezone_locale_prefs(request)
        user_timezone = user_timezone_locale['user_timezone']

        dates_tab_link = request.build_absolute_uri(
            reverse('dates', args=[course.id]))
        if course_home_mfe_dates_tab_is_active(course.id):
            dates_tab_link = get_microfrontend_url(course_key=course.id,
                                                   view_name='dates')

        transformers = BlockStructureTransformers()
        transformers += get_course_block_access_transformers(request.user)
        transformers += [
            BlocksAPITransformer(None, None, depth=3),
        ]

        course_blocks = get_course_blocks(request.user,
                                          course_usage_key,
                                          transformers,
                                          include_completion=True)

        dates_widget = {
            'course_date_blocks': [
                block for block in date_blocks
                if not isinstance(block, TodaysDate)
            ],
            'dates_tab_link':
            dates_tab_link,
            'user_timezone':
            user_timezone,
        }

        data = {
            'course_blocks': course_blocks,
            'course_expired_html': course_expired_html,
            'course_tools': course_tools,
            'dates_widget': dates_widget,
            'enroll_alert': enroll_alert,
            'handouts_html': handouts_html,
            'offer_html': offer_html,
            'welcome_message_html': welcome_message_html,
        }
        context = self.get_serializer_context()
        context['course_key'] = course_key
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
Esempio n. 47
0
 def update(self, course):
     """
     Updates the CourseGrade for this Factory's student.
     """
     course_structure = get_course_blocks(self.student, course.location)
     self._compute_and_update_grade(course, course_structure)
Esempio n. 48
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)
        course_usage_key = modulestore().make_course_usage_key(course_key)

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_metric('course_id', course_key_string)
        monitoring_utils.set_custom_metric('user_id', request.user.id)
        monitoring_utils.set_custom_metric('is_staff', request.user.is_staff)

        course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False)

        _, request.user = setup_masquerade(
            request,
            course_key,
            staff_access=has_access(request.user, 'staff', course_key),
            reset_masquerade_data=True,
        )

        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
        allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key)
        allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC
        is_enrolled = enrollment and enrollment.is_active
        is_staff = has_access(request.user, 'staff', course_key)

        show_handouts = is_enrolled or is_staff or allow_public
        handouts_html = get_course_info_section(request, request.user, course, 'handouts') if show_handouts else ''

        welcome_message_html = None
        if get_course_tag(request.user, course_key, PREFERENCE_KEY) != 'False':
            if LATEST_UPDATE_FLAG.is_enabled(course_key):
                welcome_message_html = LatestUpdateFragmentView().latest_update_html(request, course)
            else:
                welcome_message_html = WelcomeMessageFragmentView().welcome_message_html(request, course)

        course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key)
        date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1)

        # User locale settings
        user_timezone_locale = user_timezone_locale_prefs(request)
        user_timezone = user_timezone_locale['user_timezone']

        dates_tab_link = request.build_absolute_uri(reverse('dates', args=[course.id]))
        if course_home_mfe_dates_tab_is_active(course.id):
            dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates')

        transformers = BlockStructureTransformers()
        transformers += get_course_block_access_transformers(request.user)
        transformers += [
            BlocksAPITransformer(None, None, depth=3),
        ]

        course_blocks = get_course_blocks(request.user, course_usage_key, transformers, include_completion=True)

        dates_widget = {
            'course_date_blocks': [block for block in date_blocks if not isinstance(block, TodaysDate)],
            'dates_tab_link': dates_tab_link,
            'user_timezone': user_timezone,
        }

        data = {
            'course_blocks': course_blocks,
            'course_tools': course_tools,
            'dates_widget': dates_widget,
            'handouts_html': handouts_html,
            'welcome_message_html': welcome_message_html,
        }
        context = self.get_serializer_context()
        context['course_key'] = course_key
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
Esempio n. 49
0
 def test_course_version_collected_in_split(self):
     with self.store.default_store(ModuleStoreEnum.Type.split):
         blocks = self.build_course_with_problems()
     block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers)
     self.assertIsNotNone(block_structure.get_xblock_field(blocks[u'course'].location, u'course_version'))
def compute_is_prereq_met(content_id, user_id, recalc_on_unmet=False):
    """
    Returns true if the prequiste has been met for a given milestone.
    Will recalculate the subsection grade if specified and prereq unmet

    Arguments:
        content_id (BlockUsageLocator): BlockUsageLocator for the content
        user_id: The id of the user
        recalc_on_unmet: Recalculate the grade if prereq has not yet been met

    Returns:
        tuple: True|False,
        prereq_meta_info = { 'url': prereq_url|None, 'display_name': prereq_name|None}
    """
    course_key = content_id.course_key

    # if unfullfilled milestones exist it means prereq has not been met
    unfulfilled_milestones = milestones_helpers.get_course_content_milestones(
        course_key, content_id, 'requires', user_id)

    prereq_met = not unfulfilled_milestones
    prereq_meta_info = {'url': None, 'display_name': None}

    if prereq_met or not recalc_on_unmet:
        return prereq_met, prereq_meta_info

    milestone = unfulfilled_milestones[0]
    student = User.objects.get(id=user_id)
    store = modulestore()

    with store.bulk_operations(course_key):
        subsection_usage_key = UsageKey.from_string(
            _get_gating_block_id(milestone))
        subsection = store.get_item(subsection_usage_key)
        prereq_meta_info = {
            'url':
            reverse('jump_to',
                    kwargs={
                        'course_id': course_key,
                        'location': subsection_usage_key
                    }),
            'display_name':
            subsection.display_name
        }

        try:
            subsection_structure = get_course_blocks(student,
                                                     subsection_usage_key)
            if any(subsection_structure):
                subsection_grade_factory = SubsectionGradeFactory(
                    student, course_structure=subsection_structure)
                if subsection_usage_key in subsection_structure:
                    # this will force a recalcuation of the subsection grade
                    subsection_grade = subsection_grade_factory.update(
                        subsection_structure[subsection_usage_key],
                        persist_grade=False)
                    prereq_met = update_milestone(milestone, subsection_grade,
                                                  milestone, user_id)
        except ItemNotFoundError as err:
            log.warning(
                "Could not find course_block for subsection=%s error=%s",
                subsection_usage_key, err)

    return prereq_met, prereq_meta_info
Esempio n. 51
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)
        course_usage_key = modulestore().make_course_usage_key(course_key)

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_metric('course_id', course_key_string)
        monitoring_utils.set_custom_metric('user_id', request.user.id)
        monitoring_utils.set_custom_metric('is_staff', request.user.is_staff)

        course_tools = CourseToolsPluginManager.get_enabled_course_tools(
            request, course_key)

        course = get_course_with_access(request.user,
                                        'load',
                                        course_key,
                                        check_if_enrolled=False)
        date_blocks = get_course_date_blocks(course,
                                             request.user,
                                             request,
                                             num_assignments=1)

        # User locale settings
        user_timezone_locale = user_timezone_locale_prefs(request)
        user_timezone = user_timezone_locale['user_timezone']

        dates_tab_link = request.build_absolute_uri(
            reverse('dates', args=[course.id]))
        if course_home_mfe_dates_tab_is_active(course.id):
            dates_tab_link = get_microfrontend_url(course_key=course.id,
                                                   view_name='dates')

        transformers = BlockStructureTransformers()
        transformers += course_blocks_api.get_course_block_access_transformers(
            request.user)
        transformers += [
            BlocksAPITransformer(None, None, depth=3),
        ]

        course_blocks = get_course_blocks(request.user,
                                          course_usage_key,
                                          transformers,
                                          include_completion=True)

        dates_widget = {
            'course_date_blocks': [
                block for block in date_blocks
                if not isinstance(block, TodaysDate)
            ],
            'dates_tab_link':
            dates_tab_link,
            'user_timezone':
            user_timezone,
        }

        data = {
            'course_tools': course_tools,
            'course_blocks': course_blocks,
            'dates_widget': dates_widget,
        }
        context = self.get_serializer_context()
        context['course_key'] = course_key
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
Esempio n. 52
0
    def _build_student_data(
        cls,
        user_id,
        course_key,
        usage_key_str_list,
        filter_types=None,
    ):
        """
        Generate a list of problem responses for all problem under the
        ``problem_location`` root.
        Arguments:
            user_id (int): The user id for the user generating the report
            course_key (CourseKey): The ``CourseKey`` for the course whose report
                is being generated
            usage_key_str_list (List[str]): The generated report will include these
                blocks and their child blocks.
            filter_types (List[str]): The report generator will only include data for
                block types in this list.
        Returns:
              Tuple[List[Dict], List[str]]: Returns a list of dictionaries
                containing the student data which will be included in the
                final csv, and the features/keys to include in that CSV.
        """
        usage_keys = [
            UsageKey.from_string(usage_key_str).map_into_course(course_key)
            for usage_key_str in usage_key_str_list
        ]
        user = get_user_model().objects.get(pk=user_id)

        student_data = []
        max_count = settings.FEATURES.get('MAX_PROBLEM_RESPONSES_COUNT')

        store = modulestore()
        user_state_client = DjangoXBlockUserStateClient()

        # Each user's generated report data may contain different fields, so we use an OrderedDict to prevent
        # duplication of keys while preserving the order the XBlock provides the keys in.
        student_data_keys = OrderedDict()

        with store.bulk_operations(course_key):
            for usage_key in usage_keys:  # lint-amnesty, pylint: disable=too-many-nested-blocks
                if max_count is not None and max_count <= 0:
                    break
                course_blocks = get_course_blocks(user, usage_key)
                base_path = cls._build_block_base_path(
                    store.get_item(usage_key))
                for title, path, block_key in cls._build_problem_list(
                        course_blocks, usage_key):
                    # Chapter and sequential blocks are filtered out since they include state
                    # which isn't useful for this report.
                    if block_key.block_type in ('sequential', 'chapter'):
                        continue

                    if filter_types is not None and block_key.block_type not in filter_types:
                        continue

                    block = store.get_item(block_key)
                    generated_report_data = defaultdict(list)

                    # Blocks can implement the generate_report_data method to provide their own
                    # human-readable formatting for user state.
                    if hasattr(block, 'generate_report_data'):
                        try:
                            user_state_iterator = user_state_client.iter_all_for_block(
                                block_key)
                            for username, state in block.generate_report_data(
                                    user_state_iterator, max_count):
                                generated_report_data[username].append(state)
                        except NotImplementedError:
                            pass

                    responses = []

                    for response in list_problem_responses(
                            course_key, block_key, max_count):
                        response['title'] = title
                        # A human-readable location for the current block
                        response['location'] = ' > '.join(base_path + path)
                        # A machine-friendly location for the current block
                        response['block_key'] = str(block_key)
                        # A block that has a single state per user can contain multiple responses
                        # within the same state.
                        user_states = generated_report_data.get(
                            response['username'])
                        if user_states:
                            # For each response in the block, copy over the basic data like the
                            # title, location, block_key and state, and add in the responses
                            for user_state in user_states:
                                user_response = response.copy()
                                user_response.update(user_state)

                                # Respect the column order as returned by the xblock, if any.
                                if isinstance(user_state, OrderedDict):
                                    user_state_keys = user_state.keys()
                                else:
                                    user_state_keys = sorted(user_state.keys())
                                for key in user_state_keys:
                                    student_data_keys[key] = 1

                                responses.append(user_response)
                        else:
                            responses.append(response)

                    student_data += responses

                    if max_count is not None:
                        max_count -= len(responses)
                        if max_count <= 0:
                            break

        # Keep the keys in a useful order, starting with username, title and location,
        # then the columns returned by the xblock report generator in sorted order and
        # finally end with the more machine friendly block_key and state.
        student_data_keys_list = (['username', 'title', 'location'] +
                                  list(student_data_keys.keys()) +
                                  ['block_key', 'state'])

        return student_data, student_data_keys_list
Esempio n. 53
0
def get_blocks(
        request,
        usage_key,
        user=None,
        depth=None,
        nav_depth=None,
        requested_fields=None,
        block_counts=None,
        student_view_data=None,
        return_type='dict',
        block_types_filter=None,
        hide_access_denials=False,
        allow_start_dates_in_future=False,
):
    """
    Return a serialized representation of the course blocks.

    Arguments:
        request (HTTPRequest): Used for calling django reverse.
        usage_key (UsageKey): Identifies the starting block of interest.
        user (User): Optional user object for whom the blocks are being
            retrieved. If None, blocks are returned regardless of access checks.
        depth (integer or None): Identifies the depth of the tree to return
            starting at the root block.  If None, the entire tree starting at
            the root is returned.
        nav_depth (integer): Optional parameter that indicates how far deep to
            traverse into the block hierarchy before bundling all the
            descendants for navigation.
        requested_fields (list): Optional list of names of additional fields
            to return for each block.  Supported fields are listed in
            transformers.SUPPORTED_FIELDS.
        block_counts (list): Optional list of names of block types for which to
            return an aggregate count of blocks.
        student_view_data (list): Optional list of names of block types for
            which blocks to return their student_view_data.
        return_type (string): Possible values are 'dict' or 'list'. Indicates
            the format for returning the blocks.
        block_types_filter (list): Optional list of block type names used to filter
            the final result of returned blocks.
        hide_access_denials (bool): When True, filter out any blocks that were
            denied access to the user, even if they have access denial messages
            attached.
        allow_start_dates_in_future (bool): When True, will allow blocks to be
            returned that can bypass the StartDateTransformer's filter to show
            blocks with start dates in the future.
    """

    if HIDE_ACCESS_DENIALS_FLAG.is_enabled():
        hide_access_denials = True

    # create ordered list of transformers, adding BlocksAPITransformer at end.
    transformers = BlockStructureTransformers()
    if requested_fields is None:
        requested_fields = []
    include_completion = 'completion' in requested_fields
    include_effort_estimation = (EffortEstimationTransformer.EFFORT_TIME in requested_fields or
                                 EffortEstimationTransformer.EFFORT_ACTIVITIES in requested_fields)
    include_gated_sections = 'show_gated_sections' in requested_fields
    include_has_scheduled_content = 'has_scheduled_content' in requested_fields
    include_special_exams = 'special_exam_info' in requested_fields

    if user is not None:
        transformers += course_blocks_api.get_course_block_access_transformers(user)
        transformers += [
            MilestonesAndSpecialExamsTransformer(
                include_special_exams=include_special_exams,
                include_gated_sections=include_gated_sections
            ),
            HiddenContentTransformer()
        ]

    if hide_access_denials:
        transformers += [AccessDeniedMessageFilterTransformer()]

    # TODO: Remove this after REVE-52 lands and old-mobile-app traffic falls to < 5% of mobile traffic
    if is_request_from_mobile_app(request):
        transformers += [HideEmptyTransformer()]

    if include_effort_estimation:
        transformers += [EffortEstimationTransformer()]

    transformers += [
        BlocksAPITransformer(
            block_counts,
            student_view_data,
            depth,
            nav_depth
        )
    ]

    # transform
    blocks = course_blocks_api.get_course_blocks(
        user,
        usage_key,
        transformers,
        allow_start_dates_in_future=allow_start_dates_in_future,
        include_completion=include_completion,
        include_has_scheduled_content=include_has_scheduled_content
    )

    # filter blocks by types
    if block_types_filter:
        block_keys_to_remove = []
        for block_key in blocks:
            block_type = blocks.get_xblock_field(block_key, 'category')
            if block_type not in block_types_filter:
                block_keys_to_remove.append(block_key)
        for block_key in block_keys_to_remove:
            blocks.remove_block(block_key, keep_descendants=True)

    # serialize
    serializer_context = {
        'request': request,
        'block_structure': blocks,
        'requested_fields': requested_fields or [],
    }

    if return_type == 'dict':
        serializer = BlockDictSerializer(blocks, context=serializer_context, many=False)
    else:
        serializer = BlockSerializer(blocks, context=serializer_context, many=True)

    # return serialized data
    return serializer.data
Esempio n. 54
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)
        student_id = kwargs.get('student_id')
        if student_id:
            try:
                student_id = int(student_id)
            except ValueError:
                raise Http404

        if not course_home_mfe_progress_tab_is_active(course_key):
            raise Http404

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_attribute('course_id', course_key_string)
        monitoring_utils.set_custom_attribute('user_id', request.user.id)
        monitoring_utils.set_custom_attribute('is_staff',
                                              request.user.is_staff)
        is_staff = bool(has_access(request.user, 'staff', course_key))

        if student_id is None or student_id == request.user.id:
            _, student = setup_masquerade(request,
                                          course_key,
                                          staff_access=is_staff,
                                          reset_masquerade_data=True)
        else:
            # When a student_id is passed in, we display the progress page for the user
            # with the provided user id, rather than the requesting user
            try:
                coach_access = has_ccx_coach_role(request.user, course_key)
            except CCXLocatorValidationException:
                coach_access = False

            has_access_on_students_profiles = is_staff or coach_access
            # Requesting access to a different student's profile
            if not has_access_on_students_profiles:
                raise Http404
            try:
                student = User.objects.get(id=student_id)
            except User.DoesNotExist as exc:
                raise Http404 from exc

        username = get_enterprise_learner_generic_name(
            request) or student.username

        course = get_course_with_access(student,
                                        'load',
                                        course_key,
                                        check_if_enrolled=False)

        course_overview = CourseOverview.get_from_id(course_key)
        enrollment = CourseEnrollment.get_enrollment(student, course_key)
        enrollment_mode = getattr(enrollment, 'mode', None)

        if not (enrollment and enrollment.is_active) and not is_staff:
            return Response('User not enrolled.', status=401)

        # The block structure is used for both the course_grade and has_scheduled content fields
        # So it is called upfront and reused for optimization purposes
        collected_block_structure = get_block_structure_manager(
            course_key).get_collected()
        course_grade = CourseGradeFactory().read(
            student, collected_block_structure=collected_block_structure)

        # Get has_scheduled_content data
        transformers = BlockStructureTransformers()
        transformers += [
            start_date.StartDateTransformer(),
            ContentTypeGateTransformer()
        ]
        usage_key = collected_block_structure.root_block_usage_key
        course_blocks = get_course_blocks(
            student,
            usage_key,
            transformers=transformers,
            collected_block_structure=collected_block_structure,
            include_has_scheduled_content=True)
        has_scheduled_content = course_blocks.get_xblock_field(
            usage_key, 'has_scheduled_content')

        # Get user_has_passing_grade data
        user_has_passing_grade = False
        if not student.is_anonymous:
            user_grade = course_grade.percent
            user_has_passing_grade = user_grade >= course.lowest_passing_grade

        descriptor = modulestore().get_course(course_key)
        grading_policy = descriptor.grading_policy
        verification_status = IDVerificationService.user_status(student)
        verification_link = None
        if verification_status['status'] is None or verification_status[
                'status'] == 'expired':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        elif verification_status['status'] == 'must_reverify':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        verification_data = {
            'link': verification_link,
            'status': verification_status['status'],
            'status_date': verification_status['status_date'],
        }

        data = {
            'username':
            username,
            'end':
            course.end,
            'user_has_passing_grade':
            user_has_passing_grade,
            'certificate_data':
            get_cert_data(student, course, enrollment_mode, course_grade),
            'completion_summary':
            get_course_blocks_completion_summary(course_key, student),
            'course_grade':
            course_grade,
            'has_scheduled_content':
            has_scheduled_content,
            'section_scores':
            course_grade.chapter_grades.values(),
            'enrollment_mode':
            enrollment_mode,
            'grading_policy':
            grading_policy,
            'studio_url':
            get_studio_url(course, 'settings/grading'),
            'verification_data':
            verification_data,
        }
        context = self.get_serializer_context()
        context['staff_access'] = is_staff
        context['course_blocks'] = course_blocks
        context['course_key'] = course_key
        # course_overview and enrollment will be used by VerifiedModeSerializerMixin
        context['course_overview'] = course_overview
        context['enrollment'] = enrollment
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
Esempio n. 55
0
    def get_quiz_data(self):
        pr_class = ProblemResponses().__class__
        user_id = user_by_anonymous_id(
            self.xmodule_runtime.anonymous_student_id).id
        course_key = self.course_id

        valid_cohorts = self.get_cohorts()

        usage_key = self.get_quiz_unit()
        if not usage_key:
            raise InvalidKeyError

        user = get_user_model().objects.get(pk=user_id)

        student_data = []

        store = modulestore()

        with store.bulk_operations(course_key):
            try:
                course_blocks = get_course_blocks(user, usage_key)
            except:
                raise QuizNotFound
            usernames = set()
            for title, path, block_key in pr_class._build_problem_list(
                    course_blocks, usage_key):
                # Chapter and sequential blocks are filtered out since they include state
                # which isn't useful for this report.
                if block_key.block_type != "problem":
                    continue

                block = store.get_item(block_key)
                generated_report_data = defaultdict(list)

                # Blocks can implement the generate_report_data method to provide their own
                # human-readable formatting for user state.
                try:
                    user_state_iterator = iter_all_for_block(block_key)
                    for username, state in self.generate_report_data(
                            block, user_state_iterator):
                        generated_report_data[username].append(state)
                except NotImplementedError:
                    pass
                cohorted = is_course_cohorted(self.course_id)

                def in_cohort(user):
                    if cohorted:
                        cohort = get_cohort(user,
                                            course_key,
                                            assign=False,
                                            use_cached=True)
                        if not cohort or cohort.name not in valid_cohorts or (
                                self.cohort and cohort.name != self.cohort):
                            # skip this one if not on the requested cohort or has no cohort (instructor)
                            return False
                    return True

                responses = []
                for response in list_problem_responses(course_key, block_key):
                    # A block that has a single state per user can contain multiple responses
                    # within the same state.
                    try:
                        user = get_user_by_username_or_email(
                            response['username'])
                    except User.DoesNotExist:
                        continue
                    usernames.add(user.username)
                    if not in_cohort(user):
                        continue
                    response['name'] = self.format_name(user.profile.name)
                    user_states = generated_report_data.get(
                        response['username'])
                    response['state'] = json.loads(response['state'])
                    response['state'].pop('input_state', None)
                    response['state'].pop('student_answers', None)

                    if user_states:
                        response['user_states'] = user_states
                    responses.append(response)
                enrollments = CourseEnrollment.objects.filter(
                    course_id=self.course_id)
                for enr in enrollments:
                    if enr.user.username in usernames:
                        continue
                    if in_cohort(enr.user):  # add missing students
                        student_data.append({
                            'username':
                            enr.user.username,
                            'name':
                            self.format_name(enr.user.profile.name)
                        })
                        usernames.add(enr.user.username)
                student_data += responses
        return student_data
Esempio n. 56
0
def get_vertical(current_course):
    '''
    Looks through all the problems a learner has previously loaded and
    finds their parent vertical. Then randomly selects a single vertical
    to show the learner.

    Parameters:
        current_course (CourseLocator): The course the learner is currently in

    Returns a url (str) with the vertical id to render for review.
    '''
    user = crum.get_current_user()

    enroll_user_in_review_course_if_needed(user, current_course)

    store = modulestore()
    course_usage_key = store.make_course_usage_key(current_course)
    course_blocks = get_course_blocks(user, course_usage_key)

    vertical_data = set()

    for block_key, state in get_records(user, current_course):
        block_key = block_key.replace(course_key=store.fill_in_run(block_key.course_key))
        if is_valid_problem(store, block_key, state, course_blocks):
            # If the block_key does not have a subsection (sequential) in it's tree,
            # we should skip it.
            subsection = course_blocks.get_transformer_block_field(
                block_key,
                GradesTransformer,
                'subsections',
                set(),
            )
            if subsection:
                try:
                    vertical = course_blocks.get_parents(block_key)[0]
                    sequential = course_blocks.get_parents(vertical)[0]
                    # This is in case the direct parent of a problem is not a vertical,
                    # we want to keep looking until we find the parent vertical to display.
                    # For example, you may see:
                    # sequential -> vertical -> split_test -> problem
                    # OR
                    # sequential -> vertical -> vertical -> problem
                    # OR
                    # sequential -> vertical -> conditional_block -> problem
                    while sequential.block_type != 'sequential' and vertical.block_type != 'vertical':
                        vertical = sequential
                        sequential = course_blocks.get_parents(vertical)[0]
                # Catches IndexError for the case where the parent we are looking for
                # is not the first element returned in get_parents. This can lead to
                # looking in a part of the tree that does not include what we want. In
                # this case, we will just skip the problem.
                except IndexError:
                    continue

                vertical_data.add(vertical.block_id)
                delete_state_of_review_problem(user, current_course, block_key.block_id)

    if not vertical_data:
        return []

    vertical_to_show = random.sample(vertical_data, 1)[0]
    review_course_id = REVIEW_COURSE_MAPPING[str(current_course)]
    return (XBLOCK_VIEW_URL_TEMPLATE.format(course_id=review_course_id,
                                            type='vertical', xblock_id=vertical_to_show))