Commit 66fdd2fb by Sanford Student

EDUCATOR-915: force subsection grades to update when course grade updates

parent 42fffdfa
...@@ -21,7 +21,7 @@ class CourseGradeBase(object): ...@@ -21,7 +21,7 @@ class CourseGradeBase(object):
""" """
Base class for Course Grades. Base class for Course Grades.
""" """
def __init__(self, user, course_data, percent=0, letter_grade=None, passed=False): def __init__(self, user, course_data, percent=0, letter_grade=None, passed=False, force_update_subsections=False):
self.user = user self.user = user
self.course_data = course_data self.course_data = course_data
...@@ -30,6 +30,7 @@ class CourseGradeBase(object): ...@@ -30,6 +30,7 @@ class CourseGradeBase(object):
# Convert empty strings to None when reading from the table # Convert empty strings to None when reading from the table
self.letter_grade = letter_grade or None self.letter_grade = letter_grade or None
self.force_update_subsections = force_update_subsections
def __unicode__(self): def __unicode__(self):
return u'Course Grade: percent: {}, letter_grade: {}, passed: {}'.format( return u'Course Grade: percent: {}, letter_grade: {}, passed: {}'.format(
...@@ -203,7 +204,9 @@ class CourseGrade(CourseGradeBase): ...@@ -203,7 +204,9 @@ class CourseGrade(CourseGradeBase):
def update(self): def update(self):
""" """
Updates the grade for the course. Updates the grade for the course. Also updates subsection grades
if self.force_update_subsections is true, via the lazy call
to self.grader_result.
""" """
grade_cutoffs = self.course_data.course.grade_cutoffs grade_cutoffs = self.course_data.course.grade_cutoffs
self.percent = self._compute_percent(self.grader_result) self.percent = self._compute_percent(self.grader_result)
...@@ -224,7 +227,10 @@ class CourseGrade(CourseGradeBase): ...@@ -224,7 +227,10 @@ class CourseGrade(CourseGradeBase):
def _get_subsection_grade(self, subsection): def _get_subsection_grade(self, subsection):
# Pass read_only here so the subsection grades can be persisted in bulk at the end. # Pass read_only here so the subsection grades can be persisted in bulk at the end.
return self._subsection_grade_factory.create(subsection, read_only=True) if self.force_update_subsections:
return self._subsection_grade_factory.update(subsection)
else:
return self._subsection_grade_factory.create(subsection, read_only=True)
@staticmethod @staticmethod
def _compute_percent(grader_result): def _compute_percent(grader_result):
......
...@@ -66,7 +66,15 @@ class CourseGradeFactory(object): ...@@ -66,7 +66,15 @@ class CourseGradeFactory(object):
else: else:
return None return None
def update(self, user, course=None, collected_block_structure=None, course_structure=None, course_key=None): def update(
self,
user,
course=None,
collected_block_structure=None,
course_structure=None,
course_key=None,
force_update_subsections=False,
):
""" """
Computes, updates, and returns the CourseGrade for the given Computes, updates, and returns the CourseGrade for the given
user in the course. user in the course.
...@@ -75,7 +83,7 @@ class CourseGradeFactory(object): ...@@ -75,7 +83,7 @@ class CourseGradeFactory(object):
or course_key should be provided. or course_key should be provided.
""" """
course_data = CourseData(user, course, collected_block_structure, course_structure, course_key) course_data = CourseData(user, course, collected_block_structure, course_structure, course_key)
return self._update(user, course_data, read_only=False) return self._update(user, course_data, read_only=False, force_update_subsections=force_update_subsections)
@contextmanager @contextmanager
def _course_transaction(self, course_key): def _course_transaction(self, course_key):
...@@ -118,10 +126,17 @@ class CourseGradeFactory(object): ...@@ -118,10 +126,17 @@ class CourseGradeFactory(object):
def _iter_grade_result(self, user, course_data, force_update): def _iter_grade_result(self, user, course_data, force_update):
try: try:
kwargs = {
'user': user,
'course': course_data.course,
'collected_block_structure': course_data.collected_structure,
'course_key': course_data.course_key
}
if force_update:
kwargs['force_update_subsections'] = True
method = CourseGradeFactory().update if force_update else CourseGradeFactory().create method = CourseGradeFactory().update if force_update else CourseGradeFactory().create
course_grade = method( course_grade = method(**kwargs)
user, course_data.course, course_data.collected_structure, course_key=course_data.course_key,
)
return self.GradeResult(user, course_grade, None) return self.GradeResult(user, course_grade, None)
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
# Keep marching on even if this student couldn't be graded for # Keep marching on even if this student couldn't be graded for
...@@ -165,14 +180,14 @@ class CourseGradeFactory(object): ...@@ -165,14 +180,14 @@ class CourseGradeFactory(object):
return course_grade, persistent_grade.grading_policy_hash return course_grade, persistent_grade.grading_policy_hash
@staticmethod @staticmethod
def _update(user, course_data, read_only): def _update(user, course_data, read_only, force_update_subsections=False):
""" """
Computes, saves, and returns a CourseGrade object for the Computes, saves, and returns a CourseGrade object for the
given user and course. given user and course.
Sends a COURSE_GRADE_CHANGED signal to listeners and a Sends a COURSE_GRADE_CHANGED signal to listeners and a
COURSE_GRADE_NOW_PASSED if learner has passed course. COURSE_GRADE_NOW_PASSED if learner has passed course.
""" """
course_grade = CourseGrade(user, course_data) course_grade = CourseGrade(user, course_data, force_update_subsections=force_update_subsections)
course_grade.update() course_grade.update()
should_persist = ( should_persist = (
......
...@@ -106,7 +106,7 @@ def compute_grades_for_course_v2(self, **kwargs): ...@@ -106,7 +106,7 @@ def compute_grades_for_course_v2(self, **kwargs):
@task(base=_BaseTask) @task(base=_BaseTask)
def compute_grades_for_course(course_key, offset, batch_size, **kwargs): # pylint: disable=unused-argument def compute_grades_for_course(course_key, offset, batch_size, **kwargs): # pylint: disable=unused-argument
""" """
Compute grades for a set of students in the specified course. Compute and save grades for a set of students in the specified course.
The set of students will be determined by the order of enrollment date, and The set of students will be determined by the order of enrollment date, and
limited to at most <batch_size> students, starting from the specified limited to at most <batch_size> students, starting from the specified
......
...@@ -246,6 +246,20 @@ class TestCourseGradeFactory(GradeTestBase): ...@@ -246,6 +246,20 @@ class TestCourseGradeFactory(GradeTestBase):
else: else:
self.assertIsNone(course_grade) self.assertIsNone(course_grade)
@ddt.data(True, False)
def test_iter_force_update(self, force_update):
base_string = 'lms.djangoapps.grades.new.subsection_grade_factory.SubsectionGradeFactory.{}'
desired_method_name = base_string.format('update' if force_update else 'create')
undesired_method_name = base_string.format('create' if force_update else 'update')
with patch(desired_method_name) as desired_call:
with patch(undesired_method_name) as undesired_call:
set(CourseGradeFactory().iter(
users=[self.request.user], course=self.course, force_update=force_update
))
self.assertTrue(desired_call.called)
self.assertFalse(undesired_call.called)
@ddt.ddt @ddt.ddt
class TestSubsectionGradeFactory(ProblemSubmissionTestMixin, GradeTestBase): class TestSubsectionGradeFactory(ProblemSubmissionTestMixin, GradeTestBase):
......
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