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
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# -*- 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)
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
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))
|
||||
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']]
|
||||
else:
|
||||
sgrades = [x['percent'] for x in gradeset['section_breakdown']]
|
||||
datarow += sgrades
|
||||
student.grades = sgrades # store in student object
|
||||
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']
|
||||
for score in gradeset['raw_scores']:
|
||||
add_grade(score.section, getattr(score, 'earned', score[0]))
|
||||
else:
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user