diff --git a/lms/djangoapps/courseware/management/commands/regrade_partial.py b/lms/djangoapps/courseware/management/commands/regrade_partial.py new file mode 100644 index 0000000000..7bfcb16913 --- /dev/null +++ b/lms/djangoapps/courseware/management/commands/regrade_partial.py @@ -0,0 +1,90 @@ +import json +import logging +from optparse import make_option + +from django.core.management.base import BaseCommand # , CommandError + +from courseware.models import StudentModule +from capa.correctmap import CorrectMap + +# +# This is aimed at fixing a temporary problem encountered where partial credit was awarded for +# code problems, but the resulting score (or grade) was mistakenly set to zero +# because of a bug in CorrectMap.get_npoints(). +# +# The fix here is to recalculate the score/grade based on the partial credit. +# To narrow down the set of problems that might need fixing, the StudentModule +# objects to be checked is filtered down to those: +# +# grade=0.0 (the grade must have been zeroed out) +# created < '2013-03-08 05:19:00' (the problem must have been answered before the fix was installed) +# modified > '2013-03-07 20:18:00' (the problem must have been visited after the bug was introduced) +# state like '%"npoints": 0.%' (the problem must have some form of partial credit). +# + +log = logging.getLogger(__name__) + +class Command(BaseCommand): + + num_visited = 0 + num_changed = 0 + + option_list = BaseCommand.option_list + ( + make_option('--save', + action='store_true', + dest='save_changes', + default=False, + help='Persist the changes that were encountered. If not set, no changes are saved.'), ) + + def fix_studentmodules(self, save_changes): + modules = StudentModule.objects.filter(# module_type='problem', + modified__gt='2013-03-07 20:18:00', + created__lt='2013-03-08 05:19:00', + state__contains='"npoints": 0.', + grade=0.0) + for module in modules: + self.fix_studentmodule(module, save_changes) + + def fix_studentmodule(self, module, save_changes): + module_state = module.state + if module_state is None: + log.info("No state found for {type} module {id} for student {student} in course {course_id}".format( + **{'type':module.module_type, 'id':module.module_state_key, 'student':module.student.username, 'course_id':module.course_id})) + return + + state_dict = json.loads(module_state) + self.num_visited += 1 + + correct_map = CorrectMap() + if 'correct_map' in state_dict: + correct_map.set_dict(state_dict['correct_map']) + + correct = 0 + for key in correct_map: + correct += correct_map.get_npoints(key) + + if module.grade == correct: + log.info("Grade matches for {type} module {id} for student {student} in course {course_id}".format( + **{'type':module.module_type, 'id':module.module_state_key, 'student':module.student.username, 'course_id':module.course_id})) + elif save_changes: + log.info("Grade changing from {0} to {1} for {type} module {id} for student {student} in course {course_id}".format( + module.grade, correct, + **{'type':module.module_type, 'id':module.module_state_key, 'student':module.student.username, 'course_id':module.course_id})) + module.grade = correct + module.save() + self.num_changed += 1 + else: + log.info("Grade would change from {0} to {1} for {type} module {id} for student {student} in course {course_id}".format( + module.grade, correct, + **{'type':module.module_type, 'id':module.module_state_key, 'student':module.student.username, 'course_id':module.course_id})) + self.num_changed += 1 + + def handle(self, **options): + save_changes = 'save_changes' in options and options['save_changes'] + + log.info("Starting run: save_changes = {0}".format(save_changes)) + + self.fix_studentmodules(save_changes) + + log.info("Finished run: updating {0} of {1} modules".format(self.num_changed, self.num_visited)) +