Commit 9adadb01 by Justin Riley

add proctor admin buttons for override and reset

* Added proctor admin buttons for staff override and student reset -
  uses the same buttons/theme as staff debug/submission history.
  These buttons are *always* visible at the top of the page for staff
  users. The override button shows the current override status and the
  student admin button displays a modal with a form to reset a given
  user.
* Combined all proctor templates into proctor.html
* Removed all proctor JS alert() calls
parent fb0442a7
...@@ -11,6 +11,9 @@ from xmodule.seq_module import SequenceDescriptor ...@@ -11,6 +11,9 @@ from xmodule.seq_module import SequenceDescriptor
from xblock.fields import Scope, String, Boolean from xblock.fields import Scope, String, Boolean
from xblock.fragment import Fragment from xblock.fragment import Fragment
from courseware import module_tree_reset
log = logging.getLogger('mitx.' + __name__) log = logging.getLogger('mitx.' + __name__)
...@@ -135,59 +138,102 @@ class ProctorModule(ProctorFields, XModule): ...@@ -135,59 +138,102 @@ class ProctorModule(ProctorFields, XModule):
""" """
return [self.child_descriptor] return [self.child_descriptor]
def not_released_html(self): def _template_ctx(self):
return Fragment(self.runtime.render_template('proctor_release.html', { ctx = {
'element_id': self.location.html_id(),
'id': self.id, 'id': self.id,
'name': self.display_name or self.procset_name,
'pp': self.pp, 'pp': self.pp,
'name': self.display_name or self.procset_name,
'element_id': self.location.html_id(),
'location': self.location, 'location': self.location,
'ajax_url': self.runtime.ajax_url, 'ajax_url': self.runtime.ajax_url,
'is_staff': self.runtime.user_is_staff, 'is_staff': self.runtime.user_is_staff,
})) 'staff_release': self.staff_release,
'is_released': self.is_released(),
'child_html': None,
}
return ctx
def not_released_html(self):
return Fragment(self.runtime.render_template('proctor.html',
self._template_ctx()))
def student_view(self, context): def student_view(self, context):
if not self.is_released(): # check each time we do get_html() proc_ctx = self._template_ctx()
return self.not_released_html()
# for sequential module, just return HTML (no ajax container) # for sequential module, just return HTML (no ajax container)
categories = ['sequential', 'videosequence', 'problemset', 'randomize'] categories = ['sequential', 'videosequence', 'problemset', 'randomize']
if self.child.category in categories: if self.child.category in categories:
html = self.child.render('student_view', context) proc_ctx['child_html'] = self.child.render('student_view',
if self.staff_release: context).content
dishtml = self.runtime.render_template( return Fragment(self.runtime.render_template(
'proctor_disable.html', { 'proctor.html', proc_ctx))
'element_id': self.location.html_id(),
'is_staff': self.runtime.user_is_staff,
'ajax_url': self.runtime.ajax_url,
})
html.content = dishtml + html.content
return html
# return ajax container, so that we can dynamically check for # return ajax container, so that we can dynamically check for
# is_released changing # is_released changing
return Fragment(self.runtime.render_template('conditional_ajax.html', { proc_ctx['depends'] = ''
'element_id': self.location.html_id(), return Fragment(self.runtime.render_template('conditional_ajax.html',
'id': self.id, proc_ctx))
'ajax_url': self.runtime.ajax_url,
'depends': '', def handle_ajax(self, dispatch, data):
})) if self.runtime.user_is_staff:
if dispatch == 'release':
def handle_ajax(self, _dispatch, _data): self.staff_release = True
if self.runtime.user_is_staff and _dispatch == 'release': return json.dumps({'html': 'staff_release successful'})
self.staff_release = True if dispatch == 'disable':
return json.dumps({'html': 'staff_release successful'}) self.staff_release = False
if self.runtime.user_is_staff and _dispatch == 'disable': return json.dumps({'html': 'staff_disable successful'})
self.staff_release = False
return json.dumps({'html': 'staff_disable successful'}) # Proctor Student Admin URLs (STAFF ONLY)
if dispatch == 'reset':
username = data.get("username")
return self.reset(username)
if dispatch == 'status':
return self.status()
# if dispatch == 'grades':
# return self.grades()
if not self.is_released(): # check each time we do get_html() if not self.is_released(): # check each time we do get_html()
html = self.not_released_html() html = self.not_released_html()
return json.dumps({'html': [html], 'message': bool(True)}) return json.dumps({'html': [html], 'message': True})
html = [child.get_html() for child in self.get_display_items()] html = [child.get_html() for child in self.get_display_items()]
return json.dumps({'html': html}) return json.dumps({'html': html})
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):
pminfo = module_tree_reset.ProctorModuleInfo(self.runtime.course_id)
pminfo.get_assignments_attempted_and_failed(username, do_reset=True)
return self.status(username)
def status(self, username):
try:
student = self.pp.user
pminfo = module_tree_reset.ProctorModuleInfo(self.runtime.course_id)
status = pminfo.get_student_status(username)
except Exception as err:
log.exception("Failed to get status for %s" % student)
status = {'msg': 'Error getting grades for %s' % student,
'error': True, 'errstr': str(err)}
return json.dumps(status)
# TODO: investigate whether this is needed or not
#
# def grades(self):
# student = self.pp.user
# 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:
# log.exception("Failed to get grades for %s" % student)
# return json.dumps(
# {'msg': 'Error getting grades for %s' % student,
# 'error': True})
# grades = gradeset['totaled_scores']
# grades['student_id'] = student.id
# return json.dumps(grades)
class ProctorDescriptor(ProctorFields, SequenceDescriptor): class ProctorDescriptor(ProctorFields, SequenceDescriptor):
# the editing interface can be the same as for sequences -- just a container # the editing interface can be the same as for sequences -- just a container
......
<%! from django.utils.translation import ugettext as _ %>
<%namespace name='static' file='/static_content.html'/>
% if is_staff:
<% override_label = "On" if staff_release else "Off" %>
<div aria-hidden="true" class="wrap-instructor-info">
<a class="instructor-info-action" href="#proctor_admin_${element_id}" id="proctor_admin">${_("Proctor Student Admin")}</a>
<a class="instructor-info-action" href="#proctor_release_${element_id}" id="proctor_override">${_("Proctor Override")}: <b>${_(override_label)}</b></a>
</div>
<hr/>
<section aria-hidden="true" class="modal history-modal" id="proctor_admin_${element_id}" style="width:80%; left:20%; height:80%; overflow:auto;" >
<div class="inner-wrapper" style="color:black">
<header>
<h2>${_("Proctor Student Admin")}</h2>
</header>
<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="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>
</div>
</form>
<div id="proctor_${element_id}_text" class="staff_info" style="display:block">
</div>
</div>
</section>
<script type="text/javascript" src="${static.url('js/vendor/jquery.leanModal.min.js')}"></script>
<script type="text/javascript">
<% override_cmd = "disable" if staff_release else "release" %>
proctor_override = function(){
$.get( "${ajax_url}/${override_cmd}", function( data ) {
// $("#proctor_release").html( data );
// procrel.set_skiperr();
console.log("release successful");
location.reload();
});
}
$('#proctor_override').click(proctor_override);
$('#proctor_admin').leanModal();
$('#proctor_${element_id}_submit').click( function() {
$.ajax({
url: '${ajax_url}/reset',
type: 'POST',
dataType: 'json',
data: $('#proctor_${element_id}_reset_form').serialize(),
success: function(data) {
$('#proctor_${element_id}_text').append(JSON.stringify(data));
}
});
});
</script>
% endif
% if not is_released:
<div> <div>
<h2>Welcome: <b>${pp.user.profile.name or pp.user}</b></h2> <h2>Welcome: <b>${pp.user.profile.name or pp.user}</b></h2>
<p>${name} has not been released</p> <p>${name} has not been released</p>
<p><input id="proctor_${element_id}" type="button" value="Request Access"></p>
<div id="proctor_stat_${element_id}"></div> <div id="proctor_stat_${element_id}"></div>
% if is_staff: <p><input id="proctor_${element_id}" type="button" value="Request Access"></p>
<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}');
...@@ -54,11 +94,11 @@ procrel = (function(){ ...@@ -54,11 +94,11 @@ procrel = (function(){
data: { "uname": "${pp.user.username}", data: { "uname": "${pp.user.username}",
"name": "${pp.user.profile.name}" "name": "${pp.user.profile.name}"
}, },
xhrFields: { xhrFields: {
withCredentials: true withCredentials: true
}, },
username: "${pp.proc_user}", username: "${pp.proc_user}",
password: "${pp.proc_pass}", password: "${pp.proc_pass}",
success: gfun, success: gfun,
dataType: "json", dataType: "json",
error: function(xhr, status, error) { error: function(xhr, status, error) {
...@@ -77,7 +117,7 @@ procrel = (function(){ ...@@ -77,7 +117,7 @@ procrel = (function(){
console.log("clearing interval for check_access"); console.log("clearing interval for check_access");
clearInterval(interval); clearInterval(interval);
} }
alert ("Access Granted!"); console.log("Access Granted!");
location.reload(); location.reload();
} }
}); });
...@@ -113,3 +153,9 @@ procrel = (function(){ ...@@ -113,3 +153,9 @@ procrel = (function(){
}() ); }() );
</script> </script>
% elif child_html is not None:
${child_html}
% endif
% 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>
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