Commit f92c96ef by Tyler Hallada

Add course flag and service for grade override

parent c51c47f5
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
This module contains various configuration settings via This module contains various configuration settings via
waffle switches for the Grades app. 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 # Namespace
WAFFLE_NAMESPACE = u'grades' WAFFLE_NAMESPACE = u'grades'
...@@ -12,9 +12,27 @@ WRITE_ONLY_IF_ENGAGED = u'write_only_if_engaged' ...@@ -12,9 +12,27 @@ WRITE_ONLY_IF_ENGAGED = u'write_only_if_engaged'
ASSUME_ZERO_GRADE_IF_ABSENT = u'assume_zero_grade_if_absent' ASSUME_ZERO_GRADE_IF_ABSENT = u'assume_zero_grade_if_absent'
ESTIMATE_FIRST_ATTEMPTED = u'estimate_first_attempted' ESTIMATE_FIRST_ATTEMPTED = u'estimate_first_attempted'
# Course Flags
REJECTED_EXAM_OVERRIDES_GRADE = u'rejected_exam_overrides_grade'
def waffle(): def waffle():
""" """
Returns the namespaced, cached, audited Waffle class for Grades. Returns the namespaced, cached, audited Waffle class for Grades.
""" """
return WaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'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
)
}
...@@ -6,6 +6,7 @@ from opaque_keys.edx.keys import CourseKey, UsageKey ...@@ -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 track.event_transaction_utils import create_new_event_transaction_id, set_event_transaction_type
from util.date_utils import to_timestamp from util.date_utils import to_timestamp
from .config.waffle import waffle_flags, REJECTED_EXAM_OVERRIDES_GRADE
from .constants import ScoreDatabaseTableEnum from .constants import ScoreDatabaseTableEnum
from .models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride from .models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride
...@@ -141,3 +142,7 @@ class GradesService(object): ...@@ -141,3 +142,7 @@ class GradesService(object):
score_db_table=ScoreDatabaseTableEnum.overrides 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)
...@@ -12,9 +12,18 @@ from util.date_utils import to_timestamp ...@@ -12,9 +12,18 @@ from util.date_utils import to_timestamp
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from ..config.waffle import REJECTED_EXAM_OVERRIDES_GRADE
from ..constants import ScoreDatabaseTableEnum from ..constants import ScoreDatabaseTableEnum
class MockWaffleFlag():
def __init__(self, state):
self.state = state
def is_enabled(self, course_key):
return self.state
@ddt.ddt @ddt.ddt
class GradesServiceTests(ModuleStoreTestCase): class GradesServiceTests(ModuleStoreTestCase):
""" """
...@@ -44,11 +53,17 @@ class GradesServiceTests(ModuleStoreTestCase): ...@@ -44,11 +53,17 @@ class GradesServiceTests(ModuleStoreTestCase):
self.mock_create_id.return_value = 1 self.mock_create_id.return_value = 1
self.type_patcher = patch('lms.djangoapps.grades.services.set_event_transaction_type') self.type_patcher = patch('lms.djangoapps.grades.services.set_event_transaction_type')
self.mock_set_type = self.type_patcher.start() 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): def tearDown(self):
self.recalc_patcher.stop() self.recalc_patcher.stop()
self.id_patcher.stop() self.id_patcher.stop()
self.type_patcher.stop() self.type_patcher.stop()
self.flag_patcher.stop()
def subsection_grade_to_dict(self, grade): def subsection_grade_to_dict(self, grade):
return { return {
...@@ -218,3 +233,10 @@ class GradesServiceTests(ModuleStoreTestCase): ...@@ -218,3 +233,10 @@ class GradesServiceTests(ModuleStoreTestCase):
@ddt.unpack @ddt.unpack
def test_get_key(self, input_key, output_key, key_cls): def test_get_key(self, input_key, output_key, key_cls):
self.assertEqual(_get_key(input_key, key_cls), output_key) 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'))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment