Files
edx-platform/lms/djangoapps/grades/new/subsection_grade.py
Nimisha Asthagiri be5940e6f4 Robust Grades refactor
Adds ENABLE_SUBSECTION_GRADES_SAVED feature flag to both lms and cms. Also
installs the wiring that will allow robust grades to be used for courses
that enable it. This functionality is still gated by the feature flag
and should not be used until the remaining robust grades work is finished.
2016-08-03 15:47:16 -04:00

143 lines
5.2 KiB
Python

"""
SubsectionGrade Class
"""
from collections import OrderedDict
from lazy import lazy
from django.conf import settings
from courseware.model_data import ScoresClient
from lms.djangoapps.grades.scores import get_score, possibly_scored
from student.models import anonymous_id_for_user
from submissions import api as submissions_api
from xmodule import block_metadata_utils, graders
from xmodule.graders import Score
class SubsectionGrade(object):
"""
Class for Subsection Grades.
"""
def __init__(self, subsection):
self.location = subsection.location
self.display_name = block_metadata_utils.display_name_with_default_escaped(subsection)
self.url_name = block_metadata_utils.url_name_for_block(subsection)
self.format = getattr(subsection, 'format', '')
self.due = getattr(subsection, 'due', None)
self.graded = getattr(subsection, 'graded', False)
self.graded_total = None # aggregated grade for all graded problems
self.all_total = None # aggregated grade for all problems, regardless of whether they are graded
self.locations_to_scores = OrderedDict() # dict of problem locations to their Score objects
@lazy
def scores(self):
"""
List of all problem scores in the subsection.
"""
return list(self.locations_to_scores.itervalues())
def compute(self, student, course_structure, scores_client, submissions_scores):
"""
Compute the grade of this subsection for the given student and course.
"""
for descendant_key in course_structure.post_order_traversal(
filter_func=possibly_scored,
start_node=self.location,
):
descendant = course_structure[descendant_key]
if not getattr(descendant, 'has_score', False):
continue
(earned, possible) = get_score(
student,
descendant,
scores_client,
submissions_scores,
)
if earned is None and possible is None:
continue
# cannot grade a problem with a denominator of 0
descendant_graded = descendant.graded if possible > 0 else False
self.locations_to_scores[descendant.location] = Score(
earned,
possible,
descendant_graded,
block_metadata_utils.display_name_with_default_escaped(descendant),
descendant.location,
)
self.all_total, self.graded_total = graders.aggregate_scores(
self.scores, self.display_name,
)
class SubsectionGradeFactory(object):
"""
Factory for Subsection Grades.
"""
def __init__(self, student):
self.student = student
self._scores_client = None
self._submissions_scores = None
def create(self, subsection, course_structure, course):
"""
Returns the SubsectionGrade object for the student and subsection.
"""
return (
self._get_saved_grade(subsection, course) or
self._compute_and_update_grade(subsection, course_structure, course)
)
def _compute_and_update_grade(self, subsection, course_structure, course):
"""
Freshly computes and updates the grade for the student and subsection.
"""
self._prefetch_scores(course_structure, course)
subsection_grade = SubsectionGrade(subsection)
subsection_grade.compute(self.student, course_structure, self._scores_client, self._submissions_scores)
self._update_saved_grade(subsection_grade, subsection, course)
return subsection_grade
def _get_saved_grade(self, subsection, course): # pylint: disable=unused-argument
"""
Returns the saved grade for the given course and student.
"""
if settings.FEATURES.get('ENABLE_SUBSECTION_GRADES_SAVED') and course.enable_subsection_grades_saved:
# TODO Retrieve the saved grade for the subsection, if it exists.
pass
def _update_saved_grade(self, subsection_grade, subsection, course): # pylint: disable=unused-argument
"""
Returns the saved grade for the given course and student.
"""
if settings.FEATURES.get('ENABLE_SUBSECTION_GRADES_SAVED') and course.enable_subsection_grades_saved:
# TODO Update the saved grade for the subsection.
_pretend_to_save_subsection_grades()
def _prefetch_scores(self, course_structure, course):
"""
Returns the prefetched scores for the given student and course.
"""
if not self._scores_client:
scorable_locations = [block_key for block_key in course_structure if possibly_scored(block_key)]
self._scores_client = ScoresClient.create_for_locations(
course.id, self.student.id, scorable_locations
)
self._submissions_scores = submissions_api.get_scores(
unicode(course.id), anonymous_id_for_user(self.student, course.id)
)
def _pretend_to_save_subsection_grades():
"""
Stub to facilitate testing feature flag until robust grade work lands.
"""
pass