def test_field_data_cache_scorable_locations(self): """Only scorable locations should be in FieldDataCache.scorable_locations.""" fd_cache = field_data_cache_for_grading(self.course, self.student) block_types = set(loc.block_type for loc in fd_cache.scorable_locations) self.assertNotIn('video', block_types) self.assertNotIn('html', block_types) self.assertNotIn('discussion', block_types) self.assertIn('problem', block_types)
def test_field_data_cache_scorable_locations(self): """Only scorable locations should be in FieldDataCache.scorable_locations.""" fd_cache = field_data_cache_for_grading(self.course, self.student) block_types = set(loc.block_type for loc in fd_cache.scorable_locations) self.assertNotIn('video', block_types) self.assertNotIn('html', block_types) self.assertNotIn('discussion', block_types) self.assertIn('problem', block_types)
def has_passed(request, course_id, section_url_name): """ Returns True if the student has higher or equeal grades in asssignment type. """ student = request.user # Get the course by ID course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(student, 'load', course_key, depth=None) # Get the grade summary with outer_atomic(): field_data_cache = grades.field_data_cache_for_grading(course, student) scores_client = ScoresClient.from_field_data_cache(field_data_cache) grade_summary = grades.grade(student, request, course, field_data_cache=field_data_cache, scores_client=scores_client) # Get assignment type wise percent assignments = {} for section in grade_summary['section_breakdown']: if section.get('prominent', False): assignments.update({section['category']: section['percent']}) # Get the section assignment type section_assignment_type = '' for chapter in course.get_children(): for sequenctial in chapter.get_children(): if sequenctial.url_name == section_url_name: section_assignment_type = sequenctial.format break # Get section assignment percent percentage = assignments.get(section_assignment_type, 0.0) # Return passing status return percentage * 100 == 100
def prepare_sections_with_grade(request, course): ''' Create sections with grade details. Return format: { 'sections': [ { 'display_name': name, # in case of cohorts or any other accessibility settings 'hidden': hidden, 'url_name': url_name, 'units': UNITS, 'rank': rank, 'badge': bagde status, 'points': grade points, 'podium': podium status, 'week': section_index + 1, }, ], } where UNITS is a list [ { 'display_name': name, 'position': unit position in section, 'css_class': css class, } , ... ] sections with name 'hidden' are skipped. NOTE: assumes that if we got this far, user has access to course. Returns [] if this is not the case. ''' # Set the student to request user student = request.user # Get the field data cache field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, student, course, depth=2, ) # Get the course module with modulestore().bulk_operations(course.id): course_module = get_module_for_descriptor( student, request, course, field_data_cache, course.id, course=course ) if course_module is None: return [] # Get the field data cache staff_user = User.objects.filter(is_staff=1)[0] staff_field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, staff_user, course, depth=2, ) # Get the course module with modulestore().bulk_operations(course.id): staff_course_module = get_module_for_descriptor( staff_user, request, course, staff_field_data_cache, course.id, course=course ) # staff accessible chapters staff_chapters = staff_course_module.get_display_items() # find the passing grade for the course nonzero_cutoffs = [cutoff for cutoff in course.grade_cutoffs.values() if cutoff > 0] success_cutoff = min(nonzero_cutoffs) if nonzero_cutoffs else 0 # find the course progress progress = get_course_progress(student, course.id) # prepare a list of discussions participated by user discussions_participated = get_discussions_participated( request, course.id.to_deprecated_string(), student.id ) # get courseware summary with outer_atomic(): field_data_cache = grades.field_data_cache_for_grading(course, student) scores_client = ScoresClient.from_field_data_cache(field_data_cache) courseware_summary = grades.progress_summary( student, request, course, field_data_cache=field_data_cache, scores_client=scores_client ) section_grades = {} for section in courseware_summary: earned = 0 total = 0 for sub_section in section['sections']: earned += sub_section['section_total'].earned total += sub_section['section_total'].possible section_score = earned / total if earned > 0 and total > 0 else 0 section_grades[section['url_name']] = { 'earned': earned, 'total': total, 'css_class': ('text-red', 'text-green')[int(section_score >= 0.6)] if total > 0 else '' } # Check for content which needs to be completed # before the rest of the content is made available required_content = milestones_helpers.get_required_content(course, student) # Check for gated content gated_content = gating_api.get_gated_content(course, student) # The user may not actually have to complete the entrance exam, if one is required if not user_must_complete_entrance_exam(request, student, course): required_content = [content for content in required_content if not content == course.entrance_exam_id] # define inner function def create_module(descriptor): '''creates an XModule instance given a descriptor''' return get_module_for_descriptor( student, request, descriptor, field_data_cache, course.id, course=course ) with outer_atomic(): submissions_scores = sub_api.get_scores( course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id) ) max_scores_cache = grades.MaxScoresCache.create_for_course(course) max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations) sections = list() student_chapters = course_module.get_display_items() urlname_chapters = {} for student_chap in student_chapters: urlname_chapters.update({student_chap.url_name:student_chap}) final_chapters = OrderedDict() for chapter_index, chapter in enumerate(staff_chapters): fin_chap = urlname_chapters.get(chapter.url_name) if fin_chap: final_chapters.update({str(chapter_index+1):{'hidden': False, 'chapter':fin_chap}}) else: final_chapters.update({str(chapter_index+1):{'hidden':True}}) for section_index, chapter_info in final_chapters.items(): # Mark as hidden and Skip the current chapter if a hide flag is tripped if chapter_info['hidden']: sections.append({ 'hidden': True, 'week': "WEEK {week}: ".format(week=section_index), 'points': { 'total': 0, 'earned': 0, 'css_class': 'text-disabled' }, }) continue chapter = chapter_info['chapter'] # get the points section_points = section_grades.get(chapter.url_name, {}) units = list() for sequential in chapter.get_display_items(): # Set hidden status of the sequential if it is gated/hidden from the user hidden = ( gated_content and unicode(sequential.location) in gated_content or sequential.hide_from_toc ) if hidden: continue for index, unit in enumerate(sequential.get_display_items()): css_class = 'dark-gray' if unit.graded: total_excercises = 0 attempted_excercises = 0 unit_max_score = 0 unit_score = 0 for component in unit.get_display_items(): if component.category == 'problem': if component.graded: total_excercises += 1 attempted_excercises += is_attempted_internal( str(component.location), progress ) (correct, total) = grades.get_score( student, component, create_module, scores_client, submissions_scores, max_scores_cache, ) unit_max_score += total unit_score += correct if total_excercises: css_class = 'blue' if attempted_excercises == total_excercises: css_class = 'green' if unit_max_score and unit_score / unit_max_score < success_cutoff: css_class = 'red' position = index + 1 # For jumping to the unit directly unit_context = { 'display_name': unit.display_name_with_default_escaped, 'position': position, 'css_class': css_class, 'courseware_url': reverse( 'courseware_position', args=[ course.id, chapter.url_name, sequential.url_name, position ] ) } units.append(unit_context) competency = None if int(section_points.get('total')): competency = int(section_points.get('earned')) == int(section_points.get('total')) section_context = { 'display_name': chapter.display_name_with_default_escaped, 'url_name': chapter.url_name, 'hidden': False, 'rank': 1, 'competency': competency, 'points': { 'total': int(section_points.get('total')), 'earned': int(section_points.get('earned')), 'css_class': section_points.get('css_class') }, 'participation': discussions_participated.get(chapter.url_name), 'units': units, 'week': "WEEK {week}: ".format(week=section_index), } sections.append(section_context) return sections
def grade(student, request, course, keep_raw_scores, field_data_cache, scores_client): """ This grades a student as quickly as possible. It returns the output from the course grader, augmented with the final letter grade. The keys in the output are: course: a CourseDescriptor - grade : A final letter grade. - percent : The final percent for the class (rounded up). - section_breakdown : A breakdown of each section that makes up the grade. (For display) - grade_breakdown : A breakdown of the major components that make up the final grade. (For display) - keep_raw_scores : if True, then value for key 'raw_scores' contains scores for every graded module More information on the format is in the docstring for CourseGrader. """ if field_data_cache is None: with manual_transaction(): field_data_cache = field_data_cache_for_grading(course, student) if scores_client is None: scores_client = ScoresClient.from_field_data_cache(field_data_cache) # Dict of item_ids -> (earned, possible) point tuples. This *only* grabs # scores that were registered with the submissions API, which for the moment # means only openassessment (edx-ora2) submissions_scores = sub_api.get_scores( course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id) ) max_scores_cache = MaxScoresCache.create_for_course(course) # For the moment, we have to get scorable_locations from field_data_cache # and not from scores_client, because scores_client is ignorant of things # in the submissions API. As a further refactoring step, submissions should # be hidden behind the ScoresClient. max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations) grading_context = course.grading_context raw_scores = [] totaled_scores = {} # This next complicated loop is just to collect the totaled_scores, which is # passed to the grader for section_format, sections in grading_context['graded_sections'].iteritems(): format_scores = [] for section in sections: section_descriptor = section['section_descriptor'] section_name = section_descriptor.display_name_with_default # some problems have state that is updated independently of interaction # with the LMS, so they need to always be scored. (E.g. foldit., # combinedopenended) should_grade_section = any( descriptor.always_recalculate_grades for descriptor in section['xmoduledescriptors'] ) # If there are no problems that always have to be regraded, 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. if not should_grade_section: should_grade_section = any( descriptor.location.to_deprecated_string() in submissions_scores for descriptor in section['xmoduledescriptors'] ) if not should_grade_section: should_grade_section = any( descriptor.location in scores_client for descriptor in section['xmoduledescriptors'] ) # 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 = [] def create_module(descriptor): '''creates an XModule instance given a descriptor''' # TODO: We need the request to pass into here. If we could forego that, our arguments # would be simpler return get_module_for_descriptor( student, request, descriptor, field_data_cache, course.id, course=course ) descendants = yield_dynamic_descriptor_descendants(section_descriptor, student.id, create_module) for module_descriptor in descendants: (correct, total) = get_score( student, module_descriptor, create_module, scores_client, submissions_scores, max_scores_cache, ) 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 = module_descriptor.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, module_descriptor.display_name_with_default, module_descriptor.location ) ) __, graded_total = aggregate_section_scores( scores, section_name, getattr(section_descriptor, 'weight', 1.0) ) if keep_raw_scores: raw_scores += scores else: graded_total = WeightedScore( 0.0, 1.0, True, section_name, None, getattr(section_descriptor, 'weight', 1.0) ) # 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. {}".format( section_descriptor.location ) ) totaled_scores[section_format] = format_scores # Grading policy might be overriden by a CCX, need to reset it course.set_grading_policy(course.grading_policy) grade_summary = course.grader.grade(totaled_scores, generate_random_scores=settings.GENERATE_PROFILE_SCORES) # We round the grade here, to make sure that the grade is an whole percentage and # doesn't get displayed differently than it gets grades grade_summary['percent'] = round(grade_summary['percent'] * 100 + 0.05) / 100 letter_grade = grade_for_percentage( course.grade_cutoffs, grade_summary['percent'], grade_summary['sections_passed'] ) grade_summary['grade'] = letter_grade grade_summary['totaled_scores'] = totaled_scores # make this available, eg for instructor download & debugging if keep_raw_scores: # way to get all RAW scores out to instructor # so grader can be double-checked grade_summary['raw_scores'] = raw_scores max_scores_cache.push_to_remote() return grade_summary
def progress_summary(student, request, course, field_data_cache=None, scores_client=None, grading_type='vertical'): """ 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. Arguments: student: A User object for the student to grade course: A Descriptor containing the course to grade If the student does not have access to load the course module, this function will return None. """ with manual_transaction(): if field_data_cache is None: field_data_cache = field_data_cache_for_grading(course, student) if scores_client is None: scores_client = ScoresClient.from_field_data_cache(field_data_cache) course_module = get_module_for_descriptor( student, request, course, field_data_cache, course.id, course=course ) if not course_module: return None course_module = getattr(course_module, '_x_module', course_module) submissions_scores = sub_api.get_scores( course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id) ) max_scores_cache = MaxScoresCache.create_for_course(course) # For the moment, we have to get scorable_locations from field_data_cache # and not from scores_client, because scores_client is ignorant of things # in the submissions API. As a further refactoring step, submissions should # be hidden behind the ScoresClient. max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations) blocks_stack = [course_module] blocks_dict = {} while blocks_stack: curr_block = blocks_stack.pop() with manual_transaction(): # Skip if the block is hidden if curr_block.hide_from_toc: continue key = unicode(curr_block.scope_ids.usage_id) children = curr_block.get_display_items() if curr_block.category != grading_type else [] block = { 'display_name': curr_block.display_name_with_default, 'block_type': curr_block.category, 'url_name': curr_block.url_name, 'children': [unicode(child.scope_ids.usage_id) for child in children], } if curr_block.category == grading_type: graded = curr_block.graded scores = [] module_creator = curr_block.xmodule_runtime.get_module for module_descriptor in yield_dynamic_descriptor_descendants( curr_block, student.id, module_creator ): (correct, total) = get_score( student, module_descriptor, module_creator, scores_client, submissions_scores, max_scores_cache, ) if correct is None and total is None: continue scores.append( Score( correct, total, graded, module_descriptor.display_name_with_default, module_descriptor.location ) ) scores.reverse() total, _ = aggregate_scores(scores, curr_block.display_name_with_default) module_format = curr_block.format if curr_block.format is not None else '' block.update({ 'scores': scores, 'total': total, 'format': module_format, 'due': curr_block.due, 'graded': graded, }) blocks_dict[key] = block # Add this blocks children to the stack so that we can traverse them as well. blocks_stack.extend(children) max_scores_cache.push_to_remote() return { 'root': unicode(course.scope_ids.usage_id), 'blocks': blocks_dict, }
def grade(student, request, course, keep_raw_scores, field_data_cache, scores_client): """ This grades a student as quickly as possible. It returns the output from the course grader, augmented with the final letter grade. The keys in the output are: course: a CourseDescriptor - grade : A final letter grade. - percent : The final percent for the class (rounded up). - section_breakdown : A breakdown of each section that makes up the grade. (For display) - grade_breakdown : A breakdown of the major components that make up the final grade. (For display) - keep_raw_scores : if True, then value for key 'raw_scores' contains scores for every graded module More information on the format is in the docstring for CourseGrader. """ if field_data_cache is None: with manual_transaction(): field_data_cache = field_data_cache_for_grading( course, student) if scores_client is None: scores_client = ScoresClient.from_field_data_cache( field_data_cache) # Dict of item_ids -> (earned, possible) point tuples. This *only* grabs # scores that were registered with the submissions API, which for the moment # means only openassessment (edx-ora2) submissions_scores = sub_api.get_scores( course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id)) max_scores_cache = MaxScoresCache.create_for_course(course) # For the moment, we have to get scorable_locations from field_data_cache # and not from scores_client, because scores_client is ignorant of things # in the submissions API. As a further refactoring step, submissions should # be hidden behind the ScoresClient. max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations) grading_context = course.grading_context raw_scores = [] totaled_scores = {} # This next complicated loop is just to collect the totaled_scores, which is # passed to the grader for section_format, sections in grading_context[ 'graded_sections'].iteritems(): format_scores = [] for section in sections: section_descriptor = section['section_descriptor'] section_name = section_descriptor.display_name_with_default # some problems have state that is updated independently of interaction # with the LMS, so they need to always be scored. (E.g. foldit., # combinedopenended) should_grade_section = any( descriptor.always_recalculate_grades for descriptor in section['xmoduledescriptors']) # If there are no problems that always have to be regraded, 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. if not should_grade_section: should_grade_section = any( descriptor.location.to_deprecated_string() in submissions_scores for descriptor in section['xmoduledescriptors']) if not should_grade_section: should_grade_section = any( descriptor.location in scores_client for descriptor in section['xmoduledescriptors']) # 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 = [] def create_module(descriptor): '''creates an XModule instance given a descriptor''' # TODO: We need the request to pass into here. If we could forego that, our arguments # would be simpler return get_module_for_descriptor(student, request, descriptor, field_data_cache, course.id, course=course) descendants = yield_dynamic_descriptor_descendants( section_descriptor, student.id, create_module) for module_descriptor in descendants: (correct, total) = get_score( student, module_descriptor, create_module, scores_client, submissions_scores, max_scores_cache, ) 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 = module_descriptor.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, module_descriptor.display_name_with_default, module_descriptor.location)) __, graded_total = aggregate_section_scores( scores, section_name, getattr(section_descriptor, 'weight', 1.0)) if keep_raw_scores: raw_scores += scores else: graded_total = WeightedScore( 0.0, 1.0, True, section_name, None, getattr(section_descriptor, 'weight', 1.0)) # 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. {}" .format(section_descriptor.location)) totaled_scores[section_format] = format_scores # Grading policy might be overriden by a CCX, need to reset it course.set_grading_policy(course.grading_policy) grade_summary = course.grader.grade( totaled_scores, generate_random_scores=settings.GENERATE_PROFILE_SCORES) # We round the grade here, to make sure that the grade is an whole percentage and # doesn't get displayed differently than it gets grades grade_summary['percent'] = round(grade_summary['percent'] * 100 + 0.05) / 100 letter_grade = grade_for_percentage(course.grade_cutoffs, grade_summary['percent'], grade_summary['sections_passed']) grade_summary['grade'] = letter_grade grade_summary[ 'totaled_scores'] = totaled_scores # make this available, eg for instructor download & debugging if keep_raw_scores: # way to get all RAW scores out to instructor # so grader can be double-checked grade_summary['raw_scores'] = raw_scores max_scores_cache.push_to_remote() return grade_summary
def progress_summary(student, request, course, field_data_cache=None, scores_client=None, grading_type='vertical'): """ 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. Arguments: student: A User object for the student to grade course: A Descriptor containing the course to grade If the student does not have access to load the course module, this function will return None. """ with manual_transaction(): if field_data_cache is None: field_data_cache = field_data_cache_for_grading( course, student) if scores_client is None: scores_client = ScoresClient.from_field_data_cache( field_data_cache) course_module = get_module_for_descriptor(student, request, course, field_data_cache, course.id, course=course) if not course_module: return None course_module = getattr(course_module, '_x_module', course_module) submissions_scores = sub_api.get_scores( course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id)) max_scores_cache = MaxScoresCache.create_for_course(course) # For the moment, we have to get scorable_locations from field_data_cache # and not from scores_client, because scores_client is ignorant of things # in the submissions API. As a further refactoring step, submissions should # be hidden behind the ScoresClient. max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations) blocks_stack = [course_module] blocks_dict = {} while blocks_stack: curr_block = blocks_stack.pop() with manual_transaction(): # Skip if the block is hidden if curr_block.hide_from_toc: continue key = unicode(curr_block.scope_ids.usage_id) children = curr_block.get_display_items( ) if curr_block.category != grading_type else [] block = { 'display_name': curr_block.display_name_with_default, 'block_type': curr_block.category, 'url_name': curr_block.url_name, 'children': [unicode(child.scope_ids.usage_id) for child in children], } if curr_block.category == grading_type: graded = curr_block.graded scores = [] module_creator = curr_block.xmodule_runtime.get_module for module_descriptor in yield_dynamic_descriptor_descendants( curr_block, student.id, module_creator): (correct, total) = get_score( student, module_descriptor, module_creator, scores_client, submissions_scores, max_scores_cache, ) if correct is None and total is None: continue scores.append( Score(correct, total, graded, module_descriptor.display_name_with_default, module_descriptor.location)) scores.reverse() total, _ = aggregate_scores( scores, curr_block.display_name_with_default) module_format = curr_block.format if curr_block.format is not None else '' block.update({ 'scores': scores, 'total': total, 'format': module_format, 'due': curr_block.due, 'graded': graded, }) blocks_dict[key] = block # Add this blocks children to the stack so that we can traverse them as well. blocks_stack.extend(children) max_scores_cache.push_to_remote() return { 'root': unicode(course.scope_ids.usage_id), 'blocks': blocks_dict, }
def course_data(request, course_id): """ Get course's data(title, short description), Total Points/Earned Points or 404 if there is no such course. Assumes the course_id is in a valid format. """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) with modulestore().bulk_operations(course_key): course = get_course_with_access(request.user, 'load', course_key, depth=None, check_if_enrolled=True) access_response = has_access(request.user, 'load', course, course_key) context={} if course.has_started(): staff_access = bool(has_access(request.user, 'staff', course)) student = request.user # NOTE: To make sure impersonation by instructor works, use # student instead of request.user in the rest of the function. # The pre-fetching of groups is done to make auth checks not require an # additional DB lookup (this kills the Progress page in particular). student = User.objects.prefetch_related("groups").get(id=student.id) with outer_atomic(): field_data_cache = grades.field_data_cache_for_grading(course, student) scores_client = ScoresClient.from_field_data_cache(field_data_cache) title = course.display_name_with_default loc = course.location.replace(category='about', name='short_description') about_module = get_module( request.user, request, loc, field_data_cache, log_if_not_found=False, wrap_xmodule_display=False, static_asset_path=course.static_asset_path, course=course ) short_description = about_module.render(STUDENT_VIEW).content courseware_summary = grades.progress_summary( student, request, course, field_data_cache=field_data_cache, scores_client=scores_client ) grade_summary = grades.grade( student, request, course, field_data_cache=field_data_cache, scores_client=scores_client ) total_points = 0 earned_points = 0 for chapter in courseware_summary: for section in chapter['sections']: total_points += section['section_total'].possible earned_points += section['section_total'].earned percentage_points = float(earned_points)*(100.0/float(total_points)) context = { "started": course.has_started(), "course_image": course_image_url(course), "total": total_points, "earned": earned_points, "percentage": percentage_points, 'title': title, 'short_description' : short_description, 'staff_access': staff_access, 'student': student.id, 'passed': is_course_passed(course, grade_summary), } else: context={ "started": course.has_started(), } return JsonResponse(context)
def progress_summary(student, request, course, field_data_cache=None, scores_client=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. Arguments: student: A User object for the student to grade course: A Descriptor containing the course to grade If the student does not have access to load the course module, this function will return None. """ with manual_transaction(): if field_data_cache is None: field_data_cache = field_data_cache_for_grading(course, student) if scores_client is None: scores_client = ScoresClient.from_field_data_cache(field_data_cache) course_module = get_module_for_descriptor( student, request, course, field_data_cache, course.id, course=course ) if not course_module: return None course_module = getattr(course_module, '_x_module', course_module) submissions_scores = sub_api.get_scores( course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id) ) max_scores_cache = MaxScoresCache.create_for_course(course) # For the moment, we have to get scorable_locations from field_data_cache # and not from scores_client, because scores_client is ignorant of things # in the submissions API. As a further refactoring step, submissions should # be hidden behind the ScoresClient. max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations) chapters = [] # Don't include chapters that aren't displayable (e.g. due to error) for chapter_module in course_module.get_display_items(): # Skip if the chapter is hidden if chapter_module.hide_from_toc: continue sections = [] for section_module in chapter_module.get_display_items(): # Skip if the section is hidden with manual_transaction(): if section_module.hide_from_toc: continue graded = section_module.graded scores = [] module_creator = section_module.xmodule_runtime.get_module for module_descriptor in yield_dynamic_descriptor_descendants( section_module, student.id, module_creator ): (correct, total) = get_score( student, module_descriptor, module_creator, scores_client, submissions_scores, max_scores_cache, ) if correct is None and total is None: continue scores.append( Score( correct, total, graded, module_descriptor.display_name_with_default, module_descriptor.location ) ) scores.reverse() section_total, _ = graders.aggregate_scores( scores, section_module.display_name_with_default) module_format = section_module.format if section_module.format is not None else '' sections.append({ 'display_name': section_module.display_name_with_default, 'url_name': section_module.url_name, 'scores': scores, 'section_total': section_total, 'format': module_format, 'due': section_module.due, 'graded': graded, }) chapters.append({ 'course': course.display_name_with_default, 'display_name': chapter_module.display_name_with_default, 'url_name': chapter_module.url_name, 'sections': sections }) max_scores_cache.push_to_remote() return chapters
def progress_summary(student, request, course, field_data_cache=None, scores_client=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. Arguments: student: A User object for the student to grade course: A Descriptor containing the course to grade If the student does not have access to load the course module, this function will return None. """ with manual_transaction(): if field_data_cache is None: field_data_cache = field_data_cache_for_grading( course, student) if scores_client is None: scores_client = ScoresClient.from_field_data_cache( field_data_cache) course_module = get_module_for_descriptor(student, request, course, field_data_cache, course.id, course=course) if not course_module: return None course_module = getattr(course_module, '_x_module', course_module) submissions_scores = sub_api.get_scores( course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id)) max_scores_cache = MaxScoresCache.create_for_course(course) # For the moment, we have to get scorable_locations from field_data_cache # and not from scores_client, because scores_client is ignorant of things # in the submissions API. As a further refactoring step, submissions should # be hidden behind the ScoresClient. max_scores_cache.fetch_from_remote(field_data_cache.scorable_locations) chapters = [] # Don't include chapters that aren't displayable (e.g. due to error) for chapter_module in course_module.get_display_items(): # Skip if the chapter is hidden if chapter_module.hide_from_toc: continue sections = [] for section_module in chapter_module.get_display_items(): # Skip if the section is hidden with manual_transaction(): if section_module.hide_from_toc: continue graded = section_module.graded scores = [] module_creator = section_module.xmodule_runtime.get_module for module_descriptor in yield_dynamic_descriptor_descendants( section_module, student.id, module_creator): (correct, total) = get_score( student, module_descriptor, module_creator, scores_client, submissions_scores, max_scores_cache, ) if correct is None and total is None: continue scores.append( Score(correct, total, graded, module_descriptor.display_name_with_default, module_descriptor.location)) scores.reverse() section_total, _ = graders.aggregate_scores( scores, section_module.display_name_with_default) module_format = section_module.format if section_module.format is not None else '' sections.append({ 'display_name': section_module.display_name_with_default, 'url_name': section_module.url_name, 'scores': scores, 'section_total': section_total, 'format': module_format, 'due': section_module.due, 'graded': graded, }) chapters.append({ 'course': course.display_name_with_default, 'display_name': chapter_module.display_name_with_default, 'url_name': chapter_module.url_name, 'sections': sections }) max_scores_cache.push_to_remote() return chapters