Commit 27229a1f by Nimisha Asthagiri

Optimize grade reports for ZeroCourseGrades.

EDUCATOR-558
parent 0b1d22f0
...@@ -44,6 +44,20 @@ class CourseGradeBase(object): ...@@ -44,6 +44,20 @@ class CourseGradeBase(object):
""" """
return False return False
def subsection_grade(self, subsection_key):
"""
Returns the subsection grade for given subsection usage key.
Raises KeyError if the user doesn't have access to that subsection.
"""
return self._get_subsection_grade(self.course_data.structure[subsection_key])
@abstractmethod
def assignment_average(self, assignment_type):
"""
Returns the average of all assignments of the given assignment type.
"""
raise NotImplementedError
@lazy @lazy
def graded_subsections_by_format(self): def graded_subsections_by_format(self):
""" """
...@@ -207,6 +221,9 @@ class ZeroCourseGrade(CourseGradeBase): ...@@ -207,6 +221,9 @@ class ZeroCourseGrade(CourseGradeBase):
Course Grade class for Zero-value grades when no problems were Course Grade class for Zero-value grades when no problems were
attempted in the course. attempted in the course.
""" """
def assignment_average(self, assignment_type):
return 0.0
def _get_subsection_grade(self, subsection): def _get_subsection_grade(self, subsection):
return ZeroSubsectionGrade(subsection, self.course_data) return ZeroSubsectionGrade(subsection, self.course_data)
...@@ -248,6 +265,9 @@ class CourseGrade(CourseGradeBase): ...@@ -248,6 +265,9 @@ class CourseGrade(CourseGradeBase):
return True return True
return False return False
def assignment_average(self, assignment_type):
return self.grader_result['grade_breakdown'].get(assignment_type, {}).get('percent')
def _get_subsection_grade(self, subsection): def _get_subsection_grade(self, subsection):
if self.force_update_subsections: if self.force_update_subsections:
return self._subsection_grade_factory.update(subsection) return self._subsection_grade_factory.update(subsection)
......
...@@ -53,6 +53,13 @@ class SubsectionGradeBase(object): ...@@ -53,6 +53,13 @@ class SubsectionGradeBase(object):
""" """
return ShowCorrectness.correctness_available(self.show_correctness, self.due, has_staff_access) return ShowCorrectness.correctness_available(self.show_correctness, self.due, has_staff_access)
@property
def attempted_graded(self):
"""
Returns whether the user had attempted a graded problem in this subsection.
"""
raise NotImplementedError
class ZeroSubsectionGrade(SubsectionGradeBase): class ZeroSubsectionGrade(SubsectionGradeBase):
""" """
...@@ -64,6 +71,10 @@ class ZeroSubsectionGrade(SubsectionGradeBase): ...@@ -64,6 +71,10 @@ class ZeroSubsectionGrade(SubsectionGradeBase):
self.course_data = course_data self.course_data = course_data
@property @property
def attempted_graded(self):
return False
@property
def all_total(self): def all_total(self):
return self._aggregate_scores[0] return self._aggregate_scores[0]
...@@ -174,6 +185,10 @@ class SubsectionGrade(SubsectionGradeBase): ...@@ -174,6 +185,10 @@ class SubsectionGrade(SubsectionGradeBase):
self._log_event(log.debug, u"update_or_create_model", student) self._log_event(log.debug, u"update_or_create_model", student)
return PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student)) return PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student))
@property
def attempted_graded(self):
return self.graded_total.first_attempted is not None
def _should_persist_per_attempted(self, score_deleted=False): def _should_persist_per_attempted(self, score_deleted=False):
""" """
Returns whether the SubsectionGrade's model should be Returns whether the SubsectionGrade's model should be
......
...@@ -36,7 +36,7 @@ class TestCourseGradeFactory(GradeTestBase): ...@@ -36,7 +36,7 @@ class TestCourseGradeFactory(GradeTestBase):
def test_course_grade_no_access(self): def test_course_grade_no_access(self):
""" """
Test to ensure a grade can ba calculated for a student in a course, even if they themselves do not have access. Test to ensure a grade can be calculated for a student in a course, even if they themselves do not have access.
""" """
invisible_course = CourseFactory.create(visible_to_staff_only=True) invisible_course = CourseFactory.create(visible_to_staff_only=True)
access = has_access(self.request.user, 'load', invisible_course) access = has_access(self.request.user, 'load', invisible_course)
......
...@@ -306,19 +306,17 @@ class CourseGradeReport(object): ...@@ -306,19 +306,17 @@ class CourseGradeReport(object):
for assignment_type, assignment_info in context.graded_assignments.iteritems(): for assignment_type, assignment_info in context.graded_assignments.iteritems():
for subsection_location in assignment_info['subsection_headers']: for subsection_location in assignment_info['subsection_headers']:
try: try:
subsection_grade = course_grade.graded_subsections_by_format[assignment_type][subsection_location] subsection_grade = course_grade.subsection_grade(subsection_location)
except KeyError: except KeyError:
grade_result = u'Not Available' grade_result = u'Not Available'
else: else:
if subsection_grade.graded_total.first_attempted is not None: if subsection_grade.attempted_graded:
grade_result = subsection_grade.graded_total.earned / subsection_grade.graded_total.possible grade_result = subsection_grade.graded_total.earned / subsection_grade.graded_total.possible
else: else:
grade_result = u'Not Attempted' grade_result = u'Not Attempted'
grade_results.append([grade_result]) grade_results.append([grade_result])
if assignment_info['separate_subsection_avg_headers']: if assignment_info['separate_subsection_avg_headers']:
assignment_average = course_grade.grader_result['grade_breakdown'].get(assignment_type, {}).get( assignment_average = course_grade.assignment_average(assignment_type)
'percent'
)
grade_results.append([assignment_average]) grade_results.append([assignment_average])
return [course_grade.percent] + _flatten(grade_results) return [course_grade.percent] + _flatten(grade_results)
......
...@@ -679,7 +679,7 @@ class TestGradeReportConditionalContent(TestReportMixin, TestConditionalContent, ...@@ -679,7 +679,7 @@ class TestGradeReportConditionalContent(TestReportMixin, TestConditionalContent,
{ {
self.student_b: { self.student_b: {
u'Grade': '0.0', u'Grade': '0.0',
u'Homework': u'Not Available', u'Homework': u'Not Attempted',
} }
}, },
], ],
......
...@@ -1769,6 +1769,7 @@ class TestGradeReport(TestReportMixin, InstructorTaskModuleTestCase): ...@@ -1769,6 +1769,7 @@ class TestGradeReport(TestReportMixin, InstructorTaskModuleTestCase):
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'): with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
result = CourseGradeReport.generate(None, None, self.course.id, None, 'graded') result = CourseGradeReport.generate(None, None, self.course.id, None, 'graded')
self.assertDictContainsSubset( self.assertDictContainsSubset(
{'action_name': 'graded', 'attempted': 1, 'succeeded': 1, 'failed': 0}, {'action_name': 'graded', 'attempted': 1, 'succeeded': 1, 'failed': 0},
result, result,
...@@ -1783,13 +1784,30 @@ class TestGradeReport(TestReportMixin, InstructorTaskModuleTestCase): ...@@ -1783,13 +1784,30 @@ class TestGradeReport(TestReportMixin, InstructorTaskModuleTestCase):
u'Homework 1: Subsection': '0.5', u'Homework 1: Subsection': '0.5',
u'Homework 2: Hidden': u'Not Available', u'Homework 2: Hidden': u'Not Available',
u'Homework 3: Unattempted': u'Not Attempted', u'Homework 3: Unattempted': u'Not Attempted',
u'Homework 4: Empty': u'Not Available', u'Homework 4: Empty': u'Not Attempted',
u'Homework (Avg)': '0.125', u'Homework (Avg)': '0.125',
}, },
], ],
ignore_other_columns=True, ignore_other_columns=True,
) )
def test_fast_generation_zero_grade(self):
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
with patch('lms.djangoapps.grades.course_grade.CourseGradeBase._prep_course_for_grading') as mock_grader:
with patch('lms.djangoapps.grades.subsection_grade.get_score') as mock_get_score:
CourseGradeReport.generate(None, None, self.course.id, None, 'graded')
self.assertFalse(mock_grader.called)
self.assertFalse(mock_get_score.called)
def test_slow_generation_nonzero_grade(self):
self.submit_student_answer(self.student.username, u'Problem1', ['Option 1'])
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
with patch('lms.djangoapps.grades.course_grade.CourseGradeBase._prep_course_for_grading') as mock_grader:
with patch('lms.djangoapps.grades.subsection_grade.get_score') as mock_get_score:
CourseGradeReport.generate(None, None, self.course.id, None, 'graded')
self.assertTrue(mock_grader.called)
self.assertTrue(mock_get_score.called)
@ddt.ddt @ddt.ddt
@patch('lms.djangoapps.instructor_task.tasks_helper.misc.DefaultStorage', new=MockDefaultStorage) @patch('lms.djangoapps.instructor_task.tasks_helper.misc.DefaultStorage', new=MockDefaultStorage)
......
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