From f92c96ef05acad66940e693255d39e5c807d4543 Mon Sep 17 00:00:00 2001 From: Tyler Hallada Date: Wed, 2 Aug 2017 10:22:19 -0400 Subject: [PATCH] Add course flag and service for grade override --- lms/djangoapps/grades/config/waffle.py | 20 +++++++++++++++++- lms/djangoapps/grades/services.py | 5 +++++ lms/djangoapps/grades/tests/test_services.py | 22 ++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/grades/config/waffle.py b/lms/djangoapps/grades/config/waffle.py index c5705ac491..6453a75e97 100644 --- a/lms/djangoapps/grades/config/waffle.py +++ b/lms/djangoapps/grades/config/waffle.py @@ -2,7 +2,7 @@ This module contains various configuration settings via waffle switches for the Grades app. """ -from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace +from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace, WaffleFlagNamespace, CourseWaffleFlag # Namespace WAFFLE_NAMESPACE = u'grades' @@ -12,9 +12,27 @@ WRITE_ONLY_IF_ENGAGED = u'write_only_if_engaged' ASSUME_ZERO_GRADE_IF_ABSENT = u'assume_zero_grade_if_absent' ESTIMATE_FIRST_ATTEMPTED = u'estimate_first_attempted' +# Course Flags +REJECTED_EXAM_OVERRIDES_GRADE = u'rejected_exam_overrides_grade' + def waffle(): """ Returns the namespaced, cached, audited Waffle class for Grades. """ return WaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Grades: ') + + +def waffle_flags(): + """ + Returns the namespaced, cached, audited Waffle flags dictionary for Grades. + """ + namespace = WaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Grades: ') + return { + # By default, enable rejected exam grade overrides. Can be disabled on a course-by-course basis. + REJECTED_EXAM_OVERRIDES_GRADE: CourseWaffleFlag( + namespace, + REJECTED_EXAM_OVERRIDES_GRADE, + flag_undefined_default=True + ) + } diff --git a/lms/djangoapps/grades/services.py b/lms/djangoapps/grades/services.py index 387d86521d..cbc5521ac4 100644 --- a/lms/djangoapps/grades/services.py +++ b/lms/djangoapps/grades/services.py @@ -6,6 +6,7 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from track.event_transaction_utils import create_new_event_transaction_id, set_event_transaction_type from util.date_utils import to_timestamp +from .config.waffle import waffle_flags, REJECTED_EXAM_OVERRIDES_GRADE from .constants import ScoreDatabaseTableEnum from .models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride @@ -141,3 +142,7 @@ class GradesService(object): score_db_table=ScoreDatabaseTableEnum.overrides ) ) + + def should_override_grade_on_rejected_exam(self, course_key): + """Convienence function to return the state of the CourseWaffleFlag REJECTED_EXAM_OVERRIDES_GRADE""" + return waffle_flags()[REJECTED_EXAM_OVERRIDES_GRADE].is_enabled(course_key) diff --git a/lms/djangoapps/grades/tests/test_services.py b/lms/djangoapps/grades/tests/test_services.py index f6e8a05c2c..e41f1f72ce 100644 --- a/lms/djangoapps/grades/tests/test_services.py +++ b/lms/djangoapps/grades/tests/test_services.py @@ -12,9 +12,18 @@ from util.date_utils import to_timestamp from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory +from ..config.waffle import REJECTED_EXAM_OVERRIDES_GRADE from ..constants import ScoreDatabaseTableEnum +class MockWaffleFlag(): + def __init__(self, state): + self.state = state + + def is_enabled(self, course_key): + return self.state + + @ddt.ddt class GradesServiceTests(ModuleStoreTestCase): """ @@ -44,11 +53,17 @@ class GradesServiceTests(ModuleStoreTestCase): 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): self.recalc_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() + self.flag_patcher.stop() def subsection_grade_to_dict(self, grade): return { @@ -218,3 +233,10 @@ class GradesServiceTests(ModuleStoreTestCase): @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'))