add first pass at testcenter exam
This commit is contained in:
@@ -2,6 +2,7 @@ import logging
|
||||
import urllib
|
||||
|
||||
from functools import partial
|
||||
from time import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.context_processors import csrf
|
||||
@@ -297,6 +298,140 @@ def index(request, course_id, chapter=None, section=None,
|
||||
|
||||
return result
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
def testcenter_exam(request, course_id, chapter, section):
|
||||
"""
|
||||
Displays only associated content. If course, chapter,
|
||||
and section are all specified, renders the page, or returns an error if they
|
||||
are invalid.
|
||||
|
||||
Returns an error if these are not all specified and correct.
|
||||
|
||||
Arguments:
|
||||
|
||||
- request : HTTP request
|
||||
- course_id : course id (str: ORG/course/URL_NAME)
|
||||
- chapter : chapter url_name (str)
|
||||
- section : section url_name (str)
|
||||
|
||||
Returns:
|
||||
|
||||
- HTTPresponse
|
||||
"""
|
||||
course = get_course_with_access(request.user, course_id, 'load', depth=2)
|
||||
staff_access = has_access(request.user, course, 'staff')
|
||||
registered = registered_for_course(course, request.user)
|
||||
if not registered:
|
||||
log.debug('User %s tried to view course %s but is not enrolled' % (request.user,course.location.url()))
|
||||
raise # error
|
||||
try:
|
||||
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
|
||||
course.id, request.user, course, depth=2)
|
||||
|
||||
# Has this student been in this course before?
|
||||
# first_time = student_module_cache.lookup(course_id, 'course', course.location.url()) is None
|
||||
|
||||
# Load the module for the course
|
||||
course_module = get_module_for_descriptor(request.user, request, course, student_module_cache, course.id)
|
||||
if course_module is None:
|
||||
log.warning('If you see this, something went wrong: if we got this'
|
||||
' far, should have gotten a course module for this user')
|
||||
# return redirect(reverse('about_course', args=[course.id]))
|
||||
raise # error
|
||||
|
||||
if chapter is None:
|
||||
# return redirect_to_course_position(course_module, first_time)
|
||||
raise # error
|
||||
|
||||
# BW: add this test earlier, and remove later clause
|
||||
if section is None:
|
||||
# return redirect_to_course_position(course_module, first_time)
|
||||
raise # error
|
||||
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
# 'accordion': render_accordion(request, course, chapter, section),
|
||||
'COURSE_TITLE': course.title,
|
||||
'course': course,
|
||||
'init': '',
|
||||
'content': '',
|
||||
'staff_access': staff_access,
|
||||
'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER','http://xqa:server@content-qa.mitx.mit.edu/xqa')
|
||||
}
|
||||
|
||||
chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter)
|
||||
if chapter_descriptor is not None:
|
||||
instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache)
|
||||
save_child_position(course_module, chapter, instance_module)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
chapter_module = course_module.get_child_by(lambda m: m.url_name == chapter)
|
||||
if chapter_module is None:
|
||||
# User may be trying to access a chapter that isn't live yet
|
||||
raise Http404
|
||||
|
||||
section_descriptor = chapter_descriptor.get_child_by(lambda m: m.url_name == section)
|
||||
if section_descriptor is None:
|
||||
# Specifically asked-for section doesn't exist
|
||||
raise Http404
|
||||
|
||||
# Load all descendents of the section, because we're going to display its
|
||||
# html, which in general will need all of its children
|
||||
section_module = get_module(request.user, request, section_descriptor.location,
|
||||
student_module_cache, course.id, position=None, depth=None)
|
||||
if section_module is None:
|
||||
# User may be trying to be clever and access something
|
||||
# they don't have access to.
|
||||
raise Http404
|
||||
|
||||
# Save where we are in the chapter
|
||||
# instance_module = get_instance_module(course_id, request.user, chapter_module, student_module_cache)
|
||||
# save_child_position(chapter_module, section, instance_module)
|
||||
|
||||
|
||||
context['content'] = section_module.get_html()
|
||||
|
||||
# figure out when the exam should end. Going forward, this is determined by getting a "normal"
|
||||
# duration from the test, then doing some math to modify the duration based on accommodations,
|
||||
# and then use that value as the end. Once we have calculated this, it should be sticky -- we
|
||||
# use the same value for future requests, unless it's a tester.
|
||||
|
||||
# Let's try 600s for now...
|
||||
context['end_date'] = (time() + 600) * 1000
|
||||
|
||||
result = render_to_response('courseware/testcenter_exam.html', context)
|
||||
except Exception as e:
|
||||
if isinstance(e, Http404):
|
||||
# let it propagate
|
||||
raise
|
||||
|
||||
# In production, don't want to let a 500 out for any reason
|
||||
if settings.DEBUG:
|
||||
raise
|
||||
else:
|
||||
log.exception("Error in exam view: user={user}, course={course},"
|
||||
" chapter={chapter} section={section}"
|
||||
"position={position}".format(
|
||||
user=request.user,
|
||||
course=course,
|
||||
chapter=chapter,
|
||||
section=section
|
||||
))
|
||||
try:
|
||||
result = render_to_response('courseware/courseware-error.html',
|
||||
{'staff_access': staff_access,
|
||||
'course' : course})
|
||||
except:
|
||||
# Let the exception propagate, relying on global config to at
|
||||
# at least return a nice error message
|
||||
log.exception("Error while rendering courseware-error page")
|
||||
raise
|
||||
|
||||
return result
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def jump_to(request, course_id, location):
|
||||
'''
|
||||
|
||||
152
lms/templates/courseware/testcenter_exam.html
Normal file
152
lms/templates/courseware/testcenter_exam.html
Normal file
@@ -0,0 +1,152 @@
|
||||
<%inherit file="/main_nonav.html" />
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%block name="bodyclass">courseware ${course.css_class}</%block>
|
||||
<%block name="title"><title>${course.number} Exam</title></%block>
|
||||
|
||||
<%block name="headextra">
|
||||
<%static:css group='course'/>
|
||||
<%include file="../discussion/_js_head_dependencies.html" />
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<script type="text/javascript" src="${static.url('js/vendor/jquery.scrollTo-1.4.2-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
|
||||
|
||||
## codemirror
|
||||
<script type="text/javascript" src="${static.url('js/vendor/codemirror-compressed.js')}"></script>
|
||||
|
||||
## alternate codemirror
|
||||
## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/lib/codemirror.js')}"></script>
|
||||
## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/xml/xml.js')}"></script>
|
||||
## <script type="text/javascript" src="${static.url('js/vendor/CodeMirror-2.25/mode/python/python.js')}"></script>
|
||||
|
||||
|
||||
<%static:js group='courseware'/>
|
||||
<%static:js group='discussion'/>
|
||||
|
||||
<%include file="../discussion/_js_body_dependencies.html" />
|
||||
% if staff_access:
|
||||
<%include file="xqa_interface.html"/>
|
||||
% endif
|
||||
|
||||
<!-- TODO: http://docs.jquery.com/Plugins/Validation -->
|
||||
<script type="text/javascript">
|
||||
document.write('\x3Cscript type="text/javascript" src="' +
|
||||
document.location.protocol + '//www.youtube.com/player_api">\x3C/script>');
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var $$course_id = "${course.id}";
|
||||
|
||||
$(function(){
|
||||
$(".ui-accordion-header a, .ui-accordion-content .subtitle").each(function() {
|
||||
var elemText = $(this).text().replace(/^\s+|\s+$/g,''); // Strip leading and trailing whitespace
|
||||
var wordArray = elemText.split(" ");
|
||||
var finalTitle = "";
|
||||
if (wordArray.length > 0) {
|
||||
for (i=0;i<=wordArray.length-1;i++) {
|
||||
finalTitle += wordArray[i];
|
||||
if (i == (wordArray.length-2)) {
|
||||
finalTitle += " ";
|
||||
} else if (i == (wordArray.length-1)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
finalTitle += " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
$(this).html(finalTitle);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function getRemainingTime(){
|
||||
function pretty_time_string(num) {
|
||||
return ( num < 10 ? "0" : "" ) + num;
|
||||
}
|
||||
|
||||
// set the end time when the template is rendered
|
||||
var endTime = new Date(${end_date});
|
||||
var currentTime = new Date();
|
||||
var remaining_secs = Math.floor((endTime - currentTime)/1000);
|
||||
|
||||
// check to see if time has expired. If so, we need
|
||||
// to go to the exit URL.
|
||||
// TBD...
|
||||
if (remaining_secs <= 0) {
|
||||
return "00:00:00";
|
||||
}
|
||||
|
||||
// count down in terms of hours, minutes, and seconds:
|
||||
var hours = pretty_time_string(Math.floor(remaining_secs / 3600));
|
||||
remaining_secs = remaining_secs % 3600;
|
||||
var minutes = pretty_time_string(Math.floor(remaining_secs / 60));
|
||||
remaining_secs = remaining_secs % 60;
|
||||
var seconds = pretty_time_string(Math.floor(remaining_secs));
|
||||
|
||||
var remainingTimeString = hours + ":" + minutes + ":" + seconds;
|
||||
return remainingTimeString;
|
||||
}
|
||||
|
||||
setInterval(function(){
|
||||
$('#exam_timer').text(getRemainingTime()); }, 1000);
|
||||
|
||||
|
||||
// presumably we need to call something to clear the interval once
|
||||
// the time has expired? Pass the object that the setInterval() returns
|
||||
// to clearInterval()
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</%block>
|
||||
|
||||
<!-- %include file="/courseware/course_navigation.html" args="active_page='courseware'" / -->
|
||||
|
||||
<span id="exam_timer"> </span>
|
||||
|
||||
<section class="container">
|
||||
<div class="course-wrapper">
|
||||
<section class="course-content">
|
||||
${content}
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
% if course.show_calculator:
|
||||
<div class="calc-main">
|
||||
<a aria-label="Open Calculator" href="#" class="calc">Calculator</a>
|
||||
|
||||
<div id="calculator_wrapper">
|
||||
<form id="calculator">
|
||||
<div class="input-wrapper">
|
||||
<input type="text" id="calculator_input" />
|
||||
|
||||
<div class="help-wrapper">
|
||||
<a href="#">Hints</a>
|
||||
<dl class="help">
|
||||
<dt>Suffixes:</dt>
|
||||
<dd> %kMGTcmunp</dd>
|
||||
<dt>Operations:</dt>
|
||||
<dd>^ * / + - ()</dd>
|
||||
<dt>Functions:</dt>
|
||||
<dd>sin, cos, tan, sqrt, log10, log2, ln, arccos, arcsin, arctan, abs </dd>
|
||||
<dt>Constants</dt>
|
||||
<dd>e, pi</dd>
|
||||
|
||||
<!-- Students won't know what parallel means at this time. Complex numbers aren't well tested in the courseware, so we would prefer to not expose them. If you read the comments in the source, feel free to use them. If you run into a bug, please let us know. But we can't officially support them right now.
|
||||
|
||||
<dt>Unsupported:</dt> <dd>||, j </dd> -->
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<input id="calculator_button" type="submit" value="="/>
|
||||
<input type="text" id="calculator_output" readonly />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
46
lms/templates/main_nonav.html
Normal file
46
lms/templates/main_nonav.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<%block name="title"><title>edX</title></%block>
|
||||
|
||||
<!-- TODO: remove the link here, so there is no navigation at all on the page -->
|
||||
<link rel="icon" type="image/x-icon" href="${static.url('images/favicon.ico')}" />
|
||||
|
||||
<%static:css group='application'/>
|
||||
|
||||
<%static:js group='main_vendor'/>
|
||||
<%block name="headextra"/>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="${static.url('js/html5shiv.js')}"></script>
|
||||
<![endif]-->
|
||||
|
||||
<!--[if lte IE 9]>
|
||||
<%static:css group='ie-fixes'/>
|
||||
<![endif]-->
|
||||
<meta name="path_prefix" content="${MITX_ROOT_URL}">
|
||||
<meta name="google-site-verification" content="_mipQ4AtZQDNmbtOkwehQDOgCxUUV2fb_C0b6wbiRHY" />
|
||||
|
||||
% if not course:
|
||||
<%include file="google_analytics.html" />
|
||||
% endif
|
||||
|
||||
</head>
|
||||
|
||||
<body class="<%block name='bodyclass'/>">
|
||||
|
||||
<!-- %include file="navigation.html" / -->
|
||||
<section class="content-wrapper">
|
||||
${self.body()}
|
||||
<%block name="bodyextra"/>
|
||||
</section>
|
||||
|
||||
<!-- %include file="footer.html" / -->
|
||||
|
||||
<%static:js group='application'/>
|
||||
<%static:js group='module-js'/>
|
||||
|
||||
<%block name="js_extra"/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -217,6 +217,10 @@ if settings.COURSEWARE_ENABLED:
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$',
|
||||
'courseware.views.course_about', name="about_course"),
|
||||
|
||||
# testcenter exam:
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/testcenter_exam/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$',
|
||||
'courseware.views.testcenter_exam', name="testcenter_exam"),
|
||||
|
||||
#Inside the course
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/$',
|
||||
'courseware.views.course_info', name="course_root"),
|
||||
|
||||
Reference in New Issue
Block a user