diff --git a/AUTHORS b/AUTHORS index 0c07f8fcd3..28ee38920c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -253,4 +253,5 @@ Justin Abrahms Arbab Nazar Douglas Hall Awais Jibran -Muhammad Rehan \ No newline at end of file +Muhammad Rehan +Shawn Milochik diff --git a/lms/djangoapps/instructor/tests/test_legacy_raw_download_csv.py b/lms/djangoapps/instructor/tests/test_legacy_raw_download_csv.py index a39e7356ec..24adeeec8a 100644 --- a/lms/djangoapps/instructor/tests/test_legacy_raw_download_csv.py +++ b/lms/djangoapps/instructor/tests/test_legacy_raw_download_csv.py @@ -69,7 +69,7 @@ class TestRawGradeCSV(TestSubmittingProblems): def get_expected_grade_data( self, get_grades=True, get_raw_scores=False, - use_offline=False): + use_offline=False, get_score_max=False): """ Return expected results from the get_student_grade_summary_data call with any options selected. @@ -79,6 +79,11 @@ class TestRawGradeCSV(TestSubmittingProblems): get_student_grade_summary_data for this function to be accurate. If kwargs are added or removed, or the functionality triggered by them changes, this function must be updated to match. + + If get_score_max is True, instead of a single score between 0 and 1, + the actual score and total possible are returned. For example, if the + student got one out of two possible points, the values (1, 2) will be + returned instead of 0.5. """ expected_data = { 'students': [self.student_user, self.student_user2], @@ -155,6 +160,10 @@ class TestRawGradeCSV(TestSubmittingProblems): u'ID', u'Username', u'Full Name', u'edX email', u'External email', u'p3', u'p2', u'p1' ] + # Strip out the single-value float scores and replace them + # with two-tuples of actual and possible scores (see docstring). + if get_score_max: + expected_data["data"][-1][-3:] = (0.0, 1), (1.0, 1.0), (0.0, 1) return expected_data @@ -197,7 +206,7 @@ class TestRawGradeCSV(TestSubmittingProblems): request, self.course, get_grades=False ) expected_data = self.get_expected_grade_data(get_grades=False) - # if get_grades == False, get_expected_grade_data does not + # if get_grades is False, get_expected_grade_data does not # add an "assignments" key. self.assertNotIn("assignments", expected_data) self.compare_data(data, expected_data) @@ -228,6 +237,22 @@ class TestRawGradeCSV(TestSubmittingProblems): ) self.compare_data(data, expected_data) + def test_grade_summary_data_get_score_max(self): + """ + Test grade summary data report generation with get_score_max set + to True (also requires get_raw_scores to be True). + """ + request = DummyRequest() + self.answer_question() + data = get_student_grade_summary_data( + request, self.course, use_offline=True, get_raw_scores=True, + get_score_max=True, + ) + expected_data = self.get_expected_grade_data( + use_offline=True, get_raw_scores=True, get_score_max=True, + ) + self.compare_data(data, expected_data) + def compare_data(self, data, expected_data): """ Compare the output of the get_student_grade_summary_data diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py index ac9e07261c..f93dfd06c6 100644 --- a/lms/djangoapps/instructor/views/legacy.py +++ b/lms/djangoapps/instructor/views/legacy.py @@ -631,17 +631,21 @@ class GradeTable(object): self.grades = {} self._current_row = {} - def _add_grade_to_row(self, component, score): + def _add_grade_to_row(self, component, score, possible=None): """Creates component if needed, and assigns score Args: component (str): Course component being graded score (float): Score of student on component + possible (float): Max possible score for the component Returns: None """ component_index = self.components.setdefault(component, len(self.components)) + if possible is not None: + # send a tuple instead of a single value + score = (score, possible) self._current_row[component_index] = score @contextmanager @@ -681,7 +685,10 @@ class GradeTable(object): return self.components.keys() -def get_student_grade_summary_data(request, course, get_grades=True, get_raw_scores=False, use_offline=False): +def get_student_grade_summary_data( + request, course, get_grades=True, get_raw_scores=False, + use_offline=False, get_score_max=False +): """ Return data arrays with student identity and grades for specified course. @@ -697,6 +704,11 @@ def get_student_grade_summary_data(request, course, get_grades=True, get_raw_sco data = list (one per student) of lists of data corresponding to the fields If get_raw_scores=True, then instead of grade summaries, the raw grades for all graded modules are returned. + + If get_score_max is True, two values will be returned for each grade -- the + total number of points earned and the total number of points possible. For + example, if two points are possible and one is earned, (1, 2) will be + returned instead of 0.5 (the default). """ course_key = course.id enrolled_students = User.objects.filter( @@ -723,9 +735,18 @@ def get_student_grade_summary_data(request, course, get_grades=True, get_raw_sco log.debug(u'student=%s, gradeset=%s', student, gradeset) with gtab.add_row(student.id) as add_grade: if get_raw_scores: - # TODO (ichuang) encode Score as dict instead of as list, so score[0] -> score['earned'] + # The following code calls add_grade, which is an alias + # for the add_row method on the GradeTable class. This adds + # a grade for each assignment. Depending on whether + # get_score_max is True, it will return either a single + # value as a float between 0 and 1, or a two-tuple + # containing the earned score and possible score for + # the assignment (see docstring). for score in gradeset['raw_scores']: - add_grade(score.section, getattr(score, 'earned', score[0])) + if get_score_max is True: + add_grade(score.section, score.earned, score.possible) + else: + add_grade(score.section, score.earned) else: for grade_item in gradeset['section_breakdown']: add_grade(grade_item['label'], grade_item['percent'])