Commit f318e563 by Justin Riley

latest changes from production

Refactored for latest edX changes
parent 3fe35770
import json
import logging
import random
import requests
from lxml import etree
......@@ -12,7 +11,8 @@ from django.contrib.auth.models import User
from xmodule.x_module import XModule
from xmodule.seq_module import SequenceDescriptor
from xblock.core import Integer, Scope, String
from xblock.fields import Scope, String, Boolean
from xblock.fragment import Fragment
log = logging.getLogger('mitx.' + __name__)
......@@ -43,14 +43,15 @@ class ProctorPanel(object):
self.user = User.objects.get(pk=user_id)
def is_released(self):
url = '{2}/cmd/status/{0}/{1}'.format(self.user_id, self.procset_name, self.ProctorPanelServer)
#url = '{2}/cmd/status/{0}/{1}'.format(self.user_id, self.procset_name, self.ProctorPanelServer)
url = '{1}/cmd/status/{0}'.format(self.user_id, self.ProctorPanelServer)
log.info('ProctorPanel url={0}'.format(url))
#ret = self.ses.post(url, data={'userid' : self.user_id, 'urlname': self.procset_name}, verify=False)
auth = (self.ProctorPanelInterface.get('username'), self.ProctorPanelInterface.get('password'))
ret = self.ses.get(url, verify=False, auth=auth)
ret = self.ses.get(url, verify=False, auth=auth, params={'problem': self.procset_name})
try:
retdat = json.loads(ret.content)
except Exception as err:
except Exception:
log.error('bad return from proctor panel: ret.content={0}'.format(ret.content))
retdat = {}
......@@ -60,7 +61,15 @@ class ProctorPanel(object):
class ProctorFields(object):
#display_name = String(
# display_name="Display Name",
# help="This name appears in the grades progress page",
# scope=Scope.settings,
# default="Proctored Module"
#)
procset_name = String(help="Name of this proctored set", scope=Scope.settings)
staff_release = Boolean(help="True if staff forced release independent of proctor panel",
default=False, scope=Scope.user_state)
class ProctorModule(ProctorFields, XModule):
......@@ -82,17 +91,21 @@ class ProctorModule(ProctorFields, XModule):
"""
js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'),
resource_string(__name__, 'js/src/conditional/display.coffee'),
resource_string(__name__, 'js/src/collapsible.coffee'),
]}
js = {
'coffee': [
resource_string(__name__, 'js/src/javascript_loader.coffee'),
resource_string(__name__, 'js/src/conditional/display.coffee')
],
'js': [
resource_string(__name__, 'js/src/collapsible.js')
],
}
js_module_name = "Conditional"
css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]}
def __init__(self, *args, **kwargs):
XModule.__init__(self, *args, **kwargs)
super(ProctorModule, self).__init__(*args, **kwargs)
# check proctor panel to see if this should be released
user_id = self.system.seed
self.pp = ProctorPanel(user_id, self.procset_name)
......@@ -103,7 +116,16 @@ class ProctorModule(ProctorFields, XModule):
log.info('Proctor module child={0}'.format(self.child))
log.info('Proctor module child display_name={0}'.format(self.child.display_name))
self.display_name = self.child.display_name
# TODO: This attr is read-only now - need to figure out if/why this is
# needed and find a fix if necessary (disabling doesnt appear to break
# anything)
#self.display_name = self.child.display_name
def is_released(self):
if self.staff_release:
return True
return self.pp.is_released()
def get_child_descriptors(self):
......@@ -114,16 +136,19 @@ class ProctorModule(ProctorFields, XModule):
def not_released_html(self):
return self.system.render_template('proctor_release.html', {
return Fragment(self.system.render_template('proctor_release.html', {
'element_id': self.location.html_id(),
'id': self.id,
'name': self.display_name or self.procset_name,
'pp': self.pp,
})
'location': self.location,
'ajax_url': self.system.ajax_url,
'is_staff': self.system.user_is_staff,
}))
def get_html(self):
if not self.pp.is_released(): # check for release each time we do get_html()
def student_view(self, context):
if not self.is_released(): # check for release each time we do get_html()
log.info('is_released False')
return self.not_released_html()
# return "<div>%s not yet released</div>" % self.display_name
......@@ -131,21 +156,38 @@ class ProctorModule(ProctorFields, XModule):
log.info('is_released True')
# for sequential module, just return HTML (no ajax container)
if self.child.category in ['sequential', 'videosequence', 'problemset']:
return self.child.get_html()
if self.child.category in ['sequential', 'videosequence', 'problemset', 'randomize']:
html = self.child.render('student_view', context)
if self.staff_release:
dishtml = self.system.render_template('proctor_disable.html', {
'element_id': self.location.html_id(),
'is_staff': self.system.user_is_staff,
'ajax_url': self.system.ajax_url,
})
html.content = dishtml + html.content
return html
# return ajax container, so that we can dynamically check for is_released changing
return self.system.render_template('conditional_ajax.html', {
return Fragment(self.system.render_template('conditional_ajax.html', {
'element_id': self.location.html_id(),
'id': self.id,
'ajax_url': self.system.ajax_url,
'depends': '',
})
}))
def handle_ajax(self, _dispatch, _data):
if not self.pp.is_released(): # check for release each time we do get_html()
if self.system.user_is_staff and _dispatch=='release':
self.staff_release = True
# return '<html><head><META HTTP-EQUIV="refresh" CONTENT="15"></head><body>Release successful</body></html>'
return json.dumps({'html': 'staff_release successful'})
if self.system.user_is_staff and _dispatch=='disable':
self.staff_release = False
return json.dumps({'html': 'staff_disable successful'})
# return '<html><head><META HTTP-EQUIV="refresh" CONTENT="15"></head><body>Disable successful</body></html>'
if not self.is_released(): # check for release each time we do get_html()
log.info('is_released False')
# html = "<div>%s not yet released</div>" % self.display_name
html = self.not_released_html()
......@@ -171,6 +213,5 @@ class ProctorDescriptor(ProctorFields, SequenceDescriptor):
xml_object = etree.Element('proctor')
for child in self.get_children():
xml_object.append(
etree.fromstring(child.export_to_xml(resource_fs)))
self.runtime.add_block_as_child_node(child, xml_object)
return xml_object
import json
import logging
import random
......@@ -53,16 +54,20 @@ class RandomizeModule(RandomizeFields, XModule):
if self.choice is None:
# choose one based on the system seed, or randomly if that's not available
if num_choices > 0:
if self.system.seed is not None:
if self.system.seed is not None and 'use_randrange' not in (self.descriptor.xml_attributes or []):
self.choice = self.system.seed % num_choices
log.debug('using seed for %s choice=%s' % (str(self.location), self.choice))
else:
self.choice = random.randrange(0, num_choices)
log.debug('using randrange for %s' % str(self.location))
else:
log.debug('error in randomize: num_choices = %s' % num_choices)
if self.choice is not None:
self.child_descriptor = self.descriptor.get_children()[self.choice]
# Now get_children() should return a list with one element
log.debug("children of randomize module (should be only 1): %s",
self.get_children())
log.debug("choice=%s in %s, children of randomize module (should be only 1): %s",
self.choice, str(self.location), self.get_children())
self.child = self.get_children()[0]
else:
self.child_descriptor = None
......@@ -83,8 +88,32 @@ class RandomizeModule(RandomizeFields, XModule):
# raise error instead? In fact, could complain on descriptor load...
return Fragment(content=u"<div>Nothing to randomize between</div>")
if self.system.user_is_staff:
dishtml = self.system.render_template('randomize_control.html', {
'element_id': self.location.html_id(),
'is_staff': self.system.user_is_staff,
'ajax_url': self.system.ajax_url,
'choice': self.choice,
'num_choices': len(self.descriptor.get_children()),
})
# html = '<html><p>Welcome, staff. Randomize loc=%s ; Choice=%s</p><br/><hr/></br/>' % (str(self.location), self.choice)
child_html = self.child.render('student_view', context)
return Fragment(u"<html>" + dishtml + child_html.content + u"</html>")
return self.child.render('student_view', context)
def handle_ajax(self, dispatch, data):
if dispatch=='next':
self.choice = self.choice + 1
elif dispatch=='jump':
log.debug('jump, data=%s' % data)
self.choice = int(data['choice'])
num_choices = len(self.descriptor.get_children())
if self.choice >= num_choices:
self.choice = 0
result = {'ret': "choice=%s" % self.choice}
return json.dumps(result)
def get_icon_class(self):
return self.child.get_icon_class() if self.child else 'other'
......
#!/usr/bin/python
#
# Largely for 3.091-exam.
#
# Compute number of times a given problem has been attempted by a student, including StudentModuleHistory.
# Do this by walking through the course tree. For every assessment problem, look up all matching
# StudehtModuleHistory items. Count number of attempts passed, and number failed. Remove staff data.
#
# Output table with: problem url_id, problem name, number students assigned, number attempts failed, number attempts succeeded
#
from courseware.module_tree_reset import *
from courseware.access import get_access_group_name
from django.core.management.base import BaseCommand
import json
import csv
#-----------------------------------------------------------------------------
from django.conf import settings
from xmodule.modulestore.django import modulestore
from django.dispatch import Signal
from request_cache.middleware import RequestCache
from django.core.cache import get_cache
if True:
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
#-----------------------------------------------------------------------------
def ComputeStats():
pminfo = ProctorModuleInfo()
# get list of all problems
all_problems = []
stats = []
self = pminfo
if True:
for rpmod in self.rpmods:
assignment_set_name = rpmod.ra_ps.display_name
for ploc in rpmod.ra_rand.children:
problem = self.ms.get_item(ploc)
problem.assignment_set_name = assignment_set_name
all_problems.append(problem)
staffgroup = get_access_group_name(self.course, 'staff')
cnt = 0
class Stats(object):
def __init__(self):
self.nassigned = 0
self.nattempts = 0
self.npassed = 0
for problem in all_problems:
print problem.id
stat = Stats()
smset0 = StudentModule.objects.filter(module_state_key=problem.id, student__is_staff=False)
smset = smset0.exclude(student__groups__name=staffgroup)
def passed(state):
if 'correct_map' not in state:
return False
if not state['correct_map']: # correct_map = {}
return False
return all([x['correctness']=='correct' for x in state.get('correct_map').values()]) # must be all correct to pass
def update_stats(sm, stat, history=False):
if sm.grade is None:
return
state = json.loads(sm.state)
if not 'attempts' in state:
return
if not state.get('done', False):
return "notdone"
if not history:
stat.nattempts += state['attempts']
if passed(state):
stat.npassed += 1
return "passed"
return "attempted"
for sm in smset:
stat.nassigned += 1
ret = update_stats(sm, stat)
if ret in ['passed', 'attempted']:
continue
smhset = StudentModuleHistory.objects.filter(student_module=sm)
states = [ json.loads(smh.state) for smh in smhset ]
okset = [ passed(x) for x in states ]
attempts = [ x.get('attempts', 0) for x in states]
stat.nattempts += max(attempts)
if any(okset):
stat.npassed += 1
#print " assigned=%d, attempts=%d, passed=%d" % (nassigned, nattempts, npassed)
stats.append(dict(problem_id=problem.id,
pset=problem.assignment_set_name,
problem_name=problem.display_name,
due=str(problem.due),
max_attempts=problem.max_attempts,
assigned=stat.nassigned,
attempts=stat.nattempts,
passed=stat.npassed,
))
cnt += 1
#if cnt>5:
# break
if True:
dddir = settings.MITX_FEATURES.get('DOWNLOAD_DATA_DIR','')
fndir = dddir / (self.course.id.replace('/','__'))
dt = datetime.datetime.now().strftime('%Y-%m-%d-%H%M')
fn = fndir / "problem-stats-%s-%s.csv" % (self.course.id.split('/')[1], dt)
print "Saving data to %s" % fn
# fn = "stats.csv"
fieldnames = ['problem_id', 'pset', 'problem_name', 'due', 'max_attempts', 'assigned', 'attempts', 'passed']
fp = open(fn,'w')
writer = csv.DictWriter(fp, fieldnames, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL)
writer.writeheader()
for row in stats:
try:
writer.writerow(row)
except Exception as err:
print "Oops, failed to write %s, error=%s" % (row, err)
fp.close()
#-----------------------------------------------------------------------------
class Command(BaseCommand):
help = """Generate CSV file with problem attempts statistics;
CSV file columns include problem id, assigned, max_attempts, attempts, passed
for every problem in the course. Arguments: None. Works only on 3.091-exam"""
def handle(self, *args, **options):
ComputeStats()
#!/usr/bin/python
#
# 19-Sep-13 ichuang@mit.edu
import csv
from courseware.module_tree_reset import *
from django.core.management.base import BaseCommand
#-----------------------------------------------------------------------------
from django.conf import settings
from xmodule.modulestore.django import modulestore
from django.dispatch import Signal
from request_cache.middleware import RequestCache
from django.core.cache import get_cache
if True:
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):
help = """Reset exam attempts for 3.091 exam, Fall 2013.
Records students and problems which were reset.
Give filename as argument. Output is CSV file."""
def handle(self, *args, **options):
# fn = 'reset_3091exam.csv'
fn = args[0]
pminfo = ProctorModuleInfo()
students = User.objects.filter(courseenrollment__course_id=pminfo.course.id).order_by('username')
# students = User.objects.filter(username='ichuang')
data = []
# write out to csv file
fieldnames = ['id', 'name', 'username', 'assignment', 'problem', 'date', 'earned', 'possible']
fp = open(fn,'w')
csvf = csv.DictWriter(fp, fieldnames, dialect="excel", quotechar='"', quoting=csv.QUOTE_ALL)
csvf.writeheader()
cnt = 0
for student in students:
dat = pminfo.get_assignments_attempted_and_failed(student, do_reset=True)
data += dat
for row in dat:
csvf.writerow(row)
fp.flush()
cnt += 1
#if cnt>3:
# break
fp.close()
# print data
<p>Status:</p>
<pre>
${status}
</pre>
<p>Msg:</p>
<pre>
${msg}
</pre>
% if is_staff:
<div id="proctor_${element_id}_disable">
<p>
<input id="proctor_disable" type="button" value="This is a proctored module, released on staff override. Disable access"/></p>
</div>
<hr/>
% endif
<script type="text/javascript">
do_disable = function(){
$.get( "${ajax_url}/disable", function( data ) {
$( "#proctor_disable" ).html( data );
alert( "disable successful" );
location.reload();
});
}
$('#proctor_disable').click(do_disable);
</script>
......@@ -7,12 +7,36 @@
<div id="proctor_stat_${element_id}"></div>
% if is_staff:
<p><input id="proctor_release" type="button" value="Staff override: Release Module for myself"/></p>
% endif
</div>
% if is_staff:
<script type="text/javascript">
do_release = function(){
$.get( "${ajax_url}/release", function( data ) {
$( "#proctor_release" ).html( data );
procrel.set_skiperr();
alert( "release successful" );
location.reload();
});
}
$('#proctor_release').click(do_release);
</script>
% endif
<script type="text/javascript">
procrel = (function(){
var el = $('#proctor_${element_id}');
var skiperr = false;
var set_skiperr = function(){ skiperr = true; }
var mkurl = function(cmd) {
ps = encodeURIComponent("${pp.procset_name}");
return "${pp.ProctorPanelServer}/cmd/" + cmd + "/${pp.user_id}/" + ps;
......@@ -38,7 +62,9 @@ procrel = (function(){
success: gfun,
dataType: "json",
error: function(xhr, status, error) {
alert('Error: cannot connect to server' + status + "error:" + error);
if (!skiperr){
alert('Error: cannot connect to server' + status + "error:" + error);
}
}
});
}
......@@ -73,7 +99,10 @@ procrel = (function(){
el.click(make_request);
return { "check": check_access, "make": make_request, "mkurl": mkurl, "do_pp_get": do_pp_get };
return { "check": check_access, "make": make_request,
"mkurl": mkurl, "do_pp_get": do_pp_get,
"set_skiperr": set_skiperr
};
}() );
</script>
\ No newline at end of file
</script>
% if is_staff:
<div id="randomize_${element_id}_control">
<p>
<input id="randomize_next" type="button" value="This is randomized module ${choice} of ${num_choices}: Go to next"/>
or jump to:
<select id="randomize_jump">
% for kchoice in range(num_choices):
<% selected = "selected" if kchoice==choice else "" %>
<option value="${kchoice}" ${selected}>${kchoice}</option>
% endfor
</select>
</p>
</div>
<hr/>
<script type="text/javascript">
do_next = function(){
$.get( "${ajax_url}/next", function( data ) {
$( "#randomize_next" ).html( data );
alert( "next successful" );
location.reload();
});
}
$('#randomize_next').click(do_next);
rand_jump = function(){
var choice = $('#randomize_jump option:selected').text();
$.post( "${ajax_url}/jump", { 'choice': choice},
function( data ) {
$( "#randomize_next" ).html( data );
alert( "jump to " + choice + " successful" );
location.reload();
});
}
$('#randomize_jump').change(rand_jump);
</script>
% endif
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