Commit 8857b35b by Eric Fischer

Remove offline grade calc

This management command has been broken since before the django 1.8
upgrade, so I'm deleting it and all its tests, and updating a few
other files that used these methods but are easily refactored away.
parent fb10ab07
#!/usr/bin/python
"""
django management command: dump grades to csv files
for use by batch processes
"""
from instructor.offline_gradecalc import offline_grade_calculation
from courseware.courses import get_course_by_id
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "Compute grades for all students in a course, and store result in DB.\n"
help += "Usage: compute_grades course_id_or_dir \n"
help += " course_id_or_dir: either course_id or course_dir\n"
help += 'Example course_id: MITx/8.01rq_MW/Classical_Mechanics_Reading_Questions_Fall_2012_MW_Section'
def handle(self, *args, **options):
print "args = ", args
if len(args) > 0:
course_id = args[0]
else:
print self.help
return
course_key = None
# parse out the course id into a coursekey
try:
course_key = CourseKey.from_string(course_id)
# if it's not a new-style course key, parse it from an old-style
# course key
except InvalidKeyError:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
try:
_course = get_course_by_id(course_key)
except Exception as err:
print "-----------------------------------------------------------------------------"
print "Sorry, cannot find course with id {}".format(course_id)
print "Got exception {}".format(err)
print "Please provide a course ID or course data directory name, eg content-mit-801rq"
return
print "-----------------------------------------------------------------------------"
print "Computing grades for {}".format(course_id)
offline_grade_calculation(course_key)
"""
Tests for the instructor app management commands.
"""
"""
======== Offline calculation of grades =============================================================
Computing grades of a large number of students can take a long time. These routines allow grades to
be computed offline, by a batch process (eg cronjob).
The grades are stored in the OfflineComputedGrade table of the courseware model.
"""
import json
import time
from json import JSONEncoder
from courseware import models
from courseware.courses import get_course_by_id
from django.contrib.auth.models import User
from lms.djangoapps.grades import course_grades
from opaque_keys import OpaqueKey
from opaque_keys.edx.keys import UsageKey
from xmodule.graders import Score
from instructor.utils import DummyRequest
class MyEncoder(JSONEncoder):
""" JSON Encoder that can encode OpaqueKeys """
def default(self, obj): # pylint: disable=method-hidden
""" Encode an object that the default encoder hasn't been able to. """
if isinstance(obj, OpaqueKey):
return unicode(obj)
return JSONEncoder.default(self, obj)
def offline_grade_calculation(course_key):
'''
Compute grades for all students for a specified course, and save results to the DB.
'''
tstart = time.time()
enrolled_students = User.objects.filter(
courseenrollment__course_id=course_key,
courseenrollment__is_active=1
).prefetch_related("groups").order_by('username')
enc = MyEncoder()
print "{} enrolled students".format(len(enrolled_students))
course = get_course_by_id(course_key)
for student in enrolled_students:
request = DummyRequest()
request.user = student
request.session = {}
gradeset = course_grades.summary(student, course)
# Convert Score namedtuples to dicts:
totaled_scores = gradeset['totaled_scores']
for section in totaled_scores:
totaled_scores[section] = [score._asdict() for score in totaled_scores[section]]
gradeset['raw_scores'] = [score._asdict() for score in gradeset['raw_scores']]
# Encode as JSON and save:
gradeset_str = enc.encode(gradeset)
ocg, _created = models.OfflineComputedGrade.objects.get_or_create(user=student, course_id=course_key)
ocg.gradeset = gradeset_str
ocg.save()
print "%s done" % student # print statement used because this is run by a management command
tend = time.time()
dt = tend - tstart
ocgl = models.OfflineComputedGradeLog(course_id=course_key, seconds=dt, nstudents=len(enrolled_students))
ocgl.save()
print ocgl
print "All Done!"
def offline_grades_available(course_key):
'''
Returns False if no offline grades available for specified course.
Otherwise returns latest log field entry about the available pre-computed grades.
'''
ocgl = models.OfflineComputedGradeLog.objects.filter(course_id=course_key)
if not ocgl:
return False
return ocgl.latest('created')
def student_grades(student, request, course, use_offline=False): # pylint: disable=unused-argument
'''
This is the main interface to get grades. It has the same parameters as grades.grade, as well
as use_offline. If use_offline is True then this will look for an offline computed gradeset in the DB.
'''
if not use_offline:
return course_grades.summary(student, course)
try:
ocg = models.OfflineComputedGrade.objects.get(user=student, course_id=course.id)
except models.OfflineComputedGrade.DoesNotExist:
return dict(
raw_scores=[],
section_breakdown=[],
msg='Error: no offline gradeset available for {}, {}'.format(student, course.id)
)
gradeset = json.loads(ocg.gradeset)
# Convert score dicts back to Score tuples:
def score_from_dict(encoded):
""" Given a formerly JSON-encoded Score tuple, return the Score tuple """
if encoded['module_id']:
encoded['module_id'] = UsageKey.from_string(encoded['module_id'])
return Score(**encoded)
totaled_scores = gradeset['totaled_scores']
for section in totaled_scores:
totaled_scores[section] = [score_from_dict(score) for score in totaled_scores[section]]
gradeset['raw_scores'] = [score_from_dict(score) for score in gradeset['raw_scores']]
return gradeset
"""
Tests for offline_gradecalc.py
"""
import json
from mock import patch
from courseware.models import OfflineComputedGrade
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.graders import Score
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from ..offline_gradecalc import offline_grade_calculation, student_grades
def mock_grade(_student, course, **_kwargs):
""" Return some fake grade data to mock grades.grade() """
return {
'grade': u'Pass',
'totaled_scores': {
u'Homework': [
Score(earned=10.0, possible=10.0, graded=True, section=u'Subsection 1', module_id=None),
]
},
'percent': 0.85,
'raw_scores': [
Score(
earned=5.0, possible=5.0, graded=True, section=u'Numerical Input',
module_id=course.id.make_usage_key('problem', 'problem1'),
),
Score(
earned=5.0, possible=5.0, graded=True, section=u'Multiple Choice',
module_id=course.id.make_usage_key('problem', 'problem2'),
),
],
'section_breakdown': [
{'category': u'Homework', 'percent': 1.0, 'detail': u'Homework 1 - Test - 100% (10/10)', 'label': u'HW 01'},
{'category': u'Final Exam', 'prominent': True, 'percent': 0, 'detail': u'Final = 0%', 'label': u'Final'}
],
'grade_breakdown': [
{'category': u'Homework', 'percent': 0.85, 'detail': u'Homework = 85.00% of a possible 85.00%'},
{'category': u'Final Exam', 'percent': 0.0, 'detail': u'Final Exam = 0.00% of a possible 15.00%'}
]
}
class TestOfflineGradeCalc(ModuleStoreTestCase):
""" Test Offline Grade Calculation with some mocked grades """
def setUp(self):
super(TestOfflineGradeCalc, self).setUp()
with modulestore().default_store(ModuleStoreEnum.Type.split): # Test with split b/c old mongo keys are messy
self.course = CourseFactory.create()
self.user = UserFactory.create()
CourseEnrollment.enroll(self.user, self.course.id)
patcher = patch('lms.djangoapps.grades.course_grades.summary', new=mock_grade)
patcher.start()
self.addCleanup(patcher.stop)
def test_output(self):
offline_grades = OfflineComputedGrade.objects
self.assertEqual(offline_grades.filter(user=self.user, course_id=self.course.id).count(), 0)
offline_grade_calculation(self.course.id)
result = offline_grades.get(user=self.user, course_id=self.course.id)
decoded = json.loads(result.gradeset)
self.assertEqual(decoded['grade'], "Pass")
self.assertEqual(decoded['percent'], 0.85)
self.assertEqual(decoded['totaled_scores'], {
"Homework": [
{"earned": 10.0, "possible": 10.0, "graded": True, "section": "Subsection 1", "module_id": None}
]
})
self.assertEqual(decoded['raw_scores'], [
{
"earned": 5.0,
"possible": 5.0,
"graded": True,
"section": "Numerical Input",
"module_id": unicode(self.course.id.make_usage_key('problem', 'problem1')),
},
{
"earned": 5.0,
"possible": 5.0,
"graded": True,
"section": "Multiple Choice",
"module_id": unicode(self.course.id.make_usage_key('problem', 'problem2')),
}
])
self.assertEqual(decoded['section_breakdown'], [
{"category": "Homework", "percent": 1.0, "detail": "Homework 1 - Test - 100% (10/10)", "label": "HW 01"},
{"category": "Final Exam", "label": "Final", "percent": 0, "detail": "Final = 0%", "prominent": True}
])
self.assertEqual(decoded['grade_breakdown'], [
{"category": "Homework", "percent": 0.85, "detail": "Homework = 85.00% of a possible 85.00%"},
{"category": "Final Exam", "percent": 0.0, "detail": "Final Exam = 0.00% of a possible 15.00%"}
])
def test_student_grades(self):
""" Test that the data returned by student_grades() and grades.grade() match """
offline_grade_calculation(self.course.id)
with patch('lms.djangoapps.grades.course_grades.summary', side_effect=AssertionError('Should not re-grade')):
result = student_grades(self.user, None, self.course, use_offline=True)
self.assertEqual(result, mock_grade(self.user, self.course))
......@@ -13,8 +13,8 @@ from opaque_keys.edx.keys import CourseKey
from edxmako.shortcuts import render_to_response
from courseware.courses import get_course_with_access
from instructor.offline_gradecalc import student_grades
from instructor.views.api import require_level
from lms.djangoapps.grades import course_grades
from xmodule.modulestore.django import modulestore
......@@ -91,7 +91,7 @@ def get_grade_book_page(request, course, course_key):
'username': student.username,
'id': student.id,
'email': student.email,
'grade_summary': student_grades(student, request, course),
'grade_summary': course_grades.summary(student, course)
}
for student in enrolled_students
]
......
......@@ -437,7 +437,6 @@ def _section_course_info(course, access):
section_data['grade_cutoffs'] = reduce(advance, sorted_cutoffs, "")[:-2]
except Exception: # pylint: disable=broad-except
section_data['grade_cutoffs'] = "Not Available"
# section_data['offline_grades'] = offline_grades_available(course_key)
try:
section_data['course_errors'] = [(escape(a), '') for (a, _unused) in modulestore().get_course_errors(course.id)]
......
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