def chapter_grades(self): """ Returns a list of chapters, each containing its subsection grades, display name, and url name. """ chapter_grades = [] for chapter_key in self.course_structure.get_children( self.course.location): chapter = self.course_structure[chapter_key] chapter_subsection_grades = [] children = self.course_structure.get_children(chapter_key) for subsection_key in children: chapter_subsection_grades.append( self._subsection_grade_factory.create( self.course_structure[subsection_key], read_only=True)) chapter_grades.append({ 'display_name': block_metadata_utils.display_name_with_default_escaped( chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': chapter_subsection_grades }) return chapter_grades
def chapter_grades(self): """ Returns a dictionary of dictionaries. The primary dictionary is keyed by the chapter's usage_key. The secondary dictionary contains the chapter's subsection grades, display name, and url name. """ chapter_grades = OrderedDict() for chapter_key in self.course_structure.get_children( self.course.location): chapter = self.course_structure[chapter_key] chapter_subsection_grades = [] children = self.course_structure.get_children(chapter_key) for subsection_key in children: chapter_subsection_grades.append( self._subsection_grade_factory.create( self.course_structure[subsection_key], read_only=True)) chapter_grades[chapter_key] = { 'display_name': block_metadata_utils.display_name_with_default_escaped( chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': chapter_subsection_grades } return chapter_grades
def compute_and_update(self, read_only=False): """ Computes the grade for the given student and course. If read_only is True, doesn't save any updates to the grades. """ self._log_event(log.warning, u"compute_and_update, read_only: {}".format(read_only)) subsection_grade_factory = SubsectionGradeFactory(self.student, self.course, self.course_structure) for chapter_key in self.course_structure.get_children(self.course.location): chapter = self.course_structure[chapter_key] chapter_subsection_grades = [] for subsection_key in self.course_structure.get_children(chapter_key): chapter_subsection_grades.append( subsection_grade_factory.create(self.course_structure[subsection_key], read_only=True) ) self.chapter_grades.append({ 'display_name': block_metadata_utils.display_name_with_default_escaped(chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': chapter_subsection_grades }) if not read_only: subsection_grade_factory.bulk_create_unsaved() self._signal_listeners_when_grade_computed()
def compute(self): """ Computes the grade for the given student and course. """ subsection_grade_factory = SubsectionGradeFactory(self.student) for chapter_key in self.course_structure.get_children( self.course.location): chapter = self.course_structure[chapter_key] subsection_grades = [] for subsection_key in self.course_structure.get_children( chapter_key): subsection_grades.append( subsection_grade_factory.create( self.course_structure[subsection_key], self.course_structure, self.course)) self.chapter_grades.append({ 'display_name': block_metadata_utils.display_name_with_default_escaped( chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': subsection_grades }) self._signal_listeners_when_grade_computed()
def compute_and_update(self, read_only=False): """ Computes the grade for the given student and course. If read_only is True, doesn't save any updates to the grades. """ self._log_event(log.warning, u"compute_and_update, read_only: {}".format(read_only)) subsection_grade_factory = SubsectionGradeFactory( self.student, self.course, self.course_structure) for chapter_key in self.course_structure.get_children( self.course.location): chapter = self.course_structure[chapter_key] chapter_subsection_grades = [] for subsection_key in self.course_structure.get_children( chapter_key): chapter_subsection_grades.append( subsection_grade_factory.create( self.course_structure[subsection_key], read_only=True)) self.chapter_grades.append({ 'display_name': block_metadata_utils.display_name_with_default_escaped( chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': chapter_subsection_grades }) if not read_only: subsection_grade_factory.bulk_create_unsaved() self._signal_listeners_when_grade_computed()
def _verify_grades(self, raw_earned, raw_possible, weight, expected_score): """ Verifies the computed grades are as expected. """ with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): # pylint: disable=no-member for problem in self.problems: problem.weight = weight self.store.update_item(problem, self.user.id) self.store.publish(self.course.location, self.user.id) course_structure = get_course_blocks(self.request.user, self.course.location) # answer all problems for problem in self.problems: answer_problem(self.course, self.request, problem, score=raw_earned, max_value=raw_possible) # get grade subsection_grade = SubsectionGradeFactory( self.request.user, self.course, course_structure ).update(self.sequential) # verify all problem grades for problem in self.problems: problem_score = subsection_grade.locations_to_scores[problem.location] expected_score.display_name = display_name_with_default_escaped(problem) expected_score.module_id = problem.location self.assertEquals(problem_score, expected_score) # verify subsection grades self.assertEquals(subsection_grade.all_total.earned, expected_score.earned * len(self.problems)) self.assertEquals(subsection_grade.all_total.possible, expected_score.possible * len(self.problems))
def _get_chapter_grade_info(self, chapter, course_structure): """ Helper that returns a dictionary of chapter grade information. """ chapter_subsection_grades = self._get_subsection_grades(course_structure, chapter.location) return { 'display_name': block_metadata_utils.display_name_with_default_escaped(chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': chapter_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_weighted_scores = OrderedDict() # dict of problem locations to (Score, weight) tuples
def display_name_with_default_escaped(self): """ DEPRECATED: use display_name_with_default Return html escaped reasonable display name for the course. Note: This newly introduced method should not be used. It was only introduced to enable a quick search/replace and the ability to slowly migrate and test switching to display_name_with_default, which is no longer escaped. """ return block_metadata_utils.display_name_with_default_escaped(self)
def compute_and_update(self, read_only=False): """ Computes the grade for the given student and course. If read_only is True, doesn't save any updates to the grades. """ subsection_grade_factory = SubsectionGradeFactory( self.student, self.course, self.course_structure) subsections_total = 0 for chapter_key in self.course_structure.get_children( self.course.location): chapter = self.course_structure[chapter_key] chapter_subsection_grades = [] children = self.course_structure.get_children(chapter_key) subsections_total += len(children) for subsection_key in children: chapter_subsection_grades.append( subsection_grade_factory.create( self.course_structure[subsection_key], read_only=True)) self.chapter_grades.append({ 'display_name': block_metadata_utils.display_name_with_default_escaped( chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': chapter_subsection_grades }) total_graded_subsections = sum( len(x) for x in self.subsection_grade_totals_by_format.itervalues()) subsections_created = len( subsection_grade_factory._unsaved_subsection_grades) # pylint: disable=protected-access subsections_read = subsections_total - subsections_created blocks_total = len(self.locations_to_scores) if not read_only: subsection_grade_factory.bulk_create_unsaved() self._signal_listeners_when_grade_computed() self._log_event( log.warning, u"compute_and_update, read_only: {0}, subsections read/created: {1}/{2}, blocks accessed: {3}, total " u"graded subsections: {4}".format( read_only, subsections_read, subsections_created, blocks_total, total_graded_subsections, ))
def _compute_block_score( self, student, block_key, course_structure, scores_client, submissions_scores, persisted_values, ): """ Compute score for the given block. If persisted_values is provided, it is used for possible and weight. """ block = course_structure[block_key] if getattr(block, 'has_score', False): possible = persisted_values.get('possible', None) weight = persisted_values.get('weight', getattr(block, 'weight', None)) (earned, possible) = get_score( student, block, scores_client, submissions_scores, weight, possible, ) if earned is not None or possible is not None: # There's a chance that the value of graded is not the same # value when the problem was scored. Since we get the value # from the block_structure. # # Cannot grade a problem with a denominator of 0. # TODO: None > 0 is not python 3 compatible. block_graded = self._get_explicit_graded( block, course_structure) if possible > 0 else False self.locations_to_weighted_scores[block.location] = ( Score( earned, possible, block_graded, block_metadata_utils.display_name_with_default_escaped( block), block.location, ), weight, )
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.course_version = getattr(subsection, 'course_version', None) self.subtree_edited_timestamp = getattr(subsection, 'subtree_edited_on', None) 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
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.show_correctness = getattr(subsection, 'show_correctness', '') self.course_version = getattr(subsection, 'course_version', None) self.subtree_edited_timestamp = getattr(subsection, 'subtree_edited_on', None)
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.show_correctness = getattr(subsection, 'show_correctness', '') self.course_version = getattr(subsection, 'course_version', None) self.subtree_edited_timestamp = getattr(subsection, 'subtree_edited_on', None) self.override = None
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.course_version = getattr(subsection, 'course_version', None) self.subtree_edited_timestamp = getattr(subsection, 'subtree_edited_on', None) 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 ProblemScore
def _compute_block_score( self, student, block_key, course_structure, scores_client, submissions_scores, persisted_values, ): """ Compute score for the given block. If persisted_values is provided, it is used for possible and weight. """ block = course_structure[block_key] if getattr(block, 'has_score', False): possible = persisted_values.get('possible', None) weight = persisted_values.get('weight', getattr(block, 'weight', None)) (earned, possible) = get_score( student, block, scores_client, submissions_scores, weight, possible, ) if earned is not None or possible is not None: # There's a chance that the value of graded is not the same # value when the problem was scored. Since we get the value # from the block_structure. # # Cannot grade a problem with a denominator of 0. # TODO: None > 0 is not python 3 compatible. block_graded = self._get_explicit_graded(block, course_structure) if possible > 0 else False self.locations_to_weighted_scores[block.location] = ( Score( earned, possible, block_graded, block_metadata_utils.display_name_with_default_escaped(block), block.location, ), weight, )
def _compute_block_score( self, student, block_key, course_structure, scores_client, submissions_scores, persisted_values=None, ): """ Compute score for the given block. If persisted_values is provided, it will be used for possible and weight. """ block = course_structure[block_key] if getattr(block, 'has_score', False): (earned, possible) = get_score( student, block, scores_client, submissions_scores, ) # There's a chance that the value of weight is not the same value used when the problem was scored, # since we can get the value from either block_structure or CSM/submissions. weight = getattr(block, 'weight', None) if persisted_values: possible = persisted_values.get('possible', possible) weight = persisted_values.get('weight', weight) if earned is not None or possible is not None: # cannot grade a problem with a denominator of 0 block_graded = block.graded if possible > 0 else False self.locations_to_weighted_scores[block.location] = ( Score( earned, possible, block_graded, block_metadata_utils.display_name_with_default_escaped( block), block.location, ), weight, )
def _compute_block_score( self, student, block_key, course_structure, scores_client, submissions_scores, persisted_values=None, ): """ Compute score for the given block. If persisted_values is provided, it will be used for possible and weight. """ block = course_structure[block_key] if getattr(block, 'has_score', False): (earned, possible) = get_score( student, block, scores_client, submissions_scores, ) # There's a chance that the value of weight is not the same value used when the problem was scored, # since we can get the value from either block_structure or CSM/submissions. weight = block.weight if persisted_values: possible = persisted_values.get('possible', possible) weight = persisted_values.get('weight', weight) if earned is not None or possible is not None: # cannot grade a problem with a denominator of 0 block_graded = block.graded if possible > 0 else False self.locations_to_weighted_scores[block.location] = ( Score( earned, possible, block_graded, block_metadata_utils.display_name_with_default_escaped(block), block.location, ), weight, )
def chapter_grades(self): """ Returns a list of chapters, each containing its subsection grades, display name, and url name. """ chapter_grades = [] for chapter_key in self.course_structure.get_children(self.course.location): chapter = self.course_structure[chapter_key] chapter_subsection_grades = [] children = self.course_structure.get_children(chapter_key) for subsection_key in children: chapter_subsection_grades.append( self._subsection_grade_factory.create(self.course_structure[subsection_key], read_only=True) ) chapter_grades.append({ 'display_name': block_metadata_utils.display_name_with_default_escaped(chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': chapter_subsection_grades }) return chapter_grades
def compute_and_update(self, read_only=False): """ Computes the grade for the given student and course. If read_only is True, doesn't save any updates to the grades. """ subsection_grade_factory = SubsectionGradeFactory(self.student, self.course, self.course_structure) for chapter_key in self.course_structure.get_children(self.course.location): chapter = self.course_structure[chapter_key] chapter_subsection_grades = [] for subsection_key in self.course_structure.get_children(chapter_key): chapter_subsection_grades.append( subsection_grade_factory.create(self.course_structure[subsection_key], read_only=True) ) self.chapter_grades.append({ 'display_name': block_metadata_utils.display_name_with_default_escaped(chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': chapter_subsection_grades }) subsections_total = sum(len(x) for x in self.subsection_grade_totals_by_format.itervalues()) subsections_read = len(subsection_grade_factory._unsaved_subsection_grades) # pylint: disable=protected-access subsections_created = subsections_total - subsections_read blocks_total = len(self.locations_to_scores) if not read_only: subsection_grade_factory.bulk_create_unsaved() self._signal_listeners_when_grade_computed() self._log_event( log.warning, u"compute_and_update, read_only: {0}, subsections read/created: {1}/{2}, blocks accessed: {3}".format( read_only, subsections_read, subsections_created, blocks_total, ) )
def compute(self): """ Computes the grade for the given student and course. """ subsection_grade_factory = SubsectionGradeFactory(self.student) for chapter_key in self.course_structure.get_children(self.course.location): chapter = self.course_structure[chapter_key] subsection_grades = [] for subsection_key in self.course_structure.get_children(chapter_key): subsection_grades.append( subsection_grade_factory.create( self.course_structure[subsection_key], self.course_structure, self.course ) ) self.chapter_grades.append({ 'display_name': block_metadata_utils.display_name_with_default_escaped(chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': subsection_grades }) self._signal_listeners_when_grade_computed()
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, )
def _calculate_totaled_scores( student, grading_context_result, submissions_scores, scores_client, keep_raw_scores, ): """ Returns a tuple of totaled scores and raw scores, which can be passed to the grader. """ raw_scores = [] totaled_scores = {} for section_format, sections in grading_context_result['all_graded_sections'].iteritems(): format_scores = [] for section_info in sections: section = section_info['section_block'] section_name = block_metadata_utils.display_name_with_default(section) with outer_atomic(): # Check to # see if any of our locations are in the scores from the submissions # API. If scores exist, we have to calculate grades for this section. should_grade_section = any( unicode(descendant.location) in submissions_scores for descendant in section_info['scored_descendants'] ) if not should_grade_section: should_grade_section = any( descendant.location in scores_client for descendant in section_info['scored_descendants'] ) # If we haven't seen a single problem in the section, we don't have # to grade it at all! We can assume 0% if should_grade_section: scores = [] for descendant in section_info['scored_descendants']: (correct, total) = get_score( student, descendant, scores_client, submissions_scores, ) if correct is None and total is None: continue if settings.GENERATE_PROFILE_SCORES: # for debugging! if total > 1: correct = random.randrange(max(total - 2, 1), total + 1) else: correct = total graded = descendant.graded if not total > 0: # We simply cannot grade a problem that is 12/0, because we might need it as a percentage graded = False scores.append( Score( correct, total, graded, block_metadata_utils.display_name_with_default_escaped(descendant), descendant.location ) ) __, graded_total = graders.aggregate_scores(scores, section_name) if keep_raw_scores: raw_scores += scores else: graded_total = Score(0.0, 1.0, True, section_name, None) # Add the graded total to totaled_scores if graded_total.possible > 0: format_scores.append(graded_total) else: log.info( "Unable to grade a section with a total possible score of zero. " + str(section.location) ) totaled_scores[section_format] = format_scores return totaled_scores, raw_scores
def _progress_summary(student, course, course_structure=None): """ Unwrapped version of "progress_summary". This pulls a summary of all problems in the course. Returns - courseware_summary is a summary of all sections with problems in the course. It is organized as an array of chapters, each containing an array of sections, each containing an array of scores. This contains information for graded and ungraded problems, and is good for displaying a course summary with due dates, etc. - None if the student does not have access to load the course module. Arguments: student: A User object for the student to grade course: A Descriptor containing the course to grade """ if course_structure is None: course_structure = get_course_blocks(student, course.location) if not len(course_structure): return None scorable_locations = [block_key for block_key in course_structure if possibly_scored(block_key)] with outer_atomic(): scores_client = ScoresClient.create_for_locations(course.id, student.id, scorable_locations) # We need to import this here to avoid a circular dependency of the form: # XBlock --> submissions --> Django Rest Framework error strings --> # Django translation --> ... --> courseware --> submissions from submissions import api as sub_api # installed from the edx-submissions repository with outer_atomic(): submissions_scores = sub_api.get_scores( unicode(course.id), anonymous_id_for_user(student, course.id) ) # Check for gated content gated_content = gating_api.get_gated_content(course, student) chapters = [] locations_to_weighted_scores = {} for chapter_key in course_structure.get_children(course_structure.root_block_usage_key): chapter = course_structure[chapter_key] sections = [] for section_key in course_structure.get_children(chapter_key): if unicode(section_key) in gated_content: continue section = course_structure[section_key] graded = getattr(section, 'graded', False) scores = [] for descendant_key in course_structure.post_order_traversal( filter_func=possibly_scored, start_node=section_key, ): descendant = course_structure[descendant_key] (correct, total) = get_score( student, descendant, scores_client, submissions_scores, ) if correct is None and total is None: continue weighted_location_score = Score( correct, total, graded, block_metadata_utils.display_name_with_default_escaped(descendant), descendant.location ) scores.append(weighted_location_score) locations_to_weighted_scores[descendant.location] = weighted_location_score escaped_section_name = block_metadata_utils.display_name_with_default_escaped(section) section_total, _ = graders.aggregate_scores(scores, escaped_section_name) sections.append({ 'display_name': escaped_section_name, 'url_name': block_metadata_utils.url_name_for_block(section), 'scores': scores, 'section_total': section_total, 'format': getattr(section, 'format', ''), 'due': getattr(section, 'due', None), 'graded': graded, }) chapters.append({ 'course': course.display_name_with_default_escaped, 'display_name': block_metadata_utils.display_name_with_default_escaped(chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': sections }) return ProgressSummary(chapters, locations_to_weighted_scores, course_structure.get_children)
def _calculate_totaled_scores( student, grading_context_result, submissions_scores, scores_client, keep_raw_scores, ): """ Returns the totaled scores, which can be passed to the grader. """ raw_scores = [] totaled_scores = {} for section_format, sections in grading_context_result['all_graded_sections'].iteritems(): format_scores = [] for section_info in sections: section = section_info['section_block'] section_name = block_metadata_utils.display_name_with_default(section) with outer_atomic(): # Check to # see if any of our locations are in the scores from the submissions # API. If scores exist, we have to calculate grades for this section. should_grade_section = any( unicode(descendant.location) in submissions_scores for descendant in section_info['scored_descendants'] ) if not should_grade_section: should_grade_section = any( descendant.location in scores_client for descendant in section_info['scored_descendants'] ) # If we haven't seen a single problem in the section, we don't have # to grade it at all! We can assume 0% if should_grade_section: scores = [] for descendant in section_info['scored_descendants']: (correct, total) = get_score( student, descendant, scores_client, submissions_scores, ) if correct is None and total is None: continue if settings.GENERATE_PROFILE_SCORES: # for debugging! if total > 1: correct = random.randrange(max(total - 2, 1), total + 1) else: correct = total graded = descendant.graded if not total > 0: # We simply cannot grade a problem that is 12/0, because we might need it as a percentage graded = False scores.append( Score( correct, total, graded, block_metadata_utils.display_name_with_default_escaped(descendant), descendant.location ) ) __, graded_total = graders.aggregate_scores(scores, section_name) if keep_raw_scores: raw_scores += scores else: graded_total = Score(0.0, 1.0, True, section_name, None) # Add the graded total to totaled_scores if graded_total.possible > 0: format_scores.append(graded_total) else: log.info( "Unable to grade a section with a total possible score of zero. " + str(section.location) ) totaled_scores[section_format] = format_scores return totaled_scores, raw_scores
def summary(student, course, course_structure=None): """ This pulls a summary of all problems in the course. Returns - courseware_summary is a summary of all sections with problems in the course. It is organized as an array of chapters, each containing an array of sections, each containing an array of scores. This contains information for graded and ungraded problems, and is good for displaying a course summary with due dates, etc. - None if the student does not have access to load the course module. Arguments: student: A User object for the student to grade course: A Descriptor containing the course to grade """ if course_structure is None: course_structure = get_course_blocks(student, course.location) if not len(course_structure): return ProgressSummary() scorable_locations = [ block_key for block_key in course_structure if possibly_scored(block_key) ] with outer_atomic(): scores_client = ScoresClient.create_for_locations( course.id, student.id, scorable_locations) # We need to import this here to avoid a circular dependency of the form: # XBlock --> submissions --> Django Rest Framework error strings --> # Django translation --> ... --> courseware --> submissions from submissions import api as sub_api # installed from the edx-submissions repository with outer_atomic(): submissions_scores = sub_api.get_scores( unicode(course.id), anonymous_id_for_user(student, course.id)) # Check for gated content gated_content = gating_api.get_gated_content(course, student) chapters = [] locations_to_weighted_scores = {} for chapter_key in course_structure.get_children( course_structure.root_block_usage_key): chapter = course_structure[chapter_key] sections = [] for section_key in course_structure.get_children(chapter_key): if unicode(section_key) in gated_content: continue section = course_structure[section_key] graded = getattr(section, 'graded', False) scores = [] for descendant_key in course_structure.post_order_traversal( filter_func=possibly_scored, start_node=section_key, ): descendant = course_structure[descendant_key] (correct, total) = get_score( student, descendant, scores_client, submissions_scores, ) if correct is None and total is None: continue weighted_location_score = Score( correct, total, graded, block_metadata_utils.display_name_with_default_escaped( descendant), descendant.location) scores.append(weighted_location_score) locations_to_weighted_scores[ descendant.location] = weighted_location_score escaped_section_name = block_metadata_utils.display_name_with_default_escaped( section) section_total, _ = graders.aggregate_scores( scores, escaped_section_name) sections.append({ 'display_name': escaped_section_name, 'url_name': block_metadata_utils.url_name_for_block(section), 'scores': scores, 'section_total': section_total, 'format': getattr(section, 'format', ''), 'due': getattr(section, 'due', None), 'graded': graded, }) chapters.append({ 'course': course.display_name_with_default_escaped, 'display_name': block_metadata_utils.display_name_with_default_escaped(chapter), 'url_name': block_metadata_utils.url_name_for_block(chapter), 'sections': sections }) return ProgressSummary(chapters, locations_to_weighted_scores, course_structure.get_children)
def get_score(submissions_scores, csm_scores, persisted_block, block): """ Returns the score for a problem, as a ProblemScore object. It is assumed that the provided storages have already been filtered for a single user in question and have user-specific values. The score is retrieved from the provided storages in the following order of precedence. If no value for the block is found in a given storage, the next storage is checked. submissions_scores (dict of {unicode(usage_key): (earned, possible)}): A python dictionary of serialized UsageKeys to (earned, possible) tuples. These values, retrieved using the Submissions API by the caller (already filtered for the user and course), take precedence above all other score storages. When the score is found in this storage, it implies the user's score for the block was persisted via the submissions API. Typically, this API is used by ORA. The returned score includes valid values for: weighted_earned weighted_possible graded - retrieved from the persisted block, if found, else from the latest block content. Note: raw_earned and raw_possible are not required when submitting scores via the submissions API, so those values (along with the unused weight) are invalid and irrelevant. csm_scores (ScoresClient): The ScoresClient object (already filtered for the user and course), from which a courseware.models.StudentModule object can be retrieved for the block. When the score is found from this storage, it implies the user's score for the block was persisted in the Courseware Student Module. Typically, this storage is used for all CAPA problems, including scores calculated by external graders. The returned score includes valid values for: raw_earned, raw_possible - retrieved from CSM weighted_earned, weighted_possible - calculated from the raw scores and weight weight, graded - retrieved from the persisted block, if found, else from the latest block content persisted_block (.models.BlockRecord): The block values as found in the grades persistence layer. These values are used only if not found from an earlier storage, and take precedence over values stored within the latest content-version of the block. When the score is found from this storage, it implies the user has not yet attempted this problem, but the user's grade _was_ persisted. The returned score includes valid values for: raw_earned - will equal 0.0 since the user's score was not found from earlier storages raw_possible - retrieved from the persisted block weighted_earned, weighted_possible - calculated from the raw scores and weight weight, graded - retrieved from the persisted block block (block_structure.BlockData): Values from the latest content-version of the block are used only if they were not available from a prior storage. When the score is found from this storage, it implies the user has not yet attempted this problem and the user's grade was _not_ yet persisted. The returned score includes valid values for: raw_earned - will equal 0.0 since the user's score was not found from earlier storages raw_possible - retrieved from the latest block content weighted_earned, weighted_possible - calculated from the raw scores and weight weight, graded - retrieved from the latest block content """ weight = _get_weight_from_block(persisted_block, block) # Priority order for retrieving the scores: # submissions API -> CSM -> grades persisted block -> latest block content raw_earned, raw_possible, weighted_earned, weighted_possible, attempted = ( _get_score_from_submissions(submissions_scores, block) or _get_score_from_csm(csm_scores, block, weight) or _get_score_from_persisted_or_latest_block(persisted_block, block, weight) ) if weighted_possible is None or weighted_earned is None: return None else: has_valid_denominator = weighted_possible > 0.0 graded = _get_graded_from_block(persisted_block, block) if has_valid_denominator else False return ProblemScore( raw_earned, raw_possible, weighted_earned, weighted_possible, weight, graded, display_name=display_name_with_default_escaped(block), module_id=block.location, attempted=attempted, )