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
import datetime
import json
import pytz
from courseware.views import *
from instructor.offline_gradecalc import student_grades
from collections import OrderedDict
log = logging.getLogger("mitx.module_tree_reset")
#-----------------------------------------------------------------------------
class TreeNode(object):
def __init__(self, module, level, student):
self.module = module
self.level = level
self.student = student
self.smstate = None
if self.module.category in ['randomize', 'problem', 'problemset']:
try:
self.smstate = StudentModule.objects.get(module_state_key=str(self.module.location), student=student)
except StudentModule.DoesNotExist:
pass
def __str__(self):
s = "-"*self.level + ("> %s" % str(self.module.location))
s += ' (%s)' % self.module.display_name
if self.smstate is not None:
s += ' [%s]' % self.smstate.state
return s
__repr__ = __str__
class TreeNodeSet(list):
def __init__(self, module, ms, student):
list.__init__(self)
self.parent_module = module
self.student = student
self.get_tree(module, ms)
self.get_modules_to_reset()
def get_tree(self, module, ms, level=1):
self.append(TreeNode(module, level, self.student))
for child in getattr(module, 'children', []):
m = ms.get_item(child)
if m is not None:
self.get_tree(m, ms, level+1)
def get_modules_to_reset(self):
self.rset = []
self.pset = []
for tn in self:
if tn.module.category=='randomize':
self.rset.append(tn)
elif tn.module.category in ['problem', 'problemset']:
self.pset.append(tn)
def reset_randomization(self):
'''
Go through all <problem> and <randomize> modules in tree and reset their StudentModule state to empty.
'''
msg = "Resetting all <problem> and <randomize> in tree of parent module %s\n" % self.parent_module
for module in self.rset + self.pset:
msg += " Resetting %s, old state=%s\n" % (module, (module.smstate.state if module.smstate is not None else {}))
if module.smstate is not None:
module.smstate.state = '{}'
module.smstate.grade = None
module.smstate.save()
return msg
#-----------------------------------------------------------------------------
class DummyRequest(object):
META = {}
def __init__(self):
return
def get_host(self):
return 'edx.mit.edu'
def is_secure(self):
return False
#-----------------------------------------------------------------------------
class ProctorModuleInfo(object):
def __init__(self, course_loc=''):
if not course_loc:
course_loc = 'i4x://MITx/3.091r-exam/course/2013_Fall_residential_exam'
self.ms = modulestore()
self.course = self.ms.get_item(course_loc)
self.get_released_proctor_modules()
def get_released_proctor_modules(self):
chapters = []
for loc in self.course.children:
chapters.append(self.ms.get_item(loc))
#print "chapters:"
#print [c.id for c in chapters]
pmods = []
for c in chapters:
seq = self.ms.get_item(c.children[0])
if seq.category=='proctor':
pmods.append(seq)
#print "proctor modules:"
#print [x.id for x in pmods]
now = datetime.datetime.now(pytz.utc)
rpmods = [p for p in pmods if p.lms.start < now]
for rpmod in rpmods:
rpmod.ra_ps = self.ms.get_item(rpmod.children[0]) # the problemset
rpmod.ra_rand = self.ms.get_item(rpmod.ra_ps.children[0]) # the randomize
# rpmod.ra_prob = self.ms.get_item(rpmod.ra_rand.children[0]) # the problem
#print "released pmods"
#print [x.id for x in rpmods]
self.chapters = chapters
self.pmods = pmods
self.rpmods = rpmods
return rpmods
def get_grades(self, student=None, request=None):
if student is None:
student = self.student
if request is None:
request = DummyRequest()
request.user = student
request.session = {}
try:
gradeset = student_grades(student, request, self.course, keep_raw_scores=False, use_offline=False)
except Exception as err:
#log.exception("Failed to get grades for %s" % student)
print("Failed to get grades for %s" % student)
gradeset = []
self.gradeset = gradeset
return gradeset
def get_student_status(self, student):
'''
For a given student, and for all released proctored modules, get StudentModule state for each, and
see which randomized problem was selected for a student (if any).
'''
if isinstance(student, str):
student = User.objects.get(username=student)
self.student = student
smstates = OrderedDict()
# temporary - for debugging; flushes db cache
if False:
from django.db import transaction
try:
transaction.commit()
except Exception as err:
print "db cache flushed"
class StateInfo(object):
def __init__(self):
self.state = '{}'
return
for rpmod in self.rpmods: # assume <proctor><problemset><randomized/></problemset></prcotor> structure
try:
sm = StudentModule.objects.get(module_state_key=str(rpmod.ra_rand.location), student=student) # randomize state
except StudentModule.DoesNotExist:
sm = StateInfo()
sm.rpmod = rpmod
try:
ps_sm = StudentModule.objects.get(module_state_key=str(rpmod.ra_ps.location), student=student) # problemset state
except StudentModule.DoesNotExist:
ps_sm = StateInfo()
sm.ps_sm = ps_sm
sm.score = None
# get title (display_name) of problem assigned, if student had started a problem
# base this on the "choice" from the randmize module state
try:
sm.choice = int(json.loads(sm.state)['choice'])
except Exception as err:
sm.choice = None
if sm.choice is not None:
try:
sm.problem = self.ms.get_item(rpmod.ra_rand.children[sm.choice])
sm.problem_name = sm.problem.display_name
except Exception as err:
log.exception("Failed to get rand child choice=%s for %s student=%s" % (sm.choice, rpmod.ra_rand, student))
sm.problem = None
sm.problem_name = None
else:
sm.problem = None
sm.problem_name = None
smstates[rpmod.url_name] = sm # the url_name should be like 'LS1' and be the same key used in the grade scores
self.smstates = smstates
# get grades, match gradeset assignments with StudentModule states, and put grades there
self.get_grades()
for score in self.gradeset['totaled_scores']['Assessment']:
if score.section in smstates:
smstates[score.section].score = score
s = 'State for student %s:\n' % student
status = {} # this can be turned into a JSON string for the proctor panel
status['student'] = dict(username=str(student), name=student.profile.name, id=student.id)
status['assignments'] = []
for (name, sm) in smstates.iteritems():
# attempted = (sm.score is not None) # this doesn't work, since score will always appear?
attempted = 'position' in sm.ps_sm.state # if student has visited the problemset then position will have been set
if not attempted and sm.score is not None and sm.score.earned:
attempted = True
stat = dict(name=name, assignment=sm.rpmod.ra_ps.display_name, pm_sm=sm.ps_sm.state, choice=sm.choice,
problem=sm.problem_name,
attempted=attempted,
earned=(sm.score.earned if sm.score is not None else None),
possible=(sm.score.possible if sm.score is not None else None),
)
status['assignments'].append(stat)
s += "[%s] %s -> %s (%s) %s [%s]\n" % (name, stat['assignment'], stat['pm_sm'], sm.choice, sm.problem_name, sm.score)
self.status = status
self.status_str = s
return status
def get_student_grades(self, student):
'''
Return student grades for assessments as a dict suitable for CSV file output,
with id, name, username, prob1, grade1, prob2, grade2, ...
where grade1 = points earned on assignment LS1, or '' if not attempted
and prob1 = problem which was assigned or '' if not attempted
'''
status = self.get_student_status(student)
ret = OrderedDict()
ret['id'] = student.id
ret['name'] = student.profile.name
# ret['username'] = student.username
ret['email'] = '%s@mit.edu' % student.username
for stat in status['assignments']:
if stat['attempted']:
ret["problem_%s" % stat['name']] = stat['problem']
ret["grade_%s" % stat['name']] = stat['earned']
else:
ret["problem_%s" % stat['name']] = ''
ret["grade_%s" % stat['name']] = ''
return ret
def get_assignments_attempted_and_failed(self, student, do_reset=False):
status = self.get_student_status(student)
assignments = []
for stat in status['assignments']:
if stat['attempted']:
if not stat['earned'] == stat['possible']:
s = "Student %s Assignment %s attempted '%s' but failed (%s/%s)" % (student, stat['name'], stat['problem'], stat['earned'], stat['possible'])
assignments.append(OrderedDict(id=student.id,
name=student.profile.name,
username=student.username,
assignment=stat['name'],
problem=stat['problem'],
date=str(datetime.datetime.now()),
earned=stat['earned'],
possible=stat['possible'],
)
)
if do_reset:
aaf = assignments[-1]
try:
log.debug('resetting %s for student %s' % (aaf['assignment'], aaf['username']))
pmod = self.ms.get_item('i4x://MITx/3.091r-exam/proctor/%s' % aaf['assignment'])
tnset = TreeNodeSet(pmod, self.ms, student)
msg = tnset.reset_randomization()
log.debug(str(msg))
except Exception as err:
log.exception("Failed to do reset of %s for %s" % (aaf['assignment'], student))
return assignments
#-----------------------------------------------------------------------------
def getip(request):
'''
Extract IP address of requester from header, even if behind proxy
'''
ip = request.META.get('HTTP_X_REAL_IP', '') # nginx reverse proxy
if not ip:
ip = request.META.get('REMOTE_ADDR', 'None')
return ip
#-----------------------------------------------------------------------------
ALLOWED_IPS = [ '173.48.139.155', '10.152.159.162', '54.235.195.90' ]
#ALLOWED_IPS = [ ]
ALLOWED_STAFF = 'staff_MITx/3.091r-exam/2013_Fall_residential_exam'
def index(request):
ip = getip(request)
if not ip in ALLOWED_IPS:
if request.user and request.user.is_staff and False:
log.debug('request allowed because user=%s is staff' % request.user)
elif request.user is not None and request.user:
groups = [g.name for g in request.user.groups.all()]
if ALLOWED_STAFF in groups:
log.debug('request allowed because user=%s is in group %s' % (request.user, ALLOWED_STAFF))
else:
log.debug('request denied, user=%s, groups %s' % (request.user, groups))
# return HttpResponse('permission denied', status=403)
else:
log.debug('request denied, user=%s, groups %s' % (request.user, groups))
# return HttpResponse('permission denied', status=403)
else:
log.debug('request allowed, in ALLOWED_IPS')
username = request.GET.get('username')
try:
student = User.objects.get(username=username)
except User.DoesNotExist:
return HttpResponse(json.dumps({'msg':'User does not exist', 'error': True}))
cmd = request.GET.get('cmd')
if cmd=='reset':
location = request.GET.get('location') # eg proctor/Assessment_2
location = location.replace(' ','_')
ms = modulestore()
pmod = ms.get_item('i4x://MITx/3.091r-exam/proctor/%s' % location)
tnset = TreeNodeSet(pmod, ms, student)
s = ''
for r in tnset.rset + tnset.pset:
s += str(r) + '\n'
msg = tnset.reset_randomization()
# return render_to_response("module_tree_reset.html", {'status': s, 'msg': msg})
#cmd = 'grades'
cmd = 'status'
if cmd=='status':
try:
pminfo = ProctorModuleInfo()
status = pminfo.get_student_status(student)
except Exception as err:
log.exception("Failed to get status for %s" % student)
return HttpResponse(json.dumps({'msg':'Error getting grades for %s' % student, 'error': True, 'errstr': str(err)}))
return HttpResponse(json.dumps(status))
if cmd=='grades':
# from instructor.offline_gradecalc import student_grades
ms = modulestore()
course = ms.get_item('i4x://MITx/3.091r-exam/course/2013_Fall_residential_exam')
try:
gradeset = student_grades(student, request, course, keep_raw_scores=False, use_offline=False)
except Exception as err:
log.exception("Failed to get grades for %s" % student)
return HttpResponse(json.dumps({'msg':'Error getting grades for %s' % student, 'error': True}))
grades = gradeset['totaled_scores']
grades['student_id'] = student.id
return HttpResponse(json.dumps(grades))
return render_to_response("module_tree_reset.html", {'status': 'unknown command', 'msg': ''})
<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