def setUp(self): 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 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 run(): """ Executed during django startup NOTE: DO **NOT** add additional code to this method or this file! The Platform Team is moving all startup code to more standard locations using Django best practices. """ django_db_models_options.patch() # We currently use 2 template rendering engines, mako and django_templates, # and one of them (django templates), requires the directories be added # before the django.setup(). microsite.enable_microsites_pre_startup(log) django.setup() autostartup() add_mimetypes() # Mako requires the directories to be added after the django setup. microsite.enable_microsites(log) # register any dependency injections that we need to support in edx_proctoring # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit and # lms.djangoapps.grades if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): # Import these here to avoid circular dependencies of the form: # edx-platform app --> DRF --> django translation --> edx-platform app from edx_proctoring.runtime import set_runtime_service from lms.djangoapps.instructor.services import InstructorService from openedx.core.djangoapps.credit.services import CreditService from lms.djangoapps.grades.services import GradesService set_runtime_service('credit', CreditService()) # register InstructorService (for deleting student attempts and user staff access roles) set_runtime_service('instructor', InstructorService()) set_runtime_service('grades', GradesService()) # In order to allow modules to use a handler url, we need to # monkey-patch the x_module library. # TODO: Remove this code when Runtimes are no longer created by modulestores # https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes xmodule.x_module.descriptor_global_handler_url = lms_xblock.runtime.handler_url xmodule.x_module.descriptor_global_local_resource_url = lms_xblock.runtime.local_resource_url # Set the version of docs that help-tokens will go to. settings.HELP_TOKENS_LANGUAGE_CODE = settings.LANGUAGE_CODE settings.HELP_TOKENS_VERSION = doc_version() # validate configurations on startup validate_lms_config(settings)
class GradesServiceTests(ModuleStoreTestCase): """ Tests for the Grades service """ 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 tearDown(self): PersistentSubsectionGradeOverride.objects.all().delete() # clear out all previous overrides self.signal_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() self.flag_patcher.stop() def subsection_grade_to_dict(self, grade): return { 'earned_all': grade.earned_all, 'earned_graded': grade.earned_graded } def subsection_grade_override_to_dict(self, grade): return { 'earned_all_override': grade.earned_all_override, 'earned_graded_override': grade.earned_graded_override } def test_get_subsection_grade(self): self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) def test_get_subsection_grade_override(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, defaults={ 'earned_all_override': 9.0 } ) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) @ddt.data( [{ 'earned_all': 0.0, 'earned_graded': 0.0 }, { 'earned_all': 0.0, 'earned_graded': 0.0 }], [{ 'earned_all': 0.0, 'earned_graded': None }, { 'earned_all': 0.0, 'earned_graded': 5.0 }], [{ 'earned_all': None, 'earned_graded': None }, { 'earned_all': 6.0, 'earned_graded': 5.0 }], [{ 'earned_all': 3.0, 'earned_graded': 2.0 }, { 'earned_all': 3.0, 'earned_graded': 2.0 }], ) @ddt.unpack def test_override_subsection_grade(self, override, expected): self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, earned_all=override['earned_all'], earned_graded=override['earned_graded'] ) override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location ) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, override['earned_all']) self.assertEqual(override_obj.earned_graded_override, override['earned_graded']) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides ) ) @freeze_time('2017-01-01') def test_undo_override_subsection_grade(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) override = self.service.get_subsection_grade_override(self.user.id, self.course.id, self.subsection.location) self.assertIsNone(override) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides ) ) @ddt.data( ['edX/DemoX/Demo_Course', CourseKey.from_string('edX/DemoX/Demo_Course'), CourseKey], ['course-v1:edX+DemoX+Demo_Course', CourseKey.from_string('course-v1:edX+DemoX+Demo_Course'), CourseKey], [CourseKey.from_string('course-v1:edX+DemoX+Demo_Course'), CourseKey.from_string('course-v1:edX+DemoX+Demo_Course'), CourseKey], ['block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow', UsageKey.from_string('block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow'), UsageKey], [UsageKey.from_string('block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow'), UsageKey.from_string('block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow'), UsageKey], ) @ddt.unpack def test_get_key(self, input_key, output_key, key_cls): self.assertEqual(_get_key(input_key, key_cls), output_key) def test_should_override_grade_on_rejected_exam(self): self.assertTrue(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course')) self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(False) } self.assertFalse(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course'))
class GradesServiceTests(ModuleStoreTestCase): """ Tests for the Grades service """ def setUp(self): 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 tearDown(self): PersistentSubsectionGradeOverride.objects.all().delete() # clear out all previous overrides self.signal_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() self.flag_patcher.stop() def subsection_grade_to_dict(self, grade): return { 'earned_all': grade.earned_all, 'earned_graded': grade.earned_graded } def subsection_grade_override_to_dict(self, grade): return { 'earned_all_override': grade.earned_all_override, 'earned_graded_override': grade.earned_graded_override } def test_get_subsection_grade(self): self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) def test_get_subsection_grade_override(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, defaults={ 'earned_all_override': 9.0 } ) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) @ddt.data( [{ 'earned_all': 0.0, 'earned_graded': 0.0 }, { 'earned_all': 0.0, 'earned_graded': 0.0 }], [{ 'earned_all': 0.0, 'earned_graded': None }, { 'earned_all': 0.0, 'earned_graded': 5.0 }], [{ 'earned_all': None, 'earned_graded': None }, { 'earned_all': 6.0, 'earned_graded': 5.0 }], [{ 'earned_all': 3.0, 'earned_graded': 2.0 }, { 'earned_all': 3.0, 'earned_graded': 2.0 }], ) @ddt.unpack def test_override_subsection_grade(self, override, expected): self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, earned_all=override['earned_all'], earned_graded=override['earned_graded'] ) override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location ) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, override['earned_all']) self.assertEqual(override_obj.earned_graded_override, override['earned_graded']) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides ) ) @freeze_time('2017-01-01') def test_undo_override_subsection_grade(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) override = self.service.get_subsection_grade_override(self.user.id, self.course.id, self.subsection.location) self.assertIsNone(override) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides ) ) @freeze_time('2018-01-01') def test_undo_override_subsection_grade_without_grade(self): """ Test exception handling of `undo_override_subsection_grade` when PersistentSubsectionGrade does not exist. """ self.grade.delete() try: self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) except PersistentSubsectionGrade.DoesNotExist: assert False, 'Exception raised unexpectedly' self.assertFalse(self.mock_signal.called) def test_should_override_grade_on_rejected_exam(self): self.assertTrue(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course')) self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(False) } self.assertFalse(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course'))
class GradesServiceTests(ModuleStoreTestCase): """ Tests for the Grades service """ 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.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 tearDown(self): super(GradesServiceTests, self).tearDown() PersistentSubsectionGradeOverride.objects.all().delete() # clear out all previous overrides self.signal_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() self.flag_patcher.stop() def subsection_grade_to_dict(self, grade): return { 'earned_all': grade.earned_all, 'earned_graded': grade.earned_graded } def subsection_grade_override_to_dict(self, grade): return { 'earned_all_override': grade.earned_all_override, 'earned_graded_override': grade.earned_graded_override } def test_get_subsection_grade(self): self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=unicode(self.subsection.location) )), { 'earned_all': 6.0, 'earned_graded': 5.0 }) def test_get_subsection_grade_override(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, defaults={ 'earned_all_override': 9.0 } ) # test with course key parameter as string instead self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=unicode(self.course.id), usage_key_or_id=self.subsection.location )), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) def _verify_override_history(self, override_history, history_action): self.assertIsNone(override_history.user) self.assertIsNotNone(override_history.created) self.assertEqual(override_history.feature, GradeOverrideFeatureEnum.proctoring) self.assertEqual(override_history.action, history_action) @ddt.data( { 'earned_all': 0.0, 'earned_graded': 0.0 }, { 'earned_all': 0.0, 'earned_graded': None }, { 'earned_all': None, 'earned_graded': None }, { 'earned_all': 3.0, 'earned_graded': 2.0 }, ) def test_override_subsection_grade(self, override): self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, earned_all=override['earned_all'], earned_graded=override['earned_graded'] ) override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location ) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, override['earned_all']) self.assertEqual(override_obj.earned_graded_override, override['earned_graded']) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides ) ) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter(override_id=override_obj.id).first() self._verify_override_history(override_history, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE) def test_override_subsection_grade_no_psg(self): """ When there is no PersistentSubsectionGrade associated with the learner and subsection to override, one should be created. """ earned_all_override = 2 earned_graded_override = 0 self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection_without_grade.location, earned_all=earned_all_override, earned_graded=earned_graded_override ) # Assert that a new PersistentSubsectionGrade was created subsection_grade = self.service.get_subsection_grade( self.user.id, self.course.id, self.subsection_without_grade.location ) self.assertIsNotNone(subsection_grade) self.assertEqual(0, subsection_grade.earned_all) self.assertEqual(0, subsection_grade.earned_graded) # Now assert things about the grade override override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection_without_grade.location ) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, earned_all_override) self.assertEqual(override_obj.earned_graded_override, earned_graded_override) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection_without_grade.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides ) ) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter(override_id=override_obj.id).first() self._verify_override_history(override_history, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE) @freeze_time('2017-01-01') def test_undo_override_subsection_grade(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(grade=self.grade) override_id = override.id self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) override = self.service.get_subsection_grade_override(self.user.id, self.course.id, self.subsection.location) self.assertIsNone(override) self.assertEqual( self.mock_signal.call_args, call( sender=None, user_id=self.user.id, course_id=unicode(self.course.id), usage_id=unicode(self.subsection.location), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides ) ) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter(override_id=override_id).first() self._verify_override_history(override_history, PersistentSubsectionGradeOverrideHistory.DELETE) @freeze_time('2018-01-01') def test_undo_override_subsection_grade_without_grade(self): """ Test exception handling of `undo_override_subsection_grade` when PersistentSubsectionGrade does not exist. """ self.grade.delete() try: self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) except PersistentSubsectionGrade.DoesNotExist: assert False, 'Exception raised unexpectedly' self.assertFalse(self.mock_signal.called) def test_should_override_grade_on_rejected_exam(self): self.assertTrue(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course')) self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(False) } self.assertFalse(self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course'))
class GradesServiceTests(ModuleStoreTestCase): """ Tests for the Grades service """ 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 tearDown(self): super(GradesServiceTests, self).tearDown() PersistentSubsectionGradeOverride.objects.all().delete( ) # clear out all previous overrides self.signal_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() self.flag_patcher.stop() def subsection_grade_to_dict(self, grade): return { 'earned_all': grade.earned_all, 'earned_graded': grade.earned_graded } def subsection_grade_override_to_dict(self, grade): return { 'earned_all_override': grade.earned_all_override, 'earned_graded_override': grade.earned_graded_override } def test_get_subsection_grade(self): self.assertDictEqual( self.subsection_grade_to_dict( self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location)), { 'earned_all': 6.0, 'earned_graded': 5.0 }) # test with id strings as parameters instead self.assertDictEqual( self.subsection_grade_to_dict( self.service.get_subsection_grade( user_id=self.user.id, course_key_or_id=six.text_type(self.course.id), usage_key_or_id=six.text_type(self.subsection.location))), { 'earned_all': 6.0, 'earned_graded': 5.0 }) def test_get_subsection_grade_override(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade) self.assertDictEqual( self.subsection_grade_override_to_dict( self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location)), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, defaults={'earned_all_override': 9.0}) # test with course key parameter as string instead self.assertDictEqual( self.subsection_grade_override_to_dict( self.service.get_subsection_grade_override( user_id=self.user.id, course_key_or_id=six.text_type(self.course.id), usage_key_or_id=self.subsection.location)), { 'earned_all_override': override.earned_all_override, 'earned_graded_override': override.earned_graded_override }) def _verify_override_history(self, override_history, history_action): self.assertIsNone(override_history.user) self.assertIsNotNone(override_history.created) self.assertEqual(override_history.feature, GradeOverrideFeatureEnum.proctoring) self.assertEqual(override_history.action, history_action) @ddt.data( { 'earned_all': 0.0, 'earned_graded': 0.0 }, { 'earned_all': 0.0, 'earned_graded': None }, { 'earned_all': None, 'earned_graded': None }, { 'earned_all': 3.0, 'earned_graded': 2.0 }, ) def test_override_subsection_grade(self, override): self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, earned_all=override['earned_all'], earned_graded=override['earned_graded']) override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, override['earned_all']) self.assertEqual(override_obj.earned_graded_override, override['earned_graded']) self.assertEqual( self.mock_signal.call_args, call(sender=None, user_id=self.user.id, course_id=six.text_type(self.course.id), usage_id=six.text_type(self.subsection.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides)) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter( override_id=override_obj.id).first() self._verify_override_history( override_history, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE) def test_override_subsection_grade_no_psg(self): """ When there is no PersistentSubsectionGrade associated with the learner and subsection to override, one should be created. """ earned_all_override = 2 earned_graded_override = 0 self.service.override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection_without_grade.location, earned_all=earned_all_override, earned_graded=earned_graded_override) # Assert that a new PersistentSubsectionGrade was created subsection_grade = self.service.get_subsection_grade( self.user.id, self.course.id, self.subsection_without_grade.location) self.assertIsNotNone(subsection_grade) self.assertEqual(0, subsection_grade.earned_all) self.assertEqual(0, subsection_grade.earned_graded) # Now assert things about the grade override override_obj = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection_without_grade.location) self.assertIsNotNone(override_obj) self.assertEqual(override_obj.earned_all_override, earned_all_override) self.assertEqual(override_obj.earned_graded_override, earned_graded_override) self.assertEqual( self.mock_signal.call_args, call(sender=None, user_id=self.user.id, course_id=six.text_type(self.course.id), usage_id=six.text_type( self.subsection_without_grade.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, score_db_table=ScoreDatabaseTableEnum.overrides)) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter( override_id=override_obj.id).first() self._verify_override_history( override_history, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE) @freeze_time('2017-01-01') def test_undo_override_subsection_grade(self): override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, system=GradeOverrideFeatureEnum.proctoring) override_id = override.id self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) override = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location) self.assertIsNone(override) self.assertEqual( self.mock_signal.call_args, call(sender=None, user_id=self.user.id, course_id=six.text_type(self.course.id), usage_id=six.text_type(self.subsection.location), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), score_deleted=True, score_db_table=ScoreDatabaseTableEnum.overrides)) override_history = PersistentSubsectionGradeOverrideHistory.objects.filter( override_id=override_id).first() self._verify_override_history( override_history, PersistentSubsectionGradeOverrideHistory.DELETE) def test_undo_override_subsection_grade_across_features(self): """ Test that deletion of subsection grade overrides requested by one feature doesn't delete overrides created by another feature. """ override, _ = PersistentSubsectionGradeOverride.objects.update_or_create( grade=self.grade, system=GradeOverrideFeatureEnum.gradebook) self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, feature=GradeOverrideFeatureEnum.proctoring, ) override = self.service.get_subsection_grade_override( self.user.id, self.course.id, self.subsection.location) self.assertIsNotNone(override) @freeze_time('2018-01-01') def test_undo_override_subsection_grade_without_grade(self): """ Test exception handling of `undo_override_subsection_grade` when PersistentSubsectionGrade does not exist. """ self.grade.delete() try: self.service.undo_override_subsection_grade( user_id=self.user.id, course_key_or_id=self.course.id, usage_key_or_id=self.subsection.location, ) except PersistentSubsectionGrade.DoesNotExist: assert False, 'Exception raised unexpectedly' self.assertFalse(self.mock_signal.called) def test_should_override_grade_on_rejected_exam(self): self.assertTrue( self.service.should_override_grade_on_rejected_exam( 'course-v1:edX+DemoX+Demo_Course')) self.mock_waffle_flags.return_value = { REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(False) } self.assertFalse( self.service.should_override_grade_on_rejected_exam( 'course-v1:edX+DemoX+Demo_Course'))
def run(): """ Executed during django startup """ django_db_models_options.patch() # To override the settings before executing the autostartup() for python-social-auth if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False): enable_third_party_auth() # Comprehensive theming needs to be set up before django startup, # because modifying django template paths after startup has no effect. if is_comprehensive_theming_enabled(): enable_theming() # We currently use 2 template rendering engines, mako and django_templates, # and one of them (django templates), requires the directories be added # before the django.setup(). microsite.enable_microsites_pre_startup(log) django.setup() autostartup() add_mimetypes() # Mako requires the directories to be added after the django setup. microsite.enable_microsites(log) # Initialize Segment analytics module by setting the write_key. if settings.LMS_SEGMENT_KEY: analytics.write_key = settings.LMS_SEGMENT_KEY # register any dependency injections that we need to support in edx_proctoring # right now edx_proctoring is dependent on the openedx.core.djangoapps.credit and # lms.djangoapps.grades if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): # Import these here to avoid circular dependencies of the form: # edx-platform app --> DRF --> django translation --> edx-platform app from edx_proctoring.runtime import set_runtime_service from lms.djangoapps.instructor.services import InstructorService from openedx.core.djangoapps.credit.services import CreditService from lms.djangoapps.grades.services import GradesService set_runtime_service('credit', CreditService()) # register InstructorService (for deleting student attempts and user staff access roles) set_runtime_service('instructor', InstructorService()) set_runtime_service('grades', GradesService()) # In order to allow modules to use a handler url, we need to # monkey-patch the x_module library. # TODO: Remove this code when Runtimes are no longer created by modulestores # https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes xmodule.x_module.descriptor_global_handler_url = lms_xblock.runtime.handler_url xmodule.x_module.descriptor_global_local_resource_url = lms_xblock.runtime.local_resource_url # Set the version of docs that help-tokens will go to. settings.HELP_TOKENS_LANGUAGE_CODE = settings.LANGUAGE_CODE settings.HELP_TOKENS_VERSION = doc_version() # validate configurations on startup validate_lms_config(settings)