diff --git a/lms/djangoapps/instructor/management/__init__.py b/lms/djangoapps/instructor/management/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lms/djangoapps/instructor/management/commands/__init__.py b/lms/djangoapps/instructor/management/commands/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lms/djangoapps/instructor/management/commands/compute_grades.py b/lms/djangoapps/instructor/management/commands/compute_grades.py deleted file mode 100644 index c5bdbfe6cf..0000000000 --- a/lms/djangoapps/instructor/management/commands/compute_grades.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/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) diff --git a/lms/djangoapps/instructor/management/tests/__init__.py b/lms/djangoapps/instructor/management/tests/__init__.py deleted file mode 100644 index 8ab2057223..0000000000 --- a/lms/djangoapps/instructor/management/tests/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Tests for the instructor app management commands. -""" diff --git a/lms/djangoapps/instructor/offline_gradecalc.py b/lms/djangoapps/instructor/offline_gradecalc.py deleted file mode 100644 index fb7b1d31c6..0000000000 --- a/lms/djangoapps/instructor/offline_gradecalc.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -======== 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 diff --git a/lms/djangoapps/instructor/tests/test_offline_gradecalc.py b/lms/djangoapps/instructor/tests/test_offline_gradecalc.py deleted file mode 100644 index 313331eeb1..0000000000 --- a/lms/djangoapps/instructor/tests/test_offline_gradecalc.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -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)) diff --git a/lms/djangoapps/instructor/views/gradebook_api.py b/lms/djangoapps/instructor/views/gradebook_api.py index 220bfe4cad..843eeb4d10 100644 --- a/lms/djangoapps/instructor/views/gradebook_api.py +++ b/lms/djangoapps/instructor/views/gradebook_api.py @@ -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 ] diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 514d625711..0cce726355 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -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)]