Commit f318e563 by Justin Riley

latest changes from production

Refactored for latest edX changes
parent 3fe35770
import json import json
import logging import logging
import random
import requests import requests
from lxml import etree from lxml import etree
...@@ -12,7 +11,8 @@ from django.contrib.auth.models import User ...@@ -12,7 +11,8 @@ from django.contrib.auth.models import User
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.seq_module import SequenceDescriptor 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__) log = logging.getLogger('mitx.' + __name__)
...@@ -43,14 +43,15 @@ class ProctorPanel(object): ...@@ -43,14 +43,15 @@ class ProctorPanel(object):
self.user = User.objects.get(pk=user_id) self.user = User.objects.get(pk=user_id)
def is_released(self): 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)) log.info('ProctorPanel url={0}'.format(url))
#ret = self.ses.post(url, data={'userid' : self.user_id, 'urlname': self.procset_name}, verify=False) #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')) 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: try:
retdat = json.loads(ret.content) retdat = json.loads(ret.content)
except Exception as err: except Exception:
log.error('bad return from proctor panel: ret.content={0}'.format(ret.content)) log.error('bad return from proctor panel: ret.content={0}'.format(ret.content))
retdat = {} retdat = {}
...@@ -60,7 +61,15 @@ class ProctorPanel(object): ...@@ -60,7 +61,15 @@ class ProctorPanel(object):
class ProctorFields(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) 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): class ProctorModule(ProctorFields, XModule):
...@@ -82,17 +91,21 @@ class ProctorModule(ProctorFields, XModule): ...@@ -82,17 +91,21 @@ class ProctorModule(ProctorFields, XModule):
""" """
js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'), js = {
resource_string(__name__, 'js/src/conditional/display.coffee'), 'coffee': [
resource_string(__name__, 'js/src/collapsible.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" js_module_name = "Conditional"
css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]} css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]}
def __init__(self, *args, **kwargs): 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 # check proctor panel to see if this should be released
user_id = self.system.seed user_id = self.system.seed
self.pp = ProctorPanel(user_id, self.procset_name) self.pp = ProctorPanel(user_id, self.procset_name)
...@@ -103,7 +116,16 @@ class ProctorModule(ProctorFields, XModule): ...@@ -103,7 +116,16 @@ class ProctorModule(ProctorFields, XModule):
log.info('Proctor module child={0}'.format(self.child)) log.info('Proctor module child={0}'.format(self.child))
log.info('Proctor module child display_name={0}'.format(self.child.display_name)) 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): def get_child_descriptors(self):
...@@ -114,16 +136,19 @@ class ProctorModule(ProctorFields, XModule): ...@@ -114,16 +136,19 @@ class ProctorModule(ProctorFields, XModule):
def not_released_html(self): 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(), 'element_id': self.location.html_id(),
'id': self.id, 'id': self.id,
'name': self.display_name or self.procset_name, 'name': self.display_name or self.procset_name,
'pp': self.pp, 'pp': self.pp,
}) 'location': self.location,
'ajax_url': self.system.ajax_url,
'is_staff': self.system.user_is_staff,
}))
def get_html(self): def student_view(self, context):
if not self.pp.is_released(): # check for release each time we do get_html() if not self.is_released(): # check for release each time we do get_html()
log.info('is_released False') log.info('is_released False')
return self.not_released_html() return self.not_released_html()
# return "<div>%s not yet released</div>" % self.display_name # return "<div>%s not yet released</div>" % self.display_name
...@@ -131,21 +156,38 @@ class ProctorModule(ProctorFields, XModule): ...@@ -131,21 +156,38 @@ class ProctorModule(ProctorFields, XModule):
log.info('is_released True') log.info('is_released True')
# for sequential module, just return HTML (no ajax container) # for sequential module, just return HTML (no ajax container)
if self.child.category in ['sequential', 'videosequence', 'problemset']: if self.child.category in ['sequential', 'videosequence', 'problemset', 'randomize']:
return self.child.get_html() 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 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(), 'element_id': self.location.html_id(),
'id': self.id, 'id': self.id,
'ajax_url': self.system.ajax_url, 'ajax_url': self.system.ajax_url,
'depends': '', 'depends': '',
}) }))
def handle_ajax(self, _dispatch, _data): 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') log.info('is_released False')
# html = "<div>%s not yet released</div>" % self.display_name # html = "<div>%s not yet released</div>" % self.display_name
html = self.not_released_html() html = self.not_released_html()
...@@ -171,6 +213,5 @@ class ProctorDescriptor(ProctorFields, SequenceDescriptor): ...@@ -171,6 +213,5 @@ class ProctorDescriptor(ProctorFields, SequenceDescriptor):
xml_object = etree.Element('proctor') xml_object = etree.Element('proctor')
for child in self.get_children(): for child in self.get_children():
xml_object.append( self.runtime.add_block_as_child_node(child, xml_object)
etree.fromstring(child.export_to_xml(resource_fs)))
return xml_object return xml_object
import json
import logging import logging
import random import random
...@@ -53,16 +54,20 @@ class RandomizeModule(RandomizeFields, XModule): ...@@ -53,16 +54,20 @@ class RandomizeModule(RandomizeFields, XModule):
if self.choice is None: if self.choice is None:
# choose one based on the system seed, or randomly if that's not available # choose one based on the system seed, or randomly if that's not available
if num_choices > 0: 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 self.choice = self.system.seed % num_choices
log.debug('using seed for %s choice=%s' % (str(self.location), self.choice))
else: else:
self.choice = random.randrange(0, num_choices) 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: if self.choice is not None:
self.child_descriptor = self.descriptor.get_children()[self.choice] self.child_descriptor = self.descriptor.get_children()[self.choice]
# Now get_children() should return a list with one element # Now get_children() should return a list with one element
log.debug("children of randomize module (should be only 1): %s", log.debug("choice=%s in %s, children of randomize module (should be only 1): %s",
self.get_children()) self.choice, str(self.location), self.get_children())
self.child = self.get_children()[0] self.child = self.get_children()[0]
else: else:
self.child_descriptor = None self.child_descriptor = None
...@@ -83,8 +88,32 @@ class RandomizeModule(RandomizeFields, XModule): ...@@ -83,8 +88,32 @@ class RandomizeModule(RandomizeFields, XModule):
# raise error instead? In fact, could complain on descriptor load... # raise error instead? In fact, could complain on descriptor load...
return Fragment(content=u"<div>Nothing to randomize between</div>") 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) 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): 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'
......
#!/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 @@ ...@@ -7,12 +7,36 @@
<div id="proctor_stat_${element_id}"></div> <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> </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"> <script type="text/javascript">
procrel = (function(){ procrel = (function(){
var el = $('#proctor_${element_id}'); var el = $('#proctor_${element_id}');
var skiperr = false;
var set_skiperr = function(){ skiperr = true; }
var mkurl = function(cmd) { var mkurl = function(cmd) {
ps = encodeURIComponent("${pp.procset_name}"); ps = encodeURIComponent("${pp.procset_name}");
return "${pp.ProctorPanelServer}/cmd/" + cmd + "/${pp.user_id}/" + ps; return "${pp.ProctorPanelServer}/cmd/" + cmd + "/${pp.user_id}/" + ps;
...@@ -38,7 +62,9 @@ procrel = (function(){ ...@@ -38,7 +62,9 @@ procrel = (function(){
success: gfun, success: gfun,
dataType: "json", dataType: "json",
error: function(xhr, status, error) { 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(){ ...@@ -73,7 +99,10 @@ procrel = (function(){
el.click(make_request); 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> </script>
\ No newline at end of file
% 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