Commit 9e58ef69 by Carson Gee

fix raw grade dump in standard instructor dashboard

Added test and cleaned up code formatting
Review based changes
Converting class method and attributes to private
parent e1afbe50
# -*- coding: utf-8 -*-
"""
Create course and answer a problem to test raw grade CSV
"""
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from courseware.tests.test_submitting_problems import TestSubmittingProblems
from student.roles import CourseStaffRole
class TestRawGradeCSV(TestSubmittingProblems):
"""
Tests around the instructor dashboard raw grade CSV
"""
def setUp(self):
"""
Set up a simple course for testing basic grading functionality.
"""
super(TestRawGradeCSV, self).setUp()
self.instructor = 'view2@test.com'
self.create_account('u2', self.instructor, self.password)
self.activate_user(self.instructor)
CourseStaffRole(self.course.location).add_users(User.objects.get(email=self.instructor))
self.logout()
self.login(self.instructor, self.password)
self.enroll(self.course)
# set up a simple course with four problems
self.homework = self.add_graded_section_to_course('homework', late=False, reset=False, showanswer=False)
self.add_dropdown_to_section(self.homework.location, 'p1', 1)
self.add_dropdown_to_section(self.homework.location, 'p2', 1)
self.add_dropdown_to_section(self.homework.location, 'p3', 1)
self.refresh_course()
def test_download_raw_grades_dump(self):
"""
Grab raw grade report and make sure all grades are reported.
"""
# Answer second problem correctly with 2nd user to expose bug
self.login(self.instructor, self.password)
resp = self.submit_question_answer('p2', {'2_1': 'Correct'})
self.assertEqual(resp.status_code, 200)
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
msg = "url = {0}\n".format(url)
response = self.client.post(url, {'action': 'Download CSV of all RAW grades'})
msg += "instructor dashboard download raw csv grades: response = '{0}'\n".format(response)
body = response.content.replace('\r', '')
msg += "body = '{0}'\n".format(body)
expected_csv = '''"ID","Username","Full Name","edX email","External email","p3","p2","p1"
"1","u1","username","view@test.com","","None","None","None"
"2","u2","username","view2@test.com","","0.0","1.0","0.0"
'''
self.assertEqual(body, expected_csv, msg)
"""
Instructor Views
"""
from contextlib import contextmanager
import csv
import json
import logging
......@@ -1154,6 +1155,74 @@ def remove_user_from_role(request, username_or_email, role, group_title, event_n
return '<font color="green">Removed {0} from {1}</font>'.format(user, group_title)
class GradeTable(object):
"""
Keep track of grades, by student, for all graded assignment
components. Each student's grades are stored in a list. The
index of this list specifies the assignment component. Not
all lists have the same length, because at the start of going
through the set of grades, it is unknown what assignment
compoments exist. This is because some students may not do
all the assignment components.
The student grades are then stored in a dict, with the student
id as the key.
"""
def __init__(self):
self.components = OrderedDict()
self.grades = {}
self._current_row = {}
def _add_grade_to_row(self, component, score):
"""Creates component if needed, and assigns score
Args:
component (str): Course component being graded
score (float): Score of student on component
Returns:
None
"""
component_index = self.components.setdefault(component, len(self.components))
self._current_row[component_index] = score
@contextmanager
def add_row(self, student_id):
"""Context management for a row of grades
Uses a new dictionary to get all grades of a specified student
and closes by adding that dict to the internal table.
Args:
student_id (str): Student id that is having grades set
"""
self._current_row = {}
yield self._add_grade_to_row
self.grades[student_id] = self._current_row
def get_grade(self, student_id):
"""Retrieves padded list of grades for specified student
Args:
student_id (str): Student ID for desired grades
Returns:
list: Ordered list of grades for student
"""
row = self.grades.get(student_id, [])
ncomp = len(self.components)
return [row.get(comp, None) for comp in range(ncomp)]
def get_graded_components(self):
"""
Return a list of components that have been
discovered so far.
"""
return self.components.keys()
def get_student_grade_summary_data(request, course, course_id, get_grades=True, get_raw_scores=False, use_offline=False):
'''
Return data arrays with student identity and grades for specified course.
......@@ -1178,20 +1247,12 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
).prefetch_related("groups").order_by('username')
header = [_('ID'), _('Username'), _('Full Name'), _('edX email'), _('External email')]
assignments = []
if get_grades and enrolled_students.count() > 0:
# just to construct the header
gradeset = student_grades(enrolled_students[0], request, course, keep_raw_scores=get_raw_scores, use_offline=use_offline)
# log.debug('student {0} gradeset {1}'.format(enrolled_students[0], gradeset))
if get_raw_scores:
assignments += [score.section for score in gradeset['raw_scores']]
else:
assignments += [x['label'] for x in gradeset['section_breakdown']]
header += assignments
datatable = {'header': header, 'assignments': assignments, 'students': enrolled_students}
datatable = {'header': header, 'students': enrolled_students}
data = []
gtab = GradeTable()
for student in enrolled_students:
datarow = [student.id, student.username, student.profile.name, student.email]
try:
......@@ -1202,15 +1263,31 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
if get_grades:
gradeset = student_grades(student, request, course, keep_raw_scores=get_raw_scores, use_offline=use_offline)
log.debug('student={0}, gradeset={1}'.format(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']
sgrades = [(getattr(score, 'earned', '') or score[0]) for score in gradeset['raw_scores']]
for score in gradeset['raw_scores']:
add_grade(score.section, getattr(score, 'earned', score[0]))
else:
sgrades = [x['percent'] for x in gradeset['section_breakdown']]
datarow += sgrades
student.grades = sgrades # store in student object
for grade_item in gradeset['section_breakdown']:
add_grade(grade_item['label'], grade_item['percent'])
student.grades = gtab.get_grade(student.id)
data.append(datarow)
# if getting grades, need to do a second pass, and add grades to each datarow;
# on the first pass we don't know all the graded components
if get_grades:
for datarow in data:
# get grades for student
sgrades = gtab.get_grade(datarow[0])
datarow += sgrades
# get graded components and add to table header
assignments = gtab.get_graded_components()
header += assignments
datatable['assignments'] = assignments
datatable['data'] = data
return datatable
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment