Switch timed_module to store location, and use to navigate from timer when timer displays on non-exam course pages.
This commit is contained in:
@@ -1138,10 +1138,11 @@ def test_center_login(request):
|
||||
# know the module_id to use that corresponds to the particular exam_series_code.
|
||||
# For now, we can hardcode that...
|
||||
if exam_series_code == '6002x001':
|
||||
chapter_url_name = 'Final_Exam'
|
||||
section_url_name = 'Final_Exam_Fall_2012'
|
||||
redirect_url = reverse('courseware_section', args=[course_id, chapter_url_name, section_url_name])
|
||||
location = 'i4x://MITx/6.002x/2012_Fall/sequence/Final_Exam_Fall_2012'
|
||||
# This should not be hardcoded here, but should be added to the exam definition.
|
||||
# TODO: look the location up in the course, by finding the exam_info with the matching code,
|
||||
# and get the location from that.
|
||||
location = 'i4x://MITx/6.002x/sequential/Final_Exam_Fall_2012'
|
||||
redirect_url = reverse('jump_to', kwargs={'course_id': course_id, 'location': location})
|
||||
else:
|
||||
# TODO: clarify if this is the right error code for this condition.
|
||||
return HttpResponseRedirect(makeErrorURL(error_url, "incorrectCandidateTests"));
|
||||
@@ -1152,7 +1153,7 @@ def test_center_login(request):
|
||||
'ETDBTM' : 'ADDDOUBLE', }
|
||||
|
||||
# check if the test has already been taken
|
||||
timed_modules = TimedModule.objects.filter(student=testcenteruser.user, course_id=course_id, module_state_key=location)
|
||||
timed_modules = TimedModule.objects.filter(student=testcenteruser.user, course_id=course_id, location=location)
|
||||
if timed_modules:
|
||||
timed_module = timed_modules[0]
|
||||
if timed_module.has_ended:
|
||||
@@ -1167,17 +1168,9 @@ def test_center_login(request):
|
||||
if client_candidate_id == "edX003671291147":
|
||||
time_accommodation_code = 'TESTING'
|
||||
if time_accommodation_code:
|
||||
timed_module = TimedModule(student=request.user, course_id=course_id, module_state_key=location)
|
||||
timed_module = TimedModule(student=request.user, course_id=course_id, location=location)
|
||||
timed_module.accommodation_code = time_accommodation_code
|
||||
timed_module.save()
|
||||
|
||||
# Now log the user in:
|
||||
# user = authenticate(username=testcenteruser.user.username,
|
||||
# password=testcenteruser.user.password)
|
||||
#
|
||||
# if user is None:
|
||||
# # argh. We couldn't login!
|
||||
# return HttpResponseRedirect(makeErrorURL(error_url, "ARGH! User cannot log in"));
|
||||
|
||||
# UGLY HACK!!!
|
||||
# Login assumes that authentication has occurred, and that there is a
|
||||
|
||||
@@ -11,7 +11,7 @@ class Migration(SchemaMigration):
|
||||
# Adding model 'TimedModule'
|
||||
db.create_table('courseware_timedmodule', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('module_state_key', self.gf('django.db.models.fields.CharField')(max_length=255, db_column='module_id', db_index=True)),
|
||||
('location', self.gf('django.db.models.fields.CharField')(max_length=255, db_column='location', db_index=True)),
|
||||
('student', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
||||
('course_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
|
||||
('accommodation_code', self.gf('django.db.models.fields.CharField')(default='NONE', max_length=12, db_index=True)),
|
||||
@@ -22,13 +22,13 @@ class Migration(SchemaMigration):
|
||||
))
|
||||
db.send_create_signal('courseware', ['TimedModule'])
|
||||
|
||||
# Adding unique constraint on 'TimedModule', fields ['student', 'module_state_key', 'course_id']
|
||||
db.create_unique('courseware_timedmodule', ['student_id', 'module_id', 'course_id'])
|
||||
# Adding unique constraint on 'TimedModule', fields ['student', 'location', 'course_id']
|
||||
db.create_unique('courseware_timedmodule', ['student_id', 'location', 'course_id'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'TimedModule', fields ['student', 'module_state_key', 'course_id']
|
||||
db.delete_unique('courseware_timedmodule', ['student_id', 'module_id', 'course_id'])
|
||||
# Removing unique constraint on 'TimedModule', fields ['student', 'location', 'course_id']
|
||||
db.delete_unique('courseware_timedmodule', ['student_id', 'location', 'course_id'])
|
||||
|
||||
# Deleting model 'TimedModule'
|
||||
db.delete_table('courseware_timedmodule')
|
||||
@@ -103,15 +103,15 @@ class Migration(SchemaMigration):
|
||||
'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'courseware.timedmodule': {
|
||||
'Meta': {'unique_together': "(('student', 'module_state_key', 'course_id'),)", 'object_name': 'TimedModule'},
|
||||
'Meta': {'unique_together': "(('student', 'location', 'course_id'),)", 'object_name': 'TimedModule'},
|
||||
'accommodation_code': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '12', 'db_index': 'True'}),
|
||||
'beginning_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'ending_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'location'", 'db_index': 'True'}),
|
||||
'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'module_state_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'module_id'", 'db_index': 'True'}),
|
||||
'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,12 +224,14 @@ class TimedModule(models.Model):
|
||||
# but for abtests and the like, this can be set to a shared value
|
||||
# for many instances of the module.
|
||||
# Filename for homeworks, etc.
|
||||
module_state_key = models.CharField(max_length=255, db_index=True, db_column='module_id')
|
||||
# module_state_key = models.CharField(max_length=255, db_index=True, db_column='module_id')
|
||||
location = models.CharField(max_length=255, db_index=True, db_column='location')
|
||||
student = models.ForeignKey(User, db_index=True)
|
||||
course_id = models.CharField(max_length=255, db_index=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = (('student', 'module_state_key', 'course_id'),)
|
||||
# unique_together = (('student', 'module_state_key', 'course_id'),)
|
||||
unique_together = (('student', 'location', 'course_id'),)
|
||||
|
||||
# For a timed activity, we are only interested here
|
||||
# in time-related accommodations, and these should be disjoint.
|
||||
|
||||
@@ -215,6 +215,43 @@ def index(request, course_id, chapter=None, section=None,
|
||||
'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER','http://xqa:server@content-qa.mitx.mit.edu/xqa')
|
||||
}
|
||||
|
||||
# check here if this page is within a course that has an active timed module running. If so, then
|
||||
# display the appropriate timer information:
|
||||
timed_modules = TimedModule.objects.filter(student=request.user, course_id=course_id)
|
||||
if timed_modules:
|
||||
for timed_module in timed_modules:
|
||||
if timed_module.has_begun and not timed_module.has_ended:
|
||||
# a timed module has been found that is active, so display
|
||||
# the relevant time:
|
||||
# module_state_key = timed_module.module_state_key
|
||||
location = timed_module.location
|
||||
|
||||
# when we actually make the state be stored in the StudentModule, then
|
||||
# we can fetch what we need from that.
|
||||
# student_module = student_module_cache.lookup(course_id, 'sequential', module_state_key)
|
||||
# But the module doesn't give us anything helpful to find the corresponding descriptor
|
||||
|
||||
# get the corresponding section_descriptor for this timed_module entry:
|
||||
section_descriptor = modulestore().get_instance(course_id, Location(location))
|
||||
|
||||
# determine where to go when the timer expires:
|
||||
# Note that if we could get this from the timed_module, we wouldn't have to
|
||||
# fetch the section_descriptor in the first place.
|
||||
if 'time_expired_redirect_url' not in section_descriptor.metadata:
|
||||
raise Http404
|
||||
time_expired_redirect_url = section_descriptor.metadata.get('time_expired_redirect_url')
|
||||
context['time_expired_redirect_url'] = time_expired_redirect_url
|
||||
|
||||
# Fetch the end time (in GMT) as stored in the module when it was started.
|
||||
# This value should be UTC time as number of milliseconds since epoch.
|
||||
end_date = timed_module.get_end_time_in_ms()
|
||||
context['timer_expiration_datetime'] = end_date
|
||||
if 'suppress_toplevel_navigation' in section_descriptor.metadata:
|
||||
context['suppress_toplevel_navigation'] = section_descriptor.metadata['suppress_toplevel_navigation']
|
||||
return_url = reverse('jump_to', kwargs={'course_id': course_id, 'location': location})
|
||||
context['timer_navigation_return_url'] = return_url
|
||||
|
||||
|
||||
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)
|
||||
@@ -416,7 +453,7 @@ def timed_exam(request, course_id, chapter, section):
|
||||
|
||||
# get corresponding time module, if one is present:
|
||||
try:
|
||||
timed_module = TimedModule.objects.get(student=request.user, course_id=course_id, module_state_key=section_module.id)
|
||||
timed_module = TimedModule.objects.get(student=request.user, course_id=course_id, location=section_module.location)
|
||||
|
||||
# if a module exists, check to see if it has already been started,
|
||||
# and if it has already ended.
|
||||
@@ -446,7 +483,7 @@ def timed_exam(request, course_id, chapter, section):
|
||||
except TimedModule.DoesNotExist:
|
||||
# no entry found. So we're starting this test
|
||||
# without any accommodations being preset.
|
||||
timed_module = TimedModule(student=request.user, course_id=course_id, module_state_key=section_module.id)
|
||||
timed_module = TimedModule(student=request.user, course_id=course_id, location=section_module.location)
|
||||
timed_module.begin(duration)
|
||||
timed_module.save()
|
||||
|
||||
@@ -457,9 +494,12 @@ def timed_exam(request, course_id, chapter, section):
|
||||
end_date = timed_module.get_end_time_in_ms()
|
||||
|
||||
# This value should be UTC time as number of milliseconds since epoch.
|
||||
context['end_date'] = end_date
|
||||
|
||||
result = render_to_response('courseware/testcenter_exam.html', context)
|
||||
# context['end_date'] = end_date
|
||||
context['timer_expiration_datetime'] = end_date
|
||||
if 'suppress_toplevel_navigation' in section_descriptor.metadata:
|
||||
context['suppress_toplevel_navigation'] = section_descriptor.metadata['suppress_toplevel_navigation']
|
||||
|
||||
result = render_to_response('courseware/courseware.html', context)
|
||||
except Exception as e:
|
||||
if isinstance(e, Http404):
|
||||
# let it propagate
|
||||
|
||||
@@ -59,12 +59,75 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
% if timer_expiration_datetime:
|
||||
<script type="text/javascript">
|
||||
var timer = {
|
||||
timer_inst : null,
|
||||
get_remaining_secs : function() {
|
||||
// set the end time when the template is rendered.
|
||||
// This value should be UTC time as number of milliseconds since epoch.
|
||||
var endTime = new Date(${timer_expiration_datetime});
|
||||
var currentTime = new Date();
|
||||
var remaining_secs = Math.floor((endTime - currentTime)/1000);
|
||||
return remaining_secs;
|
||||
},
|
||||
get_time_string : function() {
|
||||
function pretty_time_string(num) {
|
||||
return ( num < 10 ? "0" : "" ) + num;
|
||||
}
|
||||
// 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;
|
||||
},
|
||||
update_time : function(self) {
|
||||
remaining_secs = self.get_remaining_secs();
|
||||
if (remaining_secs <= 0) {
|
||||
self.end(self);
|
||||
}
|
||||
$('#exam_timer').text(self.get_time_string(remaining_secs));
|
||||
},
|
||||
start : function() { var that = this;
|
||||
this.timer_inst = setInterval(function(){ that.update_time(that); }, 1000);
|
||||
},
|
||||
end : function(self) {
|
||||
clearInterval(self.timer_inst);
|
||||
// redirect to specified URL:
|
||||
window.location = "${time_expired_redirect_url}";
|
||||
}
|
||||
}
|
||||
// start timer right away:
|
||||
timer.start();
|
||||
</script>
|
||||
% endif
|
||||
|
||||
</%block>
|
||||
|
||||
<%include file="/courseware/course_navigation.html" args="active_page='courseware'" />
|
||||
% if timer_expiration_datetime:
|
||||
<div class="timer-main">
|
||||
<div id="timer_wrapper">
|
||||
<div class="timer_label">Time Remaining:</div> <div id="exam_timer" class="timer_value"> </div>
|
||||
% if timer_navigation_return_url:
|
||||
<a href="${timer_navigation_return_url}" class="timer_return_url">Return...</a>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
% if accordion:
|
||||
<%include file="/courseware/course_navigation.html" args="active_page='courseware'" />
|
||||
% endif
|
||||
|
||||
<section class="container">
|
||||
<div class="course-wrapper">
|
||||
|
||||
% if accordion:
|
||||
<section aria-label="Course Navigation" class="course-index">
|
||||
<header id="open_close_accordion">
|
||||
<a href="#">close</a>
|
||||
@@ -76,6 +139,7 @@
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
% endif
|
||||
|
||||
<section class="course-content">
|
||||
${content}
|
||||
|
||||
@@ -29,13 +29,18 @@
|
||||
|
||||
<body class="<%block name='bodyclass'/>">
|
||||
|
||||
% if not suppress_toplevel_navigation:
|
||||
<%include file="navigation.html" />
|
||||
% endif
|
||||
|
||||
<section class="content-wrapper">
|
||||
${self.body()}
|
||||
<%block name="bodyextra"/>
|
||||
</section>
|
||||
|
||||
% if not suppress_toplevel_navigation:
|
||||
<%include file="footer.html" />
|
||||
% endif
|
||||
|
||||
<%static:js group='application'/>
|
||||
<%static:js group='module-js'/>
|
||||
|
||||
Reference in New Issue
Block a user