Commit a9c1e1cf by Tyler Hallada

Squashed commit of the following:

commit 12880fa21eac0c2e69b2d832d42829f00484561d
Author: Tyler Hallada <thallada@edx.org>
Date:   Fri Aug 11 11:17:22 2017 -0400

    Tweak exam override messaging in progress.html

commit 6f90352dc27b3a199eea7efaf84b72df50f570a9
Merge: e0e9f83789 7c535f5f
Author: Tyler Hallada <thallada@edx.org>
Date:   Thu Aug 10 17:38:13 2017 -0400

    Merge remote-tracking branch 'origin/master' into EDUCATOR-926

commit e0e9f83789efa837577cedbcd5807c6fdba249e4
Author: Tyler Hallada <thallada@edx.org>
Date:   Thu Aug 10 17:35:26 2017 -0400

    Revert "Comment out actual override and log instead"

    This reverts commit 4953cf30.

commit 27d6b537c8915ecd1af8af51c8f9509db6ea0a26
Author: Tyler Hallada <thallada@edx.org>
Date:   Thu Aug 10 17:34:56 2017 -0400

    Revert "Fix tests"

    This reverts commit 1b2fec21.

commit cabddd504ddcb4f92aeed8b5fec4bff0d64a8de3
Author: Tyler Hallada <thallada@edx.org>
Date:   Thu Aug 10 17:34:36 2017 -0400

    Revert "Remove override behavior and log instead"

    This reverts commit 1ab18803.

commit dc9407f4606804e00d5aee4e80d309c7d7f9c437
Author: Tyler Hallada <thallada@edx.org>
Date:   Thu Aug 10 17:34:15 2017 -0400

    Revert "Fix python tests and use proctoring 1.0.0"

    This reverts commit 842ce836.
parent 7c535f5f
......@@ -412,7 +412,22 @@ class PersistentSubsectionGrade(DeleteGradesMixin, TimeStampedModel):
usage_key = params.pop('usage_key')
# apply grade override if one exists before saving model
# EDUCTATOR-1127: remove override until this behavior is verified in production
try:
override = PersistentSubsectionGradeOverride.objects.get(
grade__user_id=user_id,
grade__course_id=usage_key.course_key,
grade__usage_key=usage_key,
)
if override.earned_all_override is not None:
params['earned_all'] = override.earned_all_override
if override.possible_all_override is not None:
params['possible_all'] = override.possible_all_override
if override.earned_graded_override is not None:
params['earned_graded'] = override.earned_graded_override
if override.possible_graded_override is not None:
params['possible_graded'] = override.possible_graded_override
except PersistentSubsectionGradeOverride.DoesNotExist:
pass
grade, _ = cls.objects.update_or_create(
user_id=user_id,
......
from datetime import datetime
import logging
import pytz
from opaque_keys.edx.keys import CourseKey, UsageKey
......@@ -12,8 +11,6 @@ from .constants import ScoreDatabaseTableEnum
from .models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride
from .signals.signals import SUBSECTION_OVERRIDE_CHANGED
log = logging.getLogger(__name__)
def _get_key(key_or_id, key_cls):
"""
......@@ -73,21 +70,40 @@ class GradesService(object):
Fires off a recalculate_subsection_grade async task to update the PersistentSubsectionGrade table. Will not
override earned_all or earned_graded value if they are None. Both default to None.
"""
# prevent circular imports:
from .signals.handlers import SUBSECTION_OVERRIDE_EVENT_TYPE
course_key = _get_key(course_key_or_id, CourseKey)
usage_key = _get_key(usage_key_or_id, UsageKey)
log.info(
u"EDUCATOR-1127: Subsection grade override for user {user_id} on subsection {usage_key} in course "
u"{course_key} would be created with params: {params}"
.format(
user_id=unicode(user_id),
usage_key=unicode(usage_key),
course_key=unicode(course_key),
params=unicode({
'earned_all': earned_all,
'earned_graded': earned_graded,
})
)
grade = PersistentSubsectionGrade.objects.get(
user_id=user_id,
course_id=course_key,
usage_key=usage_key
)
# Create override that will prevent any future updates to grade
override, _ = PersistentSubsectionGradeOverride.objects.update_or_create(
grade=grade,
earned_all_override=earned_all,
earned_graded_override=earned_graded
)
# Cache a new event id and event type which the signal handler will use to emit a tracking log event.
create_new_event_transaction_id()
set_event_transaction_type(SUBSECTION_OVERRIDE_EVENT_TYPE)
# Signal will trigger subsection recalculation which will call PersistentSubsectionGrade.update_or_create_grade
# which will use the above override to update the grade before writing to the table.
SUBSECTION_OVERRIDE_CHANGED.send(
sender=None,
user_id=user_id,
course_id=unicode(course_key),
usage_id=unicode(usage_key),
only_if_higher=False,
modified=override.modified,
score_deleted=False,
score_db_table=ScoreDatabaseTableEnum.overrides
)
def undo_override_subsection_grade(self, user_id, course_key_or_id, usage_key_or_id):
......@@ -97,17 +113,33 @@ class GradesService(object):
Fires off a recalculate_subsection_grade async task to update the PersistentSubsectionGrade table. If the
override does not exist, no error is raised, it just triggers the recalculation.
"""
# prevent circular imports:
from .signals.handlers import SUBSECTION_OVERRIDE_EVENT_TYPE
course_key = _get_key(course_key_or_id, CourseKey)
usage_key = _get_key(usage_key_or_id, UsageKey)
log.info(
u"EDUCATOR-1127: Subsection grade override for user {user_id} on subsection {usage_key} in course "
u"{course_key} would be deleted"
.format(
user_id=unicode(user_id),
usage_key=unicode(usage_key),
course_key=unicode(course_key)
)
override = self.get_subsection_grade_override(user_id, course_key, usage_key)
# Older rejected exam attempts that transition to verified might not have an override created
if override is not None:
override.delete()
# Cache a new event id and event type which the signal handler will use to emit a tracking log event.
create_new_event_transaction_id()
set_event_transaction_type(SUBSECTION_OVERRIDE_EVENT_TYPE)
# Signal will trigger subsection recalculation which will call PersistentSubsectionGrade.update_or_create_grade
# which will no longer use the above deleted override, and instead return the grade to the original score from
# the actual problem responses before writing to the table.
SUBSECTION_OVERRIDE_CHANGED.send(
sender=None,
user_id=user_id,
course_id=unicode(course_key),
usage_id=unicode(usage_key),
only_if_higher=False,
modified=datetime.now().replace(tzinfo=pytz.UTC), # Not used when score_deleted=True
score_deleted=True,
score_db_table=ScoreDatabaseTableEnum.overrides
)
def should_override_grade_on_rejected_exam(self, course_key_or_id):
......
......@@ -312,9 +312,8 @@ class PersistentSubsectionGradeTest(GradesModelTestCase):
override = PersistentSubsectionGradeOverride(grade=grade, earned_all_override=0.0, earned_graded_override=0.0)
override.save()
grade = PersistentSubsectionGrade.update_or_create_grade(**self.params)
# EDUCATOR-1127 Override is not enabled yet, change to 0.0 when enabled
self.assertEqual(grade.earned_all, 6.0)
self.assertEqual(grade.earned_graded, 6.0)
self.assertEqual(grade.earned_all, 0.0)
self.assertEqual(grade.earned_graded, 0.0)
def _assert_tracker_emitted_event(self, tracker_mock, grade):
"""
......
......@@ -171,10 +171,28 @@ class GradesServiceTests(ModuleStoreTestCase):
self.course.id,
self.subsection.location
)
self.assertIsNone(override_obj)
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,
......@@ -184,6 +202,20 @@ class GradesServiceTests(ModuleStoreTestCase):
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],
......
......@@ -163,10 +163,10 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
self.assertEquals(mock_block_structure_create.call_count, 1)
@ddt.data(
(ModuleStoreEnum.Type.mongo, 1, 29, True),
(ModuleStoreEnum.Type.mongo, 1, 25, False),
(ModuleStoreEnum.Type.split, 3, 29, True),
(ModuleStoreEnum.Type.split, 3, 25, False),
(ModuleStoreEnum.Type.mongo, 1, 30, True),
(ModuleStoreEnum.Type.mongo, 1, 26, False),
(ModuleStoreEnum.Type.split, 3, 30, True),
(ModuleStoreEnum.Type.split, 3, 26, False),
)
@ddt.unpack
def test_query_counts(self, default_store, num_mongo_calls, num_sql_calls, create_multiple_subsections):
......@@ -178,8 +178,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
self._apply_recalculate_subsection_grade()
@ddt.data(
(ModuleStoreEnum.Type.mongo, 1, 29),
(ModuleStoreEnum.Type.split, 3, 29),
(ModuleStoreEnum.Type.mongo, 1, 30),
(ModuleStoreEnum.Type.split, 3, 30),
)
@ddt.unpack
def test_query_counts_dont_change_with_more_content(self, default_store, num_mongo_calls, num_sql_calls):
......@@ -239,8 +239,8 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest
self.assertEqual(len(PersistentSubsectionGrade.bulk_read_grades(self.user.id, self.course.id)), 0)
@ddt.data(
(ModuleStoreEnum.Type.mongo, 1, 26),
(ModuleStoreEnum.Type.split, 3, 26),
(ModuleStoreEnum.Type.mongo, 1, 27),
(ModuleStoreEnum.Type.split, 3, 27),
)
@ddt.unpack
def test_persistent_grades_enabled_on_course(self, default_store, num_mongo_queries, num_sql_queries):
......
......@@ -183,18 +183,15 @@ from django.utils.http import urlquote_plus
<em class="localized-datetime" data-datetime="${section.due}" data-string="${_('due {date}')}" data-timezone="${user_timezone}" data-language="${user_language}"></em>
%endif
</p>
<%doc>
EDUCATOR-1127: Do not display override notice until override is enabled
<p class="override-notice">
%if section.override is not None:
<p class="override-notice">
%if section.format is not None and section.format == "Exam":
${_("Exam grade has been overridden due to a failed proctoring review.")}
%else:
${_("Section grade has been overridden.")}
%endif
</p>
%if section.format is not None and section.format == "Exam":
${_("Suspicious activity detected during proctored exam review. Exam score 0.")}
%else:
${_("Section grade has been overridden.")}
%endif
%endif
</%doc>
</p>
%if len(section.problem_scores.values()) > 0:
%if section.show_grades(staff_access):
<dl class="scores">
......
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