예제 #1
0
 def test_persistent_grades_feature_flags(self, global_flag, enabled_for_all_courses, enabled_for_course_1):
     with persistent_grades_feature_flags(
         global_flag=global_flag,
         enabled_for_all_courses=enabled_for_all_courses,
         course_id=self.course_id_1,
         enabled_for_course=enabled_for_course_1
     ):
         assert PersistentGradesEnabledFlag.feature_enabled() == global_flag
         assert PersistentGradesEnabledFlag.feature_enabled(
             self.course_id_1
         ) == (global_flag and (enabled_for_all_courses or enabled_for_course_1))
         assert PersistentGradesEnabledFlag.feature_enabled(
             self.course_id_2
         ) == (global_flag and enabled_for_all_courses)
예제 #2
0
 def test_persistent_grades_not_enabled_on_course(self, default_store):
     with self.store.default_store(default_store):
         self.set_up_course(enable_persistent_grades=False)
         self.assertFalse(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         with check_mongo_calls(0):
             with self.assertNumQueries(0):
                 self._apply_recalculate_subsection_grade()
예제 #3
0
 def test_query_counts(self, default_store, num_mongo_calls, num_sql_calls, create_multiple_subsections):
     with self.store.default_store(default_store):
         self.set_up_course(create_multiple_subsections=create_multiple_subsections)
         self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         with check_mongo_calls(num_mongo_calls):
             with self.assertNumQueries(num_sql_calls):
                 self._apply_recalculate_subsection_grade()
예제 #4
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
    """
    try:
        course_id = kwargs.get('course_id', None)
        usage_id = kwargs.get('usage_id', None)
        student = kwargs.get('user', None)

        course_key = CourseLocator.from_string(course_id)
        if not PersistentGradesEnabledFlag.feature_enabled(course_key):
            return

        usage_key = UsageKey.from_string(usage_id).replace(
            course_key=course_key)

        from lms.djangoapps.grades.new.subsection_grade import SubsectionGradeFactory
        SubsectionGradeFactory(student).update(usage_key, course_key)
    except Exception as ex:  # pylint: disable=broad-except
        log.exception(
            u"Failed to process SCORE_CHANGED signal. "
            "user: %s, course_id: %s, "
            "usage_id: %s. Exception: %s", unicode(student), course_id,
            usage_id, ex.message)
예제 #5
0
 def _get_saved_grade(self, course, course_structure):  # pylint: disable=unused-argument
     """
     Returns the saved grade for the given course and student.
     """
     if PersistentGradesEnabledFlag.feature_enabled(course.id):
         # TODO LATER Retrieve the saved grade for the course, if it exists.
         _pretend_to_save_course_grades()
예제 #6
0
 def test_query_counts(self, default_store, num_mongo_calls, num_sql_calls, create_multiple_subsections):
     with self.store.default_store(default_store):
         self.set_up_course(create_multiple_subsections=create_multiple_subsections)
         self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         with check_mongo_calls(num_mongo_calls):
             with self.assertNumQueries(num_sql_calls):
                 self._apply_recalculate_subsection_grade()
 def _get_saved_grade(self, subsection, course_structure, course):  # pylint: disable=unused-argument
     """
     Returns the saved grade for the student and subsection.
     """
     if PersistentGradesEnabledFlag.feature_enabled(course.id):
         try:
             model = PersistentSubsectionGrade.read_grade(
                 user_id=self.student.id,
                 usage_key=subsection.location,
             )
             subsection_grade = SubsectionGrade(subsection)
             subsection_grade.load_from_data(model, course_structure,
                                             self._scores_client,
                                             self._submissions_scores)
             log.warning(
                 u"Persistent Grades: Loaded grade for course id: {0}, version: {1}, subtree edited on: {2},"
                 u" grade: {3}, user: {4}".format(
                     course.id, getattr(course, 'course_version',
                                        None), course.subtree_edited_on,
                     subsection_grade, self.student.id))
             return subsection_grade
         except PersistentSubsectionGrade.DoesNotExist:
             log.warning(
                 u"Persistent Grades: Could not find grade for course id: {0}, version: {1}, subtree edited"
                 u" on: {2}, subsection: {3}, user: {4}".format(
                     course.id, getattr(course, 'course_version',
                                        None), course.subtree_edited_on,
                     subsection.location, self.student.id))
             return None
예제 #8
0
    def create(self, subsection, block_structure=None, read_only=False):
        """
        Returns the SubsectionGrade object for the student and subsection.

        If block_structure is provided, uses it for finding and computing
        the grade instead of the course_structure passed in earlier.

        If read_only is True, doesn't save any updates to the grades.
        """
        self._log_event(
            log.info, u"create, read_only: {0}, subsection: {1}".format(read_only, subsection.location)
        )

        block_structure = self._get_block_structure(block_structure)
        subsection_grade = self._get_saved_grade(subsection, block_structure)
        if not subsection_grade:
            subsection_grade = SubsectionGrade(subsection, self.course)
            subsection_grade.init_from_structure(
                self.student, block_structure, self._submissions_scores, self._csm_scores,
            )
            if PersistentGradesEnabledFlag.feature_enabled(self.course.id):
                if read_only:
                    self._unsaved_subsection_grades.append(subsection_grade)
                else:
                    with persistence_safe_fallback():
                        grade_model = subsection_grade.create_model(self.student)
                        self._update_saved_subsection_grade(subsection.location, grade_model)
        return subsection_grade
예제 #9
0
    def update(self, subsection, only_if_higher=None):
        """
        Updates the SubsectionGrade object for the student and subsection.
        """
        # Save ourselves the extra queries if the course does not persist
        # subsection grades.
        if not PersistentGradesEnabledFlag.feature_enabled(self.course.id):
            return

        self._log_event(log.warning, u"update, subsection: {}".format(subsection.location), subsection)

        calculated_grade = SubsectionGrade(subsection).init_from_structure(
            self.student, self.course_structure, self._submissions_scores, self._csm_scores,
        )

        if only_if_higher:
            try:
                grade_model = PersistentSubsectionGrade.read_grade(self.student.id, subsection.location)
            except PersistentSubsectionGrade.DoesNotExist:
                pass
            else:
                orig_subsection_grade = SubsectionGrade(subsection).init_from_model(
                    self.student, grade_model, self.course_structure, self._submissions_scores, self._csm_scores,
                )
                if not is_score_higher(
                        orig_subsection_grade.graded_total.earned,
                        orig_subsection_grade.graded_total.possible,
                        calculated_grade.graded_total.earned,
                        calculated_grade.graded_total.possible,
                ):
                    return orig_subsection_grade

        grade_model = calculated_grade.update_or_create_model(self.student)
        self._update_saved_subsection_grade(subsection.location, grade_model)
        return calculated_grade
예제 #10
0
    def create(self, subsection, block_structure=None, read_only=False):
        """
        Returns the SubsectionGrade object for the student and subsection.

        If block_structure is provided, uses it for finding and computing
        the grade instead of the course_structure passed in earlier.

        If read_only is True, doesn't save any updates to the grades.
        """
        self._log_event(
            log.warning, u"create, read_only: {0}, subsection: {1}".format(read_only, subsection.location)
        )

        block_structure = self._get_block_structure(block_structure)
        subsection_grade = self._get_saved_grade(subsection, block_structure)
        if not subsection_grade:
            subsection_grade = SubsectionGrade(subsection, self.course)
            subsection_grade.init_from_structure(
                self.student, block_structure, self._scores_client, self._submissions_scores
            )
            if PersistentGradesEnabledFlag.feature_enabled(self.course.id):
                if read_only:
                    self._unsaved_subsection_grades.append(subsection_grade)
                else:
                    with persistence_safe_fallback():
                        grade_model = subsection_grade.create_model(self.student)
                        self._update_saved_subsection_grade(subsection.location, grade_model)
        return subsection_grade
예제 #11
0
 def test_persistent_grades_not_enabled_on_course(self, default_store):
     with self.store.default_store(default_store):
         self.set_up_course(enable_persistent_grades=False)
         self.assertFalse(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         with check_mongo_calls(0):
             with self.assertNumQueries(0):
                 self._apply_recalculate_subsection_grade()
예제 #12
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
    """
    try:
        course_id = kwargs.get('course_id', None)
        usage_id = kwargs.get('usage_id', None)
        student = kwargs.get('user', None)

        course_key = CourseLocator.from_string(course_id)
        if not PersistentGradesEnabledFlag.feature_enabled(course_key):
            return

        usage_key = UsageKey.from_string(usage_id).replace(course_key=course_key)

        from lms.djangoapps.grades.new.subsection_grade import SubsectionGradeFactory
        SubsectionGradeFactory(student).update(usage_key, course_key)
    except Exception as ex:  # pylint: disable=broad-except
        log.exception(
            u"Failed to process SCORE_CHANGED signal. "
            "user: %s, course_id: %s, "
            "usage_id: %s. Exception: %s", unicode(student), course_id, usage_id, ex.message
        )
예제 #13
0
 def _get_saved_grade(self, course, course_structure):  # pylint: disable=unused-argument
     """
     Returns the saved grade for the given course and student.
     """
     if PersistentGradesEnabledFlag.feature_enabled(course.id):
         # TODO LATER Retrieve the saved grade for the course, if it exists.
         _pretend_to_save_course_grades()
예제 #14
0
    def create(self, subsection, read_only=False):
        """
        Returns the SubsectionGrade object for the student and subsection.

        If read_only is True, doesn't save any updates to the grades.
        """
        self._log_event(
            log.debug,
            u"create, read_only: {0}, subsection: {1}".format(
                read_only, subsection.location),
            subsection,
        )

        subsection_grade = self._get_bulk_cached_grade(subsection)
        if not subsection_grade:
            subsection_grade = SubsectionGrade(subsection).init_from_structure(
                self.student,
                self.course_structure,
                self._submissions_scores,
                self._csm_scores,
            )
            if PersistentGradesEnabledFlag.feature_enabled(self.course.id):
                if read_only:
                    self._unsaved_subsection_grades.append(subsection_grade)
                else:
                    grade_model = subsection_grade.create_model(self.student)
                    self._update_saved_subsection_grade(
                        subsection.location, grade_model)
        return subsection_grade
예제 #15
0
 def test_persistent_grades_feature_flags(self, global_flag, enabled_for_all_courses, enabled_for_course_1):
     with persistent_grades_feature_flags(
         global_flag=global_flag,
         enabled_for_all_courses=enabled_for_all_courses,
         course_id=self.course_id_1,
         enabled_for_course=enabled_for_course_1
     ):
         self.assertEqual(PersistentGradesEnabledFlag.feature_enabled(), global_flag)
         self.assertEqual(
             PersistentGradesEnabledFlag.feature_enabled(self.course_id_1),
             global_flag and (enabled_for_all_courses or enabled_for_course_1)
         )
         self.assertEqual(
             PersistentGradesEnabledFlag.feature_enabled(self.course_id_2),
             global_flag and enabled_for_all_courses
         )
예제 #16
0
 def test_query_count_does_not_change_with_more_problems(self, default_store, added_queries):
     with self.store.default_store(default_store):
         self.set_up_course()
         self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         ItemFactory.create(parent=self.sequential, category='problem', display_name='problem2')
         ItemFactory.create(parent=self.sequential, category='problem', display_name='problem3')
         with check_mongo_calls(2) and self.assertNumQueries(22 + added_queries):
             self._apply_recalculate_subsection_grade()
예제 #17
0
 def test_missing_kwargs(self, kwarg):
     self.set_up_course()
     self.assertTrue(
         PersistentGradesEnabledFlag.feature_enabled(self.course.id))
     del self.score_changed_kwargs[kwarg]
     with self.assertRaises(KeyError):
         recalculate_subsection_grade_handler(None,
                                              **self.score_changed_kwargs)
예제 #18
0
 def test_subsection_grades_not_enabled_on_course(self, default_store):
     with self.store.default_store(default_store):
         self.set_up_course(enable_subsection_grades=False)
         self.assertFalse(
             PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         with check_mongo_calls(2) and self.assertNumQueries(0):
             recalculate_subsection_grade_handler(
                 None, **self.score_changed_kwargs)
예제 #19
0
    def _get_saved_grade(self, course, course_structure):
        """
        Returns the saved grade for the given course and student.
        """
        if not PersistentGradesEnabledFlag.feature_enabled(course.id):
            return None

        return CourseGrade.load_persisted_grade(self.student, course, course_structure)
예제 #20
0
 def test_subsection_grade_updated(self, default_store):
     with self.store.default_store(default_store):
         self.set_up_course()
         self.assertTrue(
             PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         with check_mongo_calls(2) and self.assertNumQueries(13):
             recalculate_subsection_grade.apply(
                 args=tuple(self.score_changed_kwargs.values()))
예제 #21
0
 def test_subsection_grade_updated(self, default_store, added_queries):
     with self.store.default_store(default_store):
         self.set_up_course()
         self.assertTrue(
             PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         with check_mongo_calls(2) and self.assertNumQueries(22 +
                                                             added_queries):
             self._apply_recalculate_subsection_grade()
예제 #22
0
 def test_query_count_does_not_change_with_more_problems(self, default_store):
     with self.store.default_store(default_store):
         self.set_up_course()
         self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         ItemFactory.create(parent=self.sequential, category='problem', display_name='problem2')
         ItemFactory.create(parent=self.sequential, category='problem', display_name='problem3')
         with check_mongo_calls(2) and self.assertNumQueries(15):
             recalculate_subsection_grade_handler(None, **self.score_changed_kwargs)
예제 #23
0
 def test_single_call_to_create_block_structure(self):
     self.set_up_course()
     self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
     with patch(
         "openedx.core.lib.block_structure.factory.BlockStructureFactory.create_from_cache", return_value=None
     ) as mock_block_structure_create:
         self._apply_recalculate_subsection_grade()
         self.assertEquals(mock_block_structure_create.call_count, 1)
예제 #24
0
 def test_query_count_does_not_change_with_more_problems(self, default_store, added_queries):
     with self.store.default_store(default_store):
         self.set_up_course()
         self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         ItemFactory.create(parent=self.sequential, category="problem", display_name="problem2")
         ItemFactory.create(parent=self.sequential, category="problem", display_name="problem3")
         with check_mongo_calls(2) and self.assertNumQueries(20 + added_queries):
             self._apply_recalculate_subsection_grade()
예제 #25
0
 def test_block_structure_created_only_once(self):
     self.set_up_course()
     self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
     with patch(
         'openedx.core.djangoapps.content.block_structure.factory.BlockStructureFactory.create_from_store',
         side_effect=BlockStructureNotFound(self.course.location),
     ) as mock_block_structure_create:
         self._apply_recalculate_subsection_grade()
         self.assertEquals(mock_block_structure_create.call_count, 1)
예제 #26
0
    def _get_saved_grade(self, course, course_structure):  # pylint: disable=unused-argument
        """
        Returns the saved grade for the given course and student.
        """
        if not PersistentGradesEnabledFlag.feature_enabled(course.id):
            return None

        return CourseGrade.load_persisted_grade(self.student, course,
                                                course_structure)
예제 #27
0
    def get_persisted(self, student, course):
        """
        Returns the saved grade for the given course and student,
        irrespective of whether the saved grade is up-to-date.
        """
        if not PersistentGradesEnabledFlag.feature_enabled(course.id):
            return None

        return CourseGrade.get_persisted_grade(student, course)
예제 #28
0
 def test_block_structure_created_only_once(self):
     self.set_up_course()
     self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
     with patch(
         'openedx.core.lib.block_structure.factory.BlockStructureFactory.create_from_cache',
         return_value=None,
     ) as mock_block_structure_create:
         self._apply_recalculate_subsection_grade()
         self.assertEquals(mock_block_structure_create.call_count, 1)
예제 #29
0
    def _get_saved_grade(self, student, course, course_structure):
        """
        Returns the saved grade for the given course and student.
        """
        if not PersistentGradesEnabledFlag.feature_enabled(course.id):
            return None

        return CourseGrade.load_persisted_grade(student, course,
                                                course_structure)
예제 #30
0
 def test_enable_disable_course_flag(self):
     """
     Ensures that the flag, once enabled for a course, can also be disabled.
     """
     with persistent_grades_feature_flags(global_flag=True,
                                          enabled_for_all_courses=False,
                                          course_id=self.course_id_1,
                                          enabled_for_course=True):
         self.assertTrue(
             PersistentGradesEnabledFlag.feature_enabled(self.course_id_1))
         # Prior to TNL-5698, creating a second object would fail due to db constraints
         with persistent_grades_feature_flags(global_flag=True,
                                              enabled_for_all_courses=False,
                                              course_id=self.course_id_1,
                                              enabled_for_course=False):
             self.assertFalse(
                 PersistentGradesEnabledFlag.feature_enabled(
                     self.course_id_1))
예제 #31
0
 def test_subsection_grades_not_enabled_on_course(self, default_store):
     with self.store.default_store(default_store):
         self.set_up_course(enable_subsection_grades=False)
         self.assertFalse(
             PersistentGradesEnabledFlag.feature_enabled(self.course.id))
         additional_queries = 1 if default_store == ModuleStoreEnum.Type.mongo else 0
         with check_mongo_calls(2) and self.assertNumQueries(
                 12 + additional_queries):
             recalculate_subsection_grade.apply(
                 args=tuple(self.score_changed_kwargs.values()))
예제 #32
0
 def test_single_call_to_create_block_structure(self):
     self.set_up_course()
     self.assertTrue(
         PersistentGradesEnabledFlag.feature_enabled(self.course.id))
     with patch(
             'openedx.core.lib.block_structure.factory.BlockStructureFactory.create_from_cache',
             return_value=None,
     ) as mock_block_structure_create:
         recalculate_subsection_grade_handler(None,
                                              **self.score_changed_kwargs)
         self.assertEquals(mock_block_structure_create.call_count, 1)
 def _save_grade(self, subsection_grade, subsection, course):  # pylint: disable=unused-argument
     """
     Updates the saved grade for the student and subsection.
     """
     if PersistentGradesEnabledFlag.feature_enabled(course.id):
         subsection_grade.save(self.student, subsection, course)
     log.warning(
         u"Persistent Grades: Saved grade for course id: {0}, version: {1}, subtree_edited_on: {2}, grade: "
         u"{3}, user: {4}".format(course.id,
                                  getattr(course, 'course_version',
                                          None), course.subtree_edited_on,
                                  subsection_grade, self.student.id))
예제 #34
0
 def test_enable_disable_globally(self):
     """
     Ensures that the flag, once enabled globally, can also be disabled.
     """
     with persistent_grades_feature_flags(
             global_flag=True,
             enabled_for_all_courses=True,
     ):
         self.assertTrue(PersistentGradesEnabledFlag.feature_enabled())
         self.assertTrue(
             PersistentGradesEnabledFlag.feature_enabled(self.course_id_1))
         with persistent_grades_feature_flags(
                 global_flag=True,
                 enabled_for_all_courses=False,
         ):
             self.assertTrue(PersistentGradesEnabledFlag.feature_enabled())
             self.assertFalse(
                 PersistentGradesEnabledFlag.feature_enabled(
                     self.course_id_1))
             with persistent_grades_feature_flags(global_flag=False, ):
                 self.assertFalse(
                     PersistentGradesEnabledFlag.feature_enabled())
                 self.assertFalse(
                     PersistentGradesEnabledFlag.feature_enabled(
                         self.course_id_1))
    def update(self, usage_key, course_structure, course):
        """
        Updates the SubsectionGrade object for the student and subsection
        identified by the given usage key.
        """
        # save ourselves the extra queries if the course does not use subsection grades
        if not PersistentGradesEnabledFlag.feature_enabled(course.id):
            return

        self._prefetch_scores(course_structure, course)
        subsection = course_structure[usage_key]
        return self._compute_and_save_grade(subsection, course_structure,
                                            course)
예제 #36
0
    def _get_saved_grade(self, subsection, block_structure):  # pylint: disable=unused-argument
        """
        Returns the saved grade for the student and subsection.
        """
        if not PersistentGradesEnabledFlag.feature_enabled(self.course.id):
            return

        saved_subsection_grade = self._get_saved_subsection_grade(subsection.location)
        if saved_subsection_grade:
            subsection_grade = SubsectionGrade(subsection, self.course)
            subsection_grade.init_from_model(
                self.student, saved_subsection_grade, block_structure, self._submissions_scores, self._csm_scores,
            )
            return subsection_grade
예제 #37
0
    def _get_bulk_cached_grade(self, subsection):
        """
        Returns the student's SubsectionGrade for the subsection,
        while caching the results of a bulk retrieval for the
        course, for future access of other subsections.
        Returns None if not found.
        """
        if not PersistentGradesEnabledFlag.feature_enabled(self.course.id):
            return

        saved_subsection_grades = self._get_bulk_cached_subsection_grades()
        subsection_grade = saved_subsection_grades.get(subsection.location)
        if subsection_grade:
            return SubsectionGrade(subsection).init_from_model(
                self.student, subsection_grade, self.course_structure, self._submissions_scores, self._csm_scores,
            )
예제 #38
0
    def update(self, usage_key, course_key):
        """
        Updates the SubsectionGrade object for the student and subsection
        identified by the given usage key.
        """
        from courseware.courses import get_course_by_id  # avoids circular import with courseware.py
        course = get_course_by_id(course_key, depth=0)
        # save ourselves the extra queries if the course does not use subsection grades
        if not PersistentGradesEnabledFlag.feature_enabled(course.id):
            return

        course_structure = get_course_blocks(self.student, usage_key)
        subsection = course_structure[usage_key]
        self._prefetch_scores(course_structure, course)
        return self._compute_and_save_grade(subsection, course_structure,
                                            course)
예제 #39
0
    def test_query_counts_dont_change_with_more_content(self, default_store, num_mongo_calls, num_sql_calls):
        with self.store.default_store(default_store):
            self.set_up_course(create_multiple_subsections=True)
            self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))

            num_problems = 10
            for _ in range(num_problems):
                ItemFactory.create(parent=self.sequential, category='problem')

            num_sequentials = 10
            for _ in range(num_sequentials):
                ItemFactory.create(parent=self.chapter, category='sequential')

            with check_mongo_calls(num_mongo_calls):
                with self.assertNumQueries(num_sql_calls):
                    self._apply_recalculate_subsection_grade()
예제 #40
0
    def update(self, subsection, block_structure=None):
        """
        Updates the SubsectionGrade object for the student and subsection.
        """
        self._log_event(log.warning, u"update, subsection: {}".format(subsection.location))

        block_structure = self._get_block_structure(block_structure)
        subsection_grade = SubsectionGrade(subsection, self.course)
        subsection_grade.init_from_structure(
            self.student, block_structure, self._submissions_scores, self._csm_scores
        )

        if PersistentGradesEnabledFlag.feature_enabled(self.course.id):
            grade_model = subsection_grade.update_or_create_model(self.student)
            self._update_saved_subsection_grade(subsection.location, grade_model)

        return subsection_grade