def test_grade_override(self): grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) override = PersistentSubsectionGradeOverride(grade=grade, earned_all_override=0.0, earned_graded_override=0.0) override.save() grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertEqual(grade.earned_all, 0.0) self.assertEqual(grade.earned_graded, 0.0)
def test_grade_override(self): """ Creating a subsection grade override should NOT change the score values of the related PersistentSubsectionGrade. """ grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) override = PersistentSubsectionGradeOverride.update_or_create_override( requesting_user=self.user, subsection_grade_model=grade, earned_all_override=0.0, earned_graded_override=0.0, feature=PersistentSubsectionGradeOverrideHistory.GRADEBOOK, ) grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertEqual(self.params['earned_all'], grade.earned_all) self.assertEqual(self.params['earned_graded'], grade.earned_graded) # Any score values that aren't specified should use the values from grade as defaults self.assertEqual(0, override.earned_all_override) self.assertEqual(0, override.earned_graded_override) self.assertEqual(grade.possible_all, override.possible_all_override) self.assertEqual(grade.possible_graded, override.possible_graded_override) # An override history record should be created self.assertEqual(1, PersistentSubsectionGradeOverrideHistory.objects.filter(override_id=override.id).count())
def update_or_create_model(self, student): """ Saves or updates the subsection grade in a persisted model. """ if self._should_persist_per_attempted: self._log_event(log.debug, u"update_or_create_model", student) return PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student))
def test_update_or_create_attempted(self, is_active, expected_first_attempted): with freeze_time(now()): if expected_first_attempted is None: expected_first_attempted = now() with waffle.waffle().override(waffle.ESTIMATE_FIRST_ATTEMPTED, active=is_active): grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertEqual(grade.first_attempted, expected_first_attempted)
def setUp(self, **kwargs): super(GradesServiceTests, self).setUp() self.service = GradesService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') self.subsection = ItemFactory.create(parent=self.course, category="subsection", display_name="Subsection") self.user = UserFactory() self.grade = PersistentSubsectionGrade.update_or_create_grade( user_id=self.user.id, course_id=self.course.id, usage_key=self.subsection.location, first_attempted=None, visible_blocks=[], earned_all=6.0, possible_all=6.0, earned_graded=5.0, possible_graded=5.0 ) self.signal_patcher = patch('lms.djangoapps.grades.signals.signals.SUBSECTION_OVERRIDE_CHANGED.send') self.mock_signal = self.signal_patcher.start() self.id_patcher = patch('lms.djangoapps.grades.services.create_new_event_transaction_id') self.mock_create_id = self.id_patcher.start() self.mock_create_id.return_value = 1 self.type_patcher = patch('lms.djangoapps.grades.services.set_event_transaction_type') self.mock_set_type = self.type_patcher.start() self.flag_patcher = patch('lms.djangoapps.grades.services.waffle_flags') self.mock_waffle_flags = self.flag_patcher.start() self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(True) }
def test_unattempted(self): self.params['first_attempted'] = None self.params['earned_all'] = 0.0 self.params['earned_graded'] = 0.0 grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertIsNone(grade.first_attempted) self.assertEqual(grade.earned_all, 0.0) self.assertEqual(grade.earned_graded, 0.0)
def test_update_or_create_grade(self, already_created): created_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) if already_created else None self.params["earned_all"] = 7 updated_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertEqual(updated_grade.earned_all, 7) if already_created: self.assertEqual(created_grade.id, updated_grade.id) self.assertEqual(created_grade.earned_all, 6) with self.assertNumQueries(1): read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) self.assertEqual(updated_grade, read_grade) self.assertEqual(read_grade.visible_blocks.blocks, self.block_records)
def test_update_or_create_grade(self, already_created): created_grade = PersistentSubsectionGrade.create_grade(**self.params) if already_created else None self.params["earned_all"] = 7 updated_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertEqual(updated_grade.earned_all, 7) if already_created: self.assertEqual(created_grade.id, updated_grade.id) self.assertEqual(created_grade.earned_all, 6)
def test_update_or_create_grade(self, already_created): created_grade = PersistentSubsectionGrade.create_grade(**self.params) if already_created else None self.params["earned_all"] = 7 updated_grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertEqual(updated_grade.earned_all, 7) if already_created: self.assertEqual(created_grade.id, updated_grade.id) self.assertEqual(created_grade.earned_all, 6)
def test_update_or_create_grade(self, already_created): created_grade = PersistentSubsectionGrade.update_or_create_grade( **self.params) if already_created else None self.params["earned_all"] = 7 updated_grade = PersistentSubsectionGrade.update_or_create_grade( **self.params) assert updated_grade.earned_all == 7 if already_created: assert created_grade.id == updated_grade.id assert created_grade.earned_all == 6 with self.assertNumQueries(1): read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) assert updated_grade == read_grade assert read_grade.visible_blocks.blocks == self.block_records
def _update_or_create_grades(self, courses_keys=None): """ Creates grades for all courses and subsections. """ if courses_keys is None: courses_keys = self.course_keys course_grade_params = { "course_version": "JoeMcEwing", "course_edited_timestamp": datetime( year=2016, month=8, day=1, hour=18, minute=53, second=24, microsecond=354741, ), "percent_grade": 77.7, "letter_grade": "Great job", "passed": True, } subsection_grade_params = { "course_version": "deadbeef", "subtree_edited_timestamp": "2016-08-01 18:53:24.354741", "earned_all": 6.0, "possible_all": 12.0, "earned_graded": 6.0, "possible_graded": 8.0, "visible_blocks": MagicMock(), "attempted": True, } for course_key in courses_keys: for user_id in self.user_ids: course_grade_params['user_id'] = user_id course_grade_params['course_id'] = course_key PersistentCourseGrade.update_or_create_course_grade(**course_grade_params) for subsection_key in self.subsection_keys_by_course[course_key]: subsection_grade_params['user_id'] = user_id subsection_grade_params['usage_key'] = subsection_key PersistentSubsectionGrade.update_or_create_grade(**subsection_grade_params)
def _update_or_create_grades(self, courses_keys=None): """ Creates grades for all courses and subsections. """ if courses_keys is None: courses_keys = self.course_keys course_grade_params = { "course_version": "JoeMcEwing", "course_edited_timestamp": datetime( year=2016, month=8, day=1, hour=18, minute=53, second=24, microsecond=354741, ), "percent_grade": 77.7, "letter_grade": "Great job", "passed": True, } subsection_grade_params = { "course_version": "deadbeef", "subtree_edited_timestamp": "2016-08-01 18:53:24.354741", "earned_all": 6.0, "possible_all": 12.0, "earned_graded": 6.0, "possible_graded": 8.0, "visible_blocks": MagicMock(), "first_attempted": datetime.now(), } for course_key in courses_keys: for user_id in self.user_ids: course_grade_params['user_id'] = user_id course_grade_params['course_id'] = course_key PersistentCourseGrade.update_or_create(**course_grade_params) for subsection_key in self.subsection_keys_by_course[course_key]: subsection_grade_params['user_id'] = user_id subsection_grade_params['usage_key'] = subsection_key PersistentSubsectionGrade.update_or_create_grade(**subsection_grade_params)
def update_or_create_model(self, student, score_deleted=False, force_update_subsections=False): """ Saves or updates the subsection grade in a persisted model. """ if self._should_persist_per_attempted(score_deleted, force_update_subsections): return PersistentSubsectionGrade.update_or_create_grade( **self._persisted_model_params(student))
def test_update_or_create_attempted(self, is_active, expected_first_attempted): with freeze_time(now()): if expected_first_attempted is None: expected_first_attempted = now() with waffle.waffle().override(waffle.ESTIMATE_FIRST_ATTEMPTED, active=is_active): grade = PersistentSubsectionGrade.update_or_create_grade( **self.params) self.assertEqual(grade.first_attempted, expected_first_attempted)
def test_update_or_create_grade(self, already_created): created_grade = PersistentSubsectionGrade.update_or_create_grade( **self.params) if already_created else None self.params["earned_all"] = 7 updated_grade = PersistentSubsectionGrade.update_or_create_grade( **self.params) self.assertEqual(updated_grade.earned_all, 7) if already_created: self.assertEqual(created_grade.id, updated_grade.id) self.assertEqual(created_grade.earned_all, 6) with self.assertNumQueries(1): read_grade = PersistentSubsectionGrade.read_grade( user_id=self.params["user_id"], usage_key=self.params["usage_key"], ) self.assertEqual(updated_grade, read_grade) self.assertEqual(read_grade.visible_blocks.blocks, self.block_records)
def setUp(self): super(OverrideSubsectionGradeTests, self).setUp() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course', run='Spring2019') self.subsection = ItemFactory.create(parent=self.course, category="subsection", display_name="Subsection") self.grade = PersistentSubsectionGrade.update_or_create_grade( user_id=self.user.id, course_id=self.course.id, usage_key=self.subsection.location, first_attempted=None, visible_blocks=[], earned_all=6.0, possible_all=6.0, earned_graded=5.0, possible_graded=5.0 )
def test_grade_override(self): """ Creating a subsection grade override should NOT change the score values of the related PersistentSubsectionGrade. """ grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) override = PersistentSubsectionGradeOverride.update_or_create_override( requesting_user=self.user, subsection_grade_model=grade, earned_all_override=0.0, earned_graded_override=0.0, feature=GradeOverrideFeatureEnum.gradebook, ) grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertEqual(self.params['earned_all'], grade.earned_all) self.assertEqual(self.params['earned_graded'], grade.earned_graded) # Any score values that aren't specified should use the values from grade as defaults self.assertEqual(0, override.earned_all_override) self.assertEqual(0, override.earned_graded_override) self.assertEqual(grade.possible_all, override.possible_all_override) self.assertEqual(grade.possible_graded, override.possible_graded_override)
def update_or_create_model(self, student, score_deleted=False, force_update_subsections=False): """ Saves or updates the subsection grade in a persisted model. """ if self._should_persist_per_attempted(score_deleted, force_update_subsections): model = PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student)) if hasattr(model, 'override'): # When we're doing an update operation, the PersistentSubsectionGrade model # will be updated based on the problem_scores, but if a grade override # exists that's related to the updated persistent grade, we need to update # the aggregated scores for this object to reflect the override. self.all_total = self._aggregated_score_from_model(model, is_graded=False) self.graded_total = self._aggregated_score_from_model(model, is_graded=True) return model
def test_override_is_visible(self): with persistent_grades_feature_flags(global_flag=True): chapter = ItemFactory(parent=self.course, category='chapter') subsection = ItemFactory.create(parent=chapter, category="sequential", display_name="Subsection") CourseEnrollment.enroll(self.user, self.course.id) params = { "user_id": self.user.id, "usage_key": subsection.location, "course_version": self.course.course_version, "subtree_edited_timestamp": "2016-08-01 18:53:24.354741Z", "earned_all": 6.0, "possible_all": 12.0, "earned_graded": 6.0, "possible_graded": 8.0, "visible_blocks": [], "first_attempted": datetime.now(), } created_grade = PersistentSubsectionGrade.update_or_create_grade( **params) proctoring_failure_comment = "Failed Test Proctoring" PersistentSubsectionGradeOverride.update_or_create_override( requesting_user=self.staff_user, subsection_grade_model=created_grade, earned_all_override=0.0, earned_graded_override=0.0, system=GradeOverrideFeatureEnum.proctoring, feature=GradeOverrideFeatureEnum.proctoring, comment=proctoring_failure_comment) response = self.client.get(self.url) assert response.status_code == 200 sections = response.data['section_scores'] overridden_subsection = sections[1]['subsections'][0] override_entry = overridden_subsection["override"] assert override_entry[ 'system'] == GradeOverrideFeatureEnum.proctoring assert override_entry['reason'] == proctoring_failure_comment
def setUp(self): super(GradesServiceTests, self).setUp() self.service = GradesService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course', run='Spring2019') self.subsection = ItemFactory.create(parent=self.course, category="subsection", display_name="Subsection") self.subsection_without_grade = ItemFactory.create( parent=self.course, category="subsection", display_name="Subsection without grade") self.user = UserFactory() self.grade = PersistentSubsectionGrade.update_or_create_grade( user_id=self.user.id, course_id=self.course.id, usage_key=self.subsection.location, first_attempted=None, visible_blocks=[], earned_all=6.0, possible_all=6.0, earned_graded=5.0, possible_graded=5.0) self.signal_patcher = patch( 'lms.djangoapps.grades.signals.signals.SUBSECTION_OVERRIDE_CHANGED.send' ) self.mock_signal = self.signal_patcher.start() self.id_patcher = patch( 'lms.djangoapps.grades.api.create_new_event_transaction_id') self.mock_create_id = self.id_patcher.start() self.mock_create_id.return_value = 1 self.type_patcher = patch( 'lms.djangoapps.grades.api.set_event_transaction_type') self.mock_set_type = self.type_patcher.start() self.flag_patcher = patch( 'lms.djangoapps.grades.config.waffle.waffle_flags') self.mock_waffle_flags = self.flag_patcher.start() self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(True) }
def update_or_create_model(self, student, score_deleted=False, force_update_subsections=False): """ Saves or updates the subsection grade in a persisted model. """ if self._should_persist_per_attempted(score_deleted, force_update_subsections): # TODO: Remove as part of EDUCATOR-4602. if str(self.location.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': log.info('Updating PersistentSubsectionGrade for student ***{}*** in' ' subsection ***{}*** with params ***{}***.' .format(student.id, self.location, self._persisted_model_params(student))) model = PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student)) if hasattr(model, 'override'): # When we're doing an update operation, the PersistentSubsectionGrade model # will be updated based on the problem_scores, but if a grade override # exists that's related to the updated persistent grade, we need to update # the aggregated scores for this object to reflect the override. self.all_total = self._aggregated_score_from_model(model, is_graded=False) self.graded_total = self._aggregated_score_from_model(model, is_graded=True) return model
def test_update_or_create_inconsistent_unattempted(self): self.params['attempted'] = False self.params['earned_all'] = 1.0 self.params['earned_graded'] = 1.0 with self.assertRaises(ValidationError): PersistentSubsectionGrade.update_or_create_grade(**self.params)
def test_first_attempted_not_changed_on_update(self): PersistentSubsectionGrade.create_grade(**self.params) moment = now() grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertLess(grade.first_attempted, moment)
def update_or_create_model(self, student, score_deleted=False): """ Saves or updates the subsection grade in a persisted model. """ if self._should_persist_per_attempted(score_deleted): return PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student))
def update_or_create_model(self, student): """ Saves or updates the subsection grade in a persisted model. """ self._log_event(log.info, u"update_or_create_model", student) return PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student))
def test_update_or_create_attempted(self): grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertIsInstance(grade.first_attempted, datetime)
def test_update_or_create_attempted(self): grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertIsInstance(grade.first_attempted, datetime)
def test_unattempted_save_does_not_remove_attempt(self): PersistentSubsectionGrade.update_or_create_grade(**self.params) self.params['first_attempted'] = None grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertIsInstance(grade.first_attempted, datetime) self.assertEqual(grade.earned_all, 6.0)
def test_optional_fields(self, field): del self.params[field] PersistentSubsectionGrade.update_or_create_grade(**self.params)
def test_optional_fields(self, field): del self.params[field] PersistentSubsectionGrade.update_or_create_grade(**self.params)
def test_update_or_create_inconsistent_unattempted(self): self.params['attempted'] = False self.params['earned_all'] = 1.0 self.params['earned_graded'] = 1.0 with self.assertRaises(ValidationError): PersistentSubsectionGrade.update_or_create_grade(**self.params)
def update_or_create_model(self, student): """ Saves or updates the subsection grade in a persisted model. """ self._log_event(log.info, u"update_or_create_model", student) return PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student))
def test_non_optional_fields(self, field, error): del self.params[field] with self.assertRaises(error): PersistentSubsectionGrade.update_or_create_grade(**self.params)
def test_unattempted_save_does_not_remove_attempt(self): PersistentSubsectionGrade.create_grade(**self.params) self.params['first_attempted'] = None grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertIsInstance(grade.first_attempted, datetime) self.assertEqual(grade.earned_all, 6.0)
def test_first_attempted_not_changed_on_update(self): PersistentSubsectionGrade.update_or_create_grade(**self.params) moment = now() grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self.assertLess(grade.first_attempted, moment)
def test_update_or_create_event(self): with patch('lms.djangoapps.grades.models.tracker') as tracker_mock: grade = PersistentSubsectionGrade.update_or_create_grade(**self.params) self._assert_tracker_emitted_event(tracker_mock, grade)
def test_create_event(self): with patch('lms.djangoapps.grades.events.tracker') as tracker_mock: grade = PersistentSubsectionGrade.update_or_create_grade( **self.params) self._assert_tracker_emitted_event(tracker_mock, grade)
def test_non_optional_fields(self, field, error): del self.params[field] with self.assertRaises(error): PersistentSubsectionGrade.update_or_create_grade(**self.params)