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):
# Proctor Student Admin URLs (STAFF ONLY)
if dispatch == 'reset':
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':
#return self.status()
# if dispatch == 'grades':
......@@ -226,10 +227,11 @@ class ProctorModule(ProctorFields, XModule):
def get_icon_class(self):
return self.child.get_icon_class() if self.child else 'other'
def reset(self, username):
def reset(self, username, wipe_history=False):
try:
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)
except Exception as exc:
return json.dumps({"error": str(exc)})
......
......@@ -11,30 +11,14 @@
#
import csv
import json
from optparse import make_option
from django.conf import settings
from django.dispatch import Signal
from django.core.cache import get_cache
from django.core.management.base import BaseCommand, CommandError
from django.core.management.base import BaseCommand, CommandError, make_option
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.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):
def __init__(self):
self.nassigned = 0
......
#!/usr/bin/python
import csv
from request_cache.middleware import RequestCache
from xmodule.modulestore.django import modulestore
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.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):
args = "<course_id>"
help = """Reset exam attempts for 3.091 exam, Fall 2013. Records students \
......@@ -39,6 +24,11 @@ arguments."""
action='store',
default=None,
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):
......@@ -47,13 +37,15 @@ arguments."""
course_id = args[0]
dry_run = options['dry_run']
csv_file = options['csv_output_filename']
wipe_history = options['wipe_history']
pminfo = ProctorModuleInfo(course_id)
students = User.objects.filter(
courseenrollment__course_id=pminfo.course.id).order_by('username')
failed = []
for student in students:
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:
self.write_to_csv(csv_file, failed)
......
......@@ -4,10 +4,15 @@ import datetime
from collections import OrderedDict
import pytz
from django.db import transaction
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 courseware.models import StudentModule
from request_cache.middleware import RequestCache
from xmodule.modulestore.django import modulestore
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")
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):
def __init__(self, course_id, module, level, student):
self.course_id = course_id
......@@ -67,7 +82,7 @@ class TreeNodeSet(list):
elif tn.module.category in ['problem', 'problemset']:
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
their StudentModule state to empty.
......@@ -75,14 +90,13 @@ class TreeNodeSet(list):
msg = ("Resetting all <problem> and <randomize> in tree of parent "
"module %s\n" % self.parent_module)
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:
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.save()
return msg
......@@ -101,6 +115,12 @@ class DummyRequest(object):
return False
class StateInfo(object):
def __init__(self):
self.state = '{}'
return
class ProctorModuleInfo(object):
def __init__(self, course_id):
self.ms = modulestore()
......@@ -163,8 +183,7 @@ class ProctorModuleInfo(object):
gradeset = student_grades(student, request, self.course,
keep_raw_scores=False, use_offline=False)
except Exception:
# log.exception("Failed to get grades for %s" % student)
print("Failed to get grades for %s" % student)
log.exception("Failed to get grades for %s" % student)
gradeset = []
self.gradeset = gradeset
......@@ -182,17 +201,12 @@ class ProctorModuleInfo(object):
# for debugging; flushes db cache
if debug:
from django.db import transaction
try:
transaction.commit()
except Exception:
print "db cache flushed"
log.debug("db cache flushed")
class StateInfo(object):
def __init__(self):
self.state = '{}'
return
# assume <proctor><problemset><randomized/></problemset></prcotor>
# assume <proctor><problemset><randomized/></problemset></proctor>
# structure
for rpmod in self.rpmods:
try:
......@@ -218,7 +232,7 @@ class ProctorModuleInfo(object):
# module state
try:
sm.choice = json.loads(sm.state)['choice']
except Exception:
except KeyError:
sm.choice = None
if sm.choice is not None:
try:
......@@ -292,7 +306,7 @@ class ProctorModuleInfo(object):
ret['id'] = student.id
ret['name'] = student.profile.name
# ret['username'] = student.username
ret['email'] = '%s@mit.edu' % student.username
ret['email'] = student.email
for stat in status['assignments']:
if stat['attempted']:
......@@ -313,18 +327,20 @@ class ProctorModuleInfo(object):
earned=assignment['earned'],
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)
status = self.get_student_status(student)
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']]
for f in failed:
log.info(
"Student %s Assignment %s attempted '%s' but failed "
"(%s/%s)" % (student, f['assignment'], f['problem'],
f['earned'], f['possible']))
if do_reset:
if reset:
try:
log.info('resetting %s for student %s' %
(f['assignment'], f['username']))
......@@ -336,7 +352,8 @@ class ProctorModuleInfo(object):
assi_url.url())
tnset = TreeNodeSet(self.course.id, pmod, self.ms,
student)
msg = tnset.reset_randomization()
msg = tnset.reset_randomization(
wipe_history=wipe_randomize_history)
log.debug(str(msg))
except Exception:
log.exception("Failed to do reset of %s for %s" %
......
......@@ -17,6 +17,7 @@
<form id="proctor_${element_id}_reset_form">
<label for="proctor_${element_id}_student_username">${_("User:")}</label>
<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}"/>
<div class="submit">
<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