Merge pull request #13881 from edx/efischer/tnl-5314
Dashboard Final Grade Updates
This commit is contained in:
@@ -240,6 +240,25 @@ class CourseGrade(object):
|
||||
|
||||
return course_grade
|
||||
|
||||
@classmethod
|
||||
def get_persisted_grade(cls, user, course):
|
||||
"""
|
||||
Gets the persisted grade in the database, without checking
|
||||
whether it is up-to-date with the course's grading policy.
|
||||
For read use only.
|
||||
"""
|
||||
try:
|
||||
persistent_grade = PersistentCourseGrade.read_course_grade(user.id, course.id)
|
||||
except PersistentCourseGrade.DoesNotExist:
|
||||
return None
|
||||
else:
|
||||
course_grade = CourseGrade(user, course, None) # no course structure needed
|
||||
course_grade._percent = persistent_grade.percent_grade # pylint: disable=protected-access
|
||||
course_grade._letter_grade = persistent_grade.letter_grade # pylint: disable=protected-access
|
||||
course_grade.course_version = persistent_grade.course_version
|
||||
course_grade.course_edited_timestamp = persistent_grade.course_edited_timestamp
|
||||
return course_grade
|
||||
|
||||
@staticmethod
|
||||
def _calc_percent(grade_value):
|
||||
"""
|
||||
@@ -328,17 +347,17 @@ class CourseGradeFactory(object):
|
||||
course_structure = get_course_blocks(self.student, course.location)
|
||||
self._compute_and_update_grade(course, course_structure)
|
||||
|
||||
def _compute_and_update_grade(self, course, course_structure, read_only=False):
|
||||
def get_persisted(self, course):
|
||||
"""
|
||||
Freshly computes and updates the grade for the student and course.
|
||||
|
||||
If read_only is True, doesn't save any updates to the grades.
|
||||
Returns the saved grade for the given course and student,
|
||||
irrespective of whether the saved grade is up-to-date.
|
||||
"""
|
||||
course_grade = CourseGrade(self.student, course, course_structure)
|
||||
course_grade.compute_and_update(read_only)
|
||||
return course_grade
|
||||
if not PersistentGradesEnabledFlag.feature_enabled(course.id):
|
||||
return None
|
||||
|
||||
def _get_saved_grade(self, course, course_structure): # pylint: disable=unused-argument
|
||||
return CourseGrade.get_persisted_grade(self.student, course)
|
||||
|
||||
def _get_saved_grade(self, course, course_structure):
|
||||
"""
|
||||
Returns the saved grade for the given course and student.
|
||||
"""
|
||||
@@ -351,6 +370,16 @@ class CourseGradeFactory(object):
|
||||
course_structure
|
||||
)
|
||||
|
||||
def _compute_and_update_grade(self, course, course_structure, read_only=False):
|
||||
"""
|
||||
Freshly computes and updates the grade for the student and course.
|
||||
|
||||
If read_only is True, doesn't save any updates to the grades.
|
||||
"""
|
||||
course_grade = CourseGrade(self.student, course, course_structure)
|
||||
course_grade.compute_and_update(read_only)
|
||||
return course_grade
|
||||
|
||||
def _user_has_access_to_course(self, course_structure):
|
||||
"""
|
||||
Given a course structure, returns whether the user
|
||||
|
||||
@@ -82,6 +82,23 @@ class TestCourseGradeFactory(GradeTestBase):
|
||||
"""
|
||||
Test that CourseGrades are calculated properly
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestCourseGradeFactory, self).setUp()
|
||||
grading_policy = {
|
||||
"GRADER": [
|
||||
{
|
||||
"type": "Homework",
|
||||
"min_count": 1,
|
||||
"drop_count": 0,
|
||||
"short_label": "HW",
|
||||
"weight": 1.0,
|
||||
},
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.5,
|
||||
},
|
||||
}
|
||||
self.course.set_grading_policy(grading_policy)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'PERSISTENT_GRADES_ENABLED_FOR_ALL_TESTS': False})
|
||||
@ddt.data(
|
||||
@@ -106,7 +123,26 @@ class TestCourseGradeFactory(GradeTestBase):
|
||||
self.assertEqual(mock_save_grades.called, feature_flag and course_setting)
|
||||
|
||||
def test_course_grade_creation(self):
|
||||
grading_policy = {
|
||||
grade_factory = CourseGradeFactory(self.request.user)
|
||||
with mock_get_score(1, 2):
|
||||
course_grade = grade_factory.create(self.course)
|
||||
self.assertEqual(course_grade.letter_grade, u'Pass')
|
||||
self.assertEqual(course_grade.percent, 0.5)
|
||||
|
||||
def test_get_persisted(self):
|
||||
grade_factory = CourseGradeFactory(self.request.user)
|
||||
# first, create a grade in the database
|
||||
with mock_get_score(1, 2):
|
||||
grade_factory.create(self.course, read_only=False)
|
||||
|
||||
# retrieve the grade, ensuring it is as expected and take just one query
|
||||
with self.assertNumQueries(1):
|
||||
course_grade = grade_factory.get_persisted(self.course)
|
||||
self.assertEqual(course_grade.letter_grade, u'Pass')
|
||||
self.assertEqual(course_grade.percent, 0.5)
|
||||
|
||||
# update the grading policy
|
||||
new_grading_policy = {
|
||||
"GRADER": [
|
||||
{
|
||||
"type": "Homework",
|
||||
@@ -117,13 +153,15 @@ class TestCourseGradeFactory(GradeTestBase):
|
||||
},
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.5,
|
||||
"Pass": 0.9,
|
||||
},
|
||||
}
|
||||
self.course.set_grading_policy(grading_policy)
|
||||
grade_factory = CourseGradeFactory(self.request.user)
|
||||
with mock_get_score(1, 2):
|
||||
course_grade = grade_factory.create(self.course)
|
||||
self.course.set_grading_policy(new_grading_policy)
|
||||
|
||||
# ensure the grade can still be retrieved via get_persisted
|
||||
# despite its outdated grading policy
|
||||
with self.assertNumQueries(1):
|
||||
course_grade = grade_factory.get_persisted(self.course)
|
||||
self.assertEqual(course_grade.letter_grade, u'Pass')
|
||||
self.assertEqual(course_grade.percent, 0.5)
|
||||
|
||||
@@ -168,18 +206,18 @@ class TestSubsectionGradeFactory(ProblemSubmissionTestMixin, GradeTestBase):
|
||||
with patch(
|
||||
'lms.djangoapps.grades.new.subsection_grade.SubsectionGradeFactory._get_bulk_cached_grade',
|
||||
wraps=self.subsection_grade_factory._get_bulk_cached_grade
|
||||
) as mock_get_saved_grade:
|
||||
) as mock_get_bulk_cached_grade:
|
||||
with self.assertNumQueries(14):
|
||||
grade_a = self.subsection_grade_factory.create(self.sequence)
|
||||
self.assertTrue(mock_get_saved_grade.called)
|
||||
self.assertTrue(mock_get_bulk_cached_grade.called)
|
||||
self.assertTrue(mock_create_grade.called)
|
||||
|
||||
mock_get_saved_grade.reset_mock()
|
||||
mock_get_bulk_cached_grade.reset_mock()
|
||||
mock_create_grade.reset_mock()
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
grade_b = self.subsection_grade_factory.create(self.sequence)
|
||||
self.assertTrue(mock_get_saved_grade.called)
|
||||
self.assertTrue(mock_get_bulk_cached_grade.called)
|
||||
self.assertFalse(mock_create_grade.called)
|
||||
|
||||
self.assertEqual(grade_a.url_name, grade_b.url_name)
|
||||
|
||||
Reference in New Issue
Block a user