Commit 67ba3185 by Justin Riley

proctor reset improvements

* Update the filter on the list of assignments to reset to include
  assignments that have None for 'earned' or 'attempted'.
* Added --wipe-randomize-history flag to reset_attempts which blasts the
  randomize history for users that have seen/attempted all problems
  (should only be used for testing!)
* Added 'Wipe randomize history?' checkbox to proctor student admin form
* Moved cache/modulestore/signal configuration out of the reset_attempts
  and attempt_stats management command and into module_tree_reset module
* Use student.email instead of student.username + '@mit.edu'
parent b8a2cc44
...@@ -205,7 +205,8 @@ class ProctorModule(ProctorFields, XModule): ...@@ -205,7 +205,8 @@ class ProctorModule(ProctorFields, XModule):
# Proctor Student Admin URLs (STAFF ONLY) # Proctor Student Admin URLs (STAFF ONLY)
if dispatch == 'reset': if dispatch == 'reset':
username = data.get("username") username = data.get("username")
return self.reset(username) wipe_history = data.get("wipe_history") == "on"
return self.reset(username, wipe_history=wipe_history)
#if dispatch == 'status': #if dispatch == 'status':
#return self.status() #return self.status()
# if dispatch == 'grades': # if dispatch == 'grades':
...@@ -226,10 +227,11 @@ class ProctorModule(ProctorFields, XModule): ...@@ -226,10 +227,11 @@ class ProctorModule(ProctorFields, XModule):
def get_icon_class(self): def get_icon_class(self):
return self.child.get_icon_class() if self.child else 'other' return self.child.get_icon_class() if self.child else 'other'
def reset(self, username): def reset(self, username, wipe_history=False):
try: try:
pminfo = module_tree_reset.ProctorModuleInfo(self.runtime.course_id) pminfo = module_tree_reset.ProctorModuleInfo(self.runtime.course_id)
pminfo.get_assignments_attempted_and_failed(username, do_reset=True) pminfo.get_assignments_attempted_and_failed(
username, reset=True, wipe_randomize_history=wipe_history)
return self.status(username) return self.status(username)
except Exception as exc: except Exception as exc:
return json.dumps({"error": str(exc)}) return json.dumps({"error": str(exc)})
......
...@@ -11,30 +11,14 @@ ...@@ -11,30 +11,14 @@
# #
import csv import csv
import json import json
from optparse import make_option
from django.conf import settings from django.core.management.base import BaseCommand, CommandError, make_option
from django.dispatch import Signal
from django.core.cache import get_cache
from django.core.management.base import BaseCommand, CommandError
from student import roles from student import roles
from request_cache.middleware import RequestCache
from xmodule.modulestore.django import modulestore
from courseware.module_tree_reset import ProctorModuleInfo from courseware.module_tree_reset import ProctorModuleInfo
from courseware.models import StudentModule, StudentModuleHistory from courseware.models import StudentModule, StudentModuleHistory
CACHE = get_cache('mongo_metadata_inheritance')
for store_name in settings.MODULESTORE:
store = modulestore(store_name)
store.metadata_inheritance_cache_subsystem = CACHE
store.request_cache = RequestCache.get_request_cache()
modulestore_update_signal = Signal(
providing_args=['modulestore', 'course_id', 'location'])
store.modulestore_update_signal = modulestore_update_signal
class Stats(object): class Stats(object):
def __init__(self): def __init__(self):
self.nassigned = 0 self.nassigned = 0
......
#!/usr/bin/python #!/usr/bin/python
import csv import csv
from request_cache.middleware import RequestCache
from xmodule.modulestore.django import modulestore
from courseware.module_tree_reset import ProctorModuleInfo from courseware.module_tree_reset import ProctorModuleInfo
from django.conf import settings
from django.dispatch import Signal
from django.core.cache import get_cache
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError, make_option from django.core.management.base import BaseCommand, CommandError, make_option
CACHE = get_cache('mongo_metadata_inheritance')
for store_name in settings.MODULESTORE:
store = modulestore(store_name)
store.metadata_inheritance_cache_subsystem = CACHE
store.request_cache = RequestCache.get_request_cache()
modulestore_update_signal = Signal(
providing_args=['modulestore', 'course_id', 'location'])
store.modulestore_update_signal = modulestore_update_signal
class Command(BaseCommand): class Command(BaseCommand):
args = "<course_id>" args = "<course_id>"
help = """Reset exam attempts for 3.091 exam, Fall 2013. Records students \ help = """Reset exam attempts for 3.091 exam, Fall 2013. Records students \
...@@ -39,6 +24,11 @@ arguments.""" ...@@ -39,6 +24,11 @@ arguments."""
action='store', action='store',
default=None, default=None,
help='Save stats to csv file'), help='Save stats to csv file'),
make_option('--wipe-randomize-history',
dest='wipe_history',
action='store_true',
default=False,
help="Reset randomization xmodule's history tracking"),
) )
def handle(self, *args, **options): def handle(self, *args, **options):
...@@ -47,13 +37,15 @@ arguments.""" ...@@ -47,13 +37,15 @@ arguments."""
course_id = args[0] course_id = args[0]
dry_run = options['dry_run'] dry_run = options['dry_run']
csv_file = options['csv_output_filename'] csv_file = options['csv_output_filename']
wipe_history = options['wipe_history']
pminfo = ProctorModuleInfo(course_id) pminfo = ProctorModuleInfo(course_id)
students = User.objects.filter( students = User.objects.filter(
courseenrollment__course_id=pminfo.course.id).order_by('username') courseenrollment__course_id=pminfo.course.id).order_by('username')
failed = [] failed = []
for student in students: for student in students:
failed += pminfo.get_assignments_attempted_and_failed( failed += pminfo.get_assignments_attempted_and_failed(
student, do_reset=not dry_run) student, reset=not dry_run,
wipe_randomize_history=wipe_history)
if csv_file: if csv_file:
self.write_to_csv(csv_file, failed) self.write_to_csv(csv_file, failed)
......
...@@ -4,10 +4,15 @@ import datetime ...@@ -4,10 +4,15 @@ import datetime
from collections import OrderedDict from collections import OrderedDict
import pytz import pytz
from django.db import transaction
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings
from django.dispatch import Signal
from django.core.cache import get_cache
from xmodule.modulestore import Location from xmodule.modulestore import Location
from courseware.models import StudentModule from courseware.models import StudentModule
from request_cache.middleware import RequestCache
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from instructor.offline_gradecalc import student_grades from instructor.offline_gradecalc import student_grades
...@@ -15,6 +20,16 @@ from instructor.offline_gradecalc import student_grades ...@@ -15,6 +20,16 @@ from instructor.offline_gradecalc import student_grades
log = logging.getLogger("mitx.module_tree_reset") log = logging.getLogger("mitx.module_tree_reset")
CACHE = get_cache('mongo_metadata_inheritance')
for store_name in settings.MODULESTORE:
store = modulestore(store_name)
store.metadata_inheritance_cache_subsystem = CACHE
store.request_cache = RequestCache.get_request_cache()
modulestore_update_signal = Signal(
providing_args=['modulestore', 'course_id', 'location'])
store.modulestore_update_signal = modulestore_update_signal
class TreeNode(object): class TreeNode(object):
def __init__(self, course_id, module, level, student): def __init__(self, course_id, module, level, student):
self.course_id = course_id self.course_id = course_id
...@@ -67,7 +82,7 @@ class TreeNodeSet(list): ...@@ -67,7 +82,7 @@ class TreeNodeSet(list):
elif tn.module.category in ['problem', 'problemset']: elif tn.module.category in ['problem', 'problemset']:
self.pset.append(tn) self.pset.append(tn)
def reset_randomization(self): def reset_randomization(self, wipe_history=False):
""" """
Go through all <problem> and <randomize> modules in tree and reset Go through all <problem> and <randomize> modules in tree and reset
their StudentModule state to empty. their StudentModule state to empty.
...@@ -75,14 +90,13 @@ class TreeNodeSet(list): ...@@ -75,14 +90,13 @@ class TreeNodeSet(list):
msg = ("Resetting all <problem> and <randomize> in tree of parent " msg = ("Resetting all <problem> and <randomize> in tree of parent "
"module %s\n" % self.parent_module) "module %s\n" % self.parent_module)
for module in self.rset + self.pset: for module in self.rset + self.pset:
old_state = json.loads(module.smstate.state if module.smstate is
not None else '{}')
msg += " Resetting %s, old state=%s\n" % (module, old_state)
new_state = {}
if 'history' in old_state:
new_state['history'] = old_state['history']
if module.smstate is not None: if module.smstate is not None:
module.smstate.state = json.dumps(new_state) old_state = json.loads(module.smstate.state or '{}')
state = {}
msg += " Resetting %s, old state=%s\n" % (module, state)
if not wipe_history and 'history' in old_state:
state['history'] = old_state['history']
module.smstate.state = json.dumps(state)
module.smstate.grade = None module.smstate.grade = None
module.smstate.save() module.smstate.save()
return msg return msg
...@@ -101,6 +115,12 @@ class DummyRequest(object): ...@@ -101,6 +115,12 @@ class DummyRequest(object):
return False return False
class StateInfo(object):
def __init__(self):
self.state = '{}'
return
class ProctorModuleInfo(object): class ProctorModuleInfo(object):
def __init__(self, course_id): def __init__(self, course_id):
self.ms = modulestore() self.ms = modulestore()
...@@ -163,8 +183,7 @@ class ProctorModuleInfo(object): ...@@ -163,8 +183,7 @@ class ProctorModuleInfo(object):
gradeset = student_grades(student, request, self.course, gradeset = student_grades(student, request, self.course,
keep_raw_scores=False, use_offline=False) keep_raw_scores=False, use_offline=False)
except Exception: except Exception:
# log.exception("Failed to get grades for %s" % student) log.exception("Failed to get grades for %s" % student)
print("Failed to get grades for %s" % student)
gradeset = [] gradeset = []
self.gradeset = gradeset self.gradeset = gradeset
...@@ -182,17 +201,12 @@ class ProctorModuleInfo(object): ...@@ -182,17 +201,12 @@ class ProctorModuleInfo(object):
# for debugging; flushes db cache # for debugging; flushes db cache
if debug: if debug:
from django.db import transaction
try: try:
transaction.commit() transaction.commit()
except Exception: except Exception:
print "db cache flushed" log.debug("db cache flushed")
class StateInfo(object): # assume <proctor><problemset><randomized/></problemset></proctor>
def __init__(self):
self.state = '{}'
return
# assume <proctor><problemset><randomized/></problemset></prcotor>
# structure # structure
for rpmod in self.rpmods: for rpmod in self.rpmods:
try: try:
...@@ -218,7 +232,7 @@ class ProctorModuleInfo(object): ...@@ -218,7 +232,7 @@ class ProctorModuleInfo(object):
# module state # module state
try: try:
sm.choice = json.loads(sm.state)['choice'] sm.choice = json.loads(sm.state)['choice']
except Exception: except KeyError:
sm.choice = None sm.choice = None
if sm.choice is not None: if sm.choice is not None:
try: try:
...@@ -292,7 +306,7 @@ class ProctorModuleInfo(object): ...@@ -292,7 +306,7 @@ class ProctorModuleInfo(object):
ret['id'] = student.id ret['id'] = student.id
ret['name'] = student.profile.name ret['name'] = student.profile.name
# ret['username'] = student.username # ret['username'] = student.username
ret['email'] = '%s@mit.edu' % student.username ret['email'] = student.email
for stat in status['assignments']: for stat in status['assignments']:
if stat['attempted']: if stat['attempted']:
...@@ -313,18 +327,20 @@ class ProctorModuleInfo(object): ...@@ -313,18 +327,20 @@ class ProctorModuleInfo(object):
earned=assignment['earned'], earned=assignment['earned'],
possible=assignment['possible']) possible=assignment['possible'])
def get_assignments_attempted_and_failed(self, student, do_reset=False): def get_assignments_attempted_and_failed(self, student, reset=False,
wipe_randomize_history=False):
student = self._get_student_obj(student) student = self._get_student_obj(student)
status = self.get_student_status(student) status = self.get_student_status(student)
failed = [self._get_od_for_assignment(student, a) failed = [self._get_od_for_assignment(student, a)
for a in status['assignments'] if a['attempted'] and for a in status['assignments']
if a['earned'] is None or a['possible'] is None or
a['earned'] != a['possible']] a['earned'] != a['possible']]
for f in failed: for f in failed:
log.info( log.info(
"Student %s Assignment %s attempted '%s' but failed " "Student %s Assignment %s attempted '%s' but failed "
"(%s/%s)" % (student, f['assignment'], f['problem'], "(%s/%s)" % (student, f['assignment'], f['problem'],
f['earned'], f['possible'])) f['earned'], f['possible']))
if do_reset: if reset:
try: try:
log.info('resetting %s for student %s' % log.info('resetting %s for student %s' %
(f['assignment'], f['username'])) (f['assignment'], f['username']))
...@@ -336,7 +352,8 @@ class ProctorModuleInfo(object): ...@@ -336,7 +352,8 @@ class ProctorModuleInfo(object):
assi_url.url()) assi_url.url())
tnset = TreeNodeSet(self.course.id, pmod, self.ms, tnset = TreeNodeSet(self.course.id, pmod, self.ms,
student) student)
msg = tnset.reset_randomization() msg = tnset.reset_randomization(
wipe_history=wipe_randomize_history)
log.debug(str(msg)) log.debug(str(msg))
except Exception: except Exception:
log.exception("Failed to do reset of %s for %s" % log.exception("Failed to do reset of %s for %s" %
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
<form id="proctor_${element_id}_reset_form"> <form id="proctor_${element_id}_reset_form">
<label for="proctor_${element_id}_student_username">${_("User:")}</label> <label for="proctor_${element_id}_student_username">${_("User:")}</label>
<input name="username" id="proctor_${element_id}_student_username" type="text" placeholder=""/> <input name="username" id="proctor_${element_id}_student_username" type="text" placeholder=""/>
<input name="wipe_history" id="proctor_${element_id}_wipe_history" type="checkbox"/> Wipe randomize history?
<input name="location" type="hidden" id="proctor_${element_id}_reset_location" value="${location}"/> <input name="location" type="hidden" id="proctor_${element_id}_reset_location" value="${location}"/>
<div class="submit"> <div class="submit">
<button id="proctor_${element_id}_submit" name="submit" type="submit">${_("Reset Attempts")}</button> <button id="proctor_${element_id}_submit" name="submit" type="submit">${_("Reset Attempts")}</button>
......
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