Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
cc11dc2a
Commit
cc11dc2a
authored
Feb 07, 2013
by
Brian Wilson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
switch to using timelimit module for Pearson test
parent
33a5a5fd
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
173 additions
and
525 deletions
+173
-525
common/djangoapps/student/views.py
+58
-38
common/lib/xmodule/setup.py
+1
-1
common/lib/xmodule/xmodule/course_module.py
+5
-1
common/lib/xmodule/xmodule/timelimit_module.py
+19
-37
lms/djangoapps/courseware/migrations/0006_add_timed_module.py
+0
-120
lms/djangoapps/courseware/models.py
+0
-84
lms/djangoapps/courseware/views.py
+90
-234
lms/urls.py
+0
-10
No files found.
common/djangoapps/student/views.py
View file @
cc11dc2a
...
...
@@ -39,12 +39,15 @@ from certificates.models import CertificateStatuses, certificate_status_for_stud
from
xmodule.course_module
import
CourseDescriptor
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore
import
Location
from
collections
import
namedtuple
from
courseware.courses
import
get_courses
,
sort_by_announcement
from
courseware.access
import
has_access
from
courseware.models
import
TimedModule
from
courseware.models
import
StudentModuleCache
from
courseware.views
import
get_module_for_descriptor
from
courseware.module_render
import
get_instance_module
from
statsd
import
statsd
...
...
@@ -1082,13 +1085,14 @@ def test_center_login(request):
# errors are returned by navigating to the error_url, adding a query parameter named "code"
# which contains the error code describing the exceptional condition.
def
makeErrorURL
(
error_url
,
error_code
):
return
"{}&code={}"
.
format
(
error_url
,
error_code
);
log
.
error
(
"generating error URL with error code {}"
.
format
(
error_code
))
return
"{}?code={}"
.
format
(
error_url
,
error_code
);
# get provided error URL, which will be used as a known prefix for returning error messages to the
# Pearson shell.
It does not have a trailing slash, so we need to add one when creating output URLs.
# Pearson shell.
error_url
=
request
.
POST
.
get
(
"errorURL"
)
# check that the parameters have not been tampered with, by comparing the code provided by Pearson
#
TODO:
check that the parameters have not been tampered with, by comparing the code provided by Pearson
# with the code we calculate for the same parameters.
if
'code'
not
in
request
.
POST
:
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"missingSecurityCode"
));
...
...
@@ -1112,65 +1116,81 @@ def test_center_login(request):
try
:
testcenteruser
=
TestCenterUser
.
objects
.
get
(
client_candidate_id
=
client_candidate_id
)
except
TestCenterUser
.
DoesNotExist
:
log
.
error
(
"not able to find demographics for cand ID {}"
.
format
(
client_candidate_id
))
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"invalidClientCandidateID"
));
# find testcenter_registration that matches the provided exam code:
# Note that we could rely on either the registrationId or the exam code,
# or possibly both.
# Note that we could rely in future on either the registrationId or the exam code,
# or possibly both. But for now we know what to do with an ExamSeriesCode,
# while we currently have no record of RegistrationID values at all.
if
'vueExamSeriesCode'
not
in
request
.
POST
:
# TODO: confirm this error code (made up, not in documentation)
log
.
error
(
"missing exam series code for cand ID {}"
.
format
(
client_candidate_id
))
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"missingExamSeriesCode"
));
exam_series_code
=
request
.
POST
.
get
(
'vueExamSeriesCode'
)
registrations
=
TestCenterRegistration
.
objects
.
filter
(
testcenter_user
=
testcenteruser
,
exam_series_code
=
exam_series_code
)
if
not
registrations
:
log
.
error
(
"not able to find exam registration for exam {} and cand ID {}"
.
format
(
exam_series_code
,
client_candidate_id
))
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"noTestsAssigned"
));
# TODO: figure out what to do if there are more than one registrations....
# for now, just take the first...
registration
=
registrations
[
0
]
course_id
=
registration
.
course_id
# if we want to look up whether the test has already been taken, or to
# communicate that a time accommodation needs to be applied, we need to
# 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'
:
# 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.
course_id
=
registration
.
course_id
course
=
course_from_id
(
course_id
)
# assume it will be found....
if
not
course
:
log
.
error
(
"not able to find course from ID {} for cand ID {}"
.
format
(
course_id
,
client_candidate_id
))
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"incorrectCandidateTests"
));
exam
=
course
.
get_test_center_exam
(
exam_series_code
)
if
not
exam
:
log
.
error
(
"not able to find exam {} for course ID {} and cand ID {}"
.
format
(
exam_series_code
,
course_id
,
client_candidate_id
))
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"incorrectCandidateTests"
));
location
=
exam
.
exam_url
redirect_url
=
reverse
(
'jump_to'
,
kwargs
=
{
'course_id'
:
course_id
,
'location'
:
location
})
log
.
info
(
"proceeding with test of cand {} on exam {} for course {}: URL = {}"
.
format
(
client_candidate_id
,
exam_series_code
,
course_id
,
location
))
# check if the test has already been taken
timelimit_descriptor
=
modulestore
()
.
get_instance
(
course_id
,
Location
(
location
))
if
not
timelimit_descriptor
:
log
.
error
(
"cand {} on exam {} for course {}: descriptor not found for location {}"
.
format
(
client_candidate_id
,
exam_series_code
,
course_id
,
location
))
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"missingClientProgram"
));
timelimit_module_cache
=
StudentModuleCache
.
cache_for_descriptor_descendents
(
course_id
,
testcenteruser
.
user
,
timelimit_descriptor
,
depth
=
None
)
timelimit_module
=
get_module_for_descriptor
(
request
.
user
,
request
,
timelimit_descriptor
,
timelimit_module_cache
,
course_id
,
position
=
None
)
if
not
timelimit_module
.
category
==
'timelimit'
:
log
.
error
(
"cand {} on exam {} for course {}: non-timelimit module at location {}"
.
format
(
client_candidate_id
,
exam_series_code
,
course_id
,
location
))
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"missingClientProgram"
));
if
timelimit_module
and
timelimit_module
.
has_ended
:
log
.
warning
(
"cand {} on exam {} for course {}: test already over at {}"
.
format
(
client_candidate_id
,
exam_series_code
,
course_id
,
timelimit_module
.
ending_at
))
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"allTestsTaken"
));
# check if we need to provide an accommodation:
time_accommodation_mapping
=
{
'ET12ET'
:
'ADDHALFTIME'
,
'ET30MN'
:
'ADD30MIN'
,
'ETDBTM'
:
'ADDDOUBLE'
,
}
# check if the test has already been taken
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
:
return
HttpResponseRedirect
(
makeErrorURL
(
error_url
,
"allTestsTaken"
));
elif
registration
.
get_accommodation_codes
():
# we don't have a timed module created yet, so if we have time accommodations
# to implement, create an entry now:
time_accommodation_code
=
None
time_accommodation_code
=
None
if
registration
.
get_accommodation_codes
():
for
code
in
registration
.
get_accommodation_codes
():
if
code
in
time_accommodation_mapping
:
time_accommodation_code
=
time_accommodation_mapping
[
code
]
if
client_candidate_id
==
"edX003671291147"
:
time_accommodation_code
=
'TESTING'
if
time_accommodation_code
:
timed_module
=
TimedModule
(
student
=
request
.
user
,
course_id
=
course_id
,
location
=
location
)
timed_module
.
accommodation_code
=
time_accommodation_code
timed_module
.
save
()
# special, hard-coded client ID used by Pearson shell for testing:
if
client_candidate_id
==
"edX003671291147"
:
time_accommodation_code
=
'TESTING'
if
time_accommodation_code
:
timelimit_module
.
accommodation_code
=
time_accommodation_code
instance_module
=
get_instance_module
(
course_id
,
testcenteruser
.
user
,
timelimit_module
,
timelimit_module_cache
)
instance_module
.
state
=
timelimit_module
.
get_instance_state
()
instance_module
.
save
()
log
.
info
(
"cand {} on exam {} for course {}: receiving accommodation {}"
.
format
(
client_candidate_id
,
exam_series_code
,
course_id
,
time_accommodation_code
))
# UGLY HACK!!!
# Login assumes that authentication has occurred, and that there is a
...
...
common/lib/xmodule/setup.py
View file @
cc11dc2a
...
...
@@ -23,7 +23,6 @@ setup(
"course = xmodule.course_module:CourseDescriptor"
,
"customtag = xmodule.template_module:CustomTagDescriptor"
,
"discuss = xmodule.backcompat_module:TranslateCustomTagDescriptor"
,
"fixedtime = xmodule.fixed_time_module:FixedTimeDescriptor"
,
"html = xmodule.html_module:HtmlDescriptor"
,
"image = xmodule.backcompat_module:TranslateCustomTagDescriptor"
,
"error = xmodule.error_module:ErrorDescriptor"
,
...
...
@@ -32,6 +31,7 @@ setup(
"section = xmodule.backcompat_module:SemanticSectionDescriptor"
,
"sequential = xmodule.seq_module:SequenceDescriptor"
,
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor"
,
"timelimit = xmodule.timelimit_module:TimeLimitDescriptor"
,
"vertical = xmodule.vertical_module:VerticalDescriptor"
,
"video = xmodule.video_module:VideoDescriptor"
,
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor"
,
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
cc11dc2a
...
...
@@ -648,7 +648,7 @@ class CourseDescriptor(SequenceDescriptor):
raise
ValueError
(
"First appointment date must be before last appointment date"
)
if
self
.
registration_end_date
>
self
.
last_eligible_appointment_date
:
raise
ValueError
(
"Registration end date must be before last appointment date"
)
self
.
exam_url
=
exam_info
.
get
(
'Exam_URL'
)
def
_try_parse_time
(
self
,
key
):
"""
...
...
@@ -704,6 +704,10 @@ class CourseDescriptor(SequenceDescriptor):
else
:
return
None
def
get_test_center_exam
(
self
,
exam_series_code
):
exams
=
[
exam
for
exam
in
self
.
test_center_exams
if
exam
.
exam_series_code
==
exam_series_code
]
return
exams
[
0
]
if
len
(
exams
)
==
1
else
None
@property
def
title
(
self
):
return
self
.
display_name
...
...
common/lib/xmodule/xmodule/
fixed_time
_module.py
→
common/lib/xmodule/xmodule/
timelimit
_module.py
View file @
cc11dc2a
...
...
@@ -4,22 +4,16 @@ import logging
from
lxml
import
etree
from
time
import
time
from
xmodule.
mako_module
import
MakoModule
Descriptor
from
xmodule.
editing_module
import
XMLEditing
Descriptor
from
xmodule.xml_module
import
XmlDescriptor
from
xmodule.x_module
import
XModule
from
xmodule.progress
import
Progress
from
xmodule.exceptions
import
NotFoundError
from
pkg_resources
import
resource_string
log
=
logging
.
getLogger
(
__name__
)
# HACK: This shouldn't be hard-coded to two types
# OBSOLETE: This obsoletes 'type'
# class_priority = ['video', 'problem']
class
FixedTimeModule
(
XModule
):
class
TimeLimitModule
(
XModule
):
'''
Wrapper module which imposes a time constraint for the completion of its child.
'''
...
...
@@ -29,9 +23,7 @@ class FixedTimeModule(XModule):
XModule
.
__init__
(
self
,
system
,
location
,
definition
,
descriptor
,
instance_state
,
shared_state
,
**
kwargs
)
# NOTE: Position is 1-indexed. This is silly, but there are now student
# positions saved on prod, so it's not easy to fix.
# self.position = 1
self
.
rendered
=
False
self
.
beginning_at
=
None
self
.
ending_at
=
None
self
.
accommodation_code
=
None
...
...
@@ -46,13 +38,6 @@ class FixedTimeModule(XModule):
if
'accommodation_code'
in
state
:
self
.
accommodation_code
=
state
[
'accommodation_code'
]
# if position is specified in system, then use that instead
# if system.get('position'):
# self.position = int(system.get('position'))
self
.
rendered
=
False
# For a timed activity, we are only interested here
# in time-related accommodations, and these should be disjoint.
# (For proctored exams, it is possible to have multiple accommodations
...
...
@@ -81,8 +66,6 @@ class FixedTimeModule(XModule):
elif
self
.
accommodation_code
==
'TESTING'
:
# when testing, set timer to run for a week at a time.
return
3600
*
24
*
7
# store state:
@property
def
has_begun
(
self
):
...
...
@@ -101,8 +84,6 @@ class FixedTimeModule(XModule):
'''
self
.
beginning_at
=
time
()
modified_duration
=
self
.
_get_accommodated_duration
(
duration
)
# datetime_duration = timedelta(seconds=modified_duration)
# self.ending_at = self.beginning_at + datetime_duration
self
.
ending_at
=
self
.
beginning_at
+
modified_duration
def
get_end_time_in_ms
(
self
):
...
...
@@ -132,31 +113,32 @@ class FixedTimeModule(XModule):
progress
=
reduce
(
Progress
.
add_counts
,
progresses
)
return
progress
def
handle_ajax
(
self
,
dispatch
,
get
):
# TODO: bounds checking
# ''' get = request.POST instance '''
# if dispatch == 'goto_position':
# self.position = int(get['position'])
# return json.dumps({'success': True})
def
handle_ajax
(
self
,
dispatch
,
get
):
raise
NotFoundError
(
'Unexpected dispatch type'
)
def
render
(
self
):
if
self
.
rendered
:
return
# assumes there is one and only one child, so it only renders the first child
child
=
self
.
get_display_items
()[
0
]
self
.
content
=
child
.
get_html
()
children
=
self
.
get_display_items
()
if
children
:
child
=
children
[
0
]
self
.
content
=
child
.
get_html
()
self
.
rendered
=
True
def
get_icon_class
(
self
):
return
self
.
get_children
()[
0
]
.
get_icon_class
()
children
=
self
.
get_children
()
if
children
:
return
children
[
0
]
.
get_icon_class
()
else
:
return
"other"
class
TimeLimitDescriptor
(
XMLEditingDescriptor
,
XmlDescriptor
):
class
FixedTimeDescriptor
(
MakoModuleDescriptor
,
XmlDescriptor
):
# TODO: fix this template?!
mako_template
=
'widgets/sequence-edit.html'
module_class
=
FixedTimeModule
module_class
=
TimeLimitModule
stores_state
=
True
# For remembering when a student started, and when they should end
# For remembering when a student started, and when they should end
stores_state
=
True
@classmethod
def
definition_from_xml
(
cls
,
xml_object
,
system
):
...
...
@@ -165,14 +147,14 @@ class FixedTimeDescriptor(MakoModuleDescriptor, XmlDescriptor):
try
:
children
.
append
(
system
.
process_xml
(
etree
.
tostring
(
child
,
encoding
=
'unicode'
))
.
location
.
url
())
except
Exception
as
e
:
log
.
exception
(
"Unable to load child when parsing
FixedTime
wrapper. Continuing..."
)
log
.
exception
(
"Unable to load child when parsing
TimeLimit
wrapper. Continuing..."
)
if
system
.
error_tracker
is
not
None
:
system
.
error_tracker
(
"ERROR: "
+
str
(
e
))
continue
return
{
'children'
:
children
}
def
definition_to_xml
(
self
,
resource_fs
):
xml_object
=
etree
.
Element
(
'
fixedtime
'
)
xml_object
=
etree
.
Element
(
'
timelimit
'
)
for
child
in
self
.
get_children
():
xml_object
.
append
(
etree
.
fromstring
(
child
.
export_to_xml
(
resource_fs
)))
...
...
lms/djangoapps/courseware/migrations/0006_add_timed_module.py
deleted
100644 → 0
View file @
33a5a5fd
# -*- coding: utf-8 -*-
import
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding model 'TimedModule'
db
.
create_table
(
'courseware_timedmodule'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
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
)),
(
'beginning_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
null
=
True
,
db_index
=
True
)),
(
'ending_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
null
=
True
,
db_index
=
True
)),
(
'created_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
auto_now_add
=
True
,
db_index
=
True
,
blank
=
True
)),
(
'modified_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
auto_now
=
True
,
db_index
=
True
,
blank
=
True
)),
))
db
.
send_create_signal
(
'courseware'
,
[
'TimedModule'
])
# 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', 'location', 'course_id']
db
.
delete_unique
(
'courseware_timedmodule'
,
[
'student_id'
,
'location'
,
'course_id'
])
# Deleting model 'TimedModule'
db
.
delete_table
(
'courseware_timedmodule'
)
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'courseware.offlinecomputedgrade'
:
{
'Meta'
:
{
'unique_together'
:
"(('user', 'course_id'),)"
,
'object_name'
:
'OfflineComputedGrade'
},
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'null'
:
'True'
,
'db_index'
:
'True'
,
'blank'
:
'True'
}),
'gradeset'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'updated'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'db_index'
:
'True'
,
'blank'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'courseware.offlinecomputedgradelog'
:
{
'Meta'
:
{
'ordering'
:
"['-created']"
,
'object_name'
:
'OfflineComputedGradeLog'
},
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'null'
:
'True'
,
'db_index'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'nstudents'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
}),
'seconds'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
})
},
'courseware.studentmodule'
:
{
'Meta'
:
{
'unique_together'
:
"(('student', 'module_state_key', 'course_id'),)"
,
'object_name'
:
'StudentModule'
},
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'db_index'
:
'True'
,
'blank'
:
'True'
}),
'done'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'na'"
,
'max_length'
:
'8'
,
'db_index'
:
'True'
}),
'grade'
:
(
'django.db.models.fields.FloatField'
,
[],
{
'db_index'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'max_grade'
:
(
'django.db.models.fields.FloatField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'modified'
:
(
'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'
}),
'module_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'problem'"
,
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'state'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'student'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'courseware.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'
}),
'student'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'courseware'
]
\ No newline at end of file
lms/djangoapps/courseware/models.py
View file @
cc11dc2a
...
...
@@ -212,87 +212,3 @@ class OfflineComputedGradeLog(models.Model):
def
__unicode__
(
self
):
return
"[OCGLog]
%
s:
%
s"
%
(
self
.
course_id
,
self
.
created
)
class
TimedModule
(
models
.
Model
):
"""
Keeps student state for a timed activity in a particular course.
Includes information about time accommodations granted,
time started, and ending time.
"""
## These three are the key for the object
# Key used to share state. By default, this is the module_id,
# 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')
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'
,
'location'
,
'course_id'
),)
# For a timed activity, we are only interested here
# in time-related accommodations, and these should be disjoint.
# (For proctored exams, it is possible to have multiple accommodations
# apply to an exam, so they require accommodating a multi-choice.)
TIME_ACCOMMODATION_CODES
=
((
'NONE'
,
'No Time Accommodation'
),
(
'ADDHALFTIME'
,
'Extra Time - 1 1/2 Time'
),
(
'ADD30MIN'
,
'Extra Time - 30 Minutes'
),
(
'DOUBLE'
,
'Extra Time - Double Time'
),
(
'TESTING'
,
'Extra Time -- Large amount for testing purposes'
)
)
accommodation_code
=
models
.
CharField
(
max_length
=
12
,
choices
=
TIME_ACCOMMODATION_CODES
,
default
=
'NONE'
,
db_index
=
True
)
def
_get_accommodated_duration
(
self
,
duration
):
'''
Get duration for activity, as adjusted for accommodations.
Input and output are expressed in seconds.
'''
if
self
.
accommodation_code
==
'NONE'
:
return
duration
elif
self
.
accommodation_code
==
'ADDHALFTIME'
:
# TODO: determine what type to return
return
int
(
duration
*
1.5
)
elif
self
.
accommodation_code
==
'ADD30MIN'
:
return
(
duration
+
(
30
*
60
))
elif
self
.
accommodation_code
==
'DOUBLE'
:
return
(
duration
*
2
)
elif
self
.
accommodation_code
==
'TESTING'
:
# when testing, set timer to run for a week at a time.
return
3600
*
24
*
7
# store state:
beginning_at
=
models
.
DateTimeField
(
null
=
True
,
db_index
=
True
)
ending_at
=
models
.
DateTimeField
(
null
=
True
,
db_index
=
True
)
created_at
=
models
.
DateTimeField
(
auto_now_add
=
True
,
db_index
=
True
)
modified_at
=
models
.
DateTimeField
(
auto_now
=
True
,
db_index
=
True
)
@property
def
has_begun
(
self
):
return
self
.
beginning_at
is
not
None
@property
def
has_ended
(
self
):
if
not
self
.
ending_at
:
return
False
return
self
.
ending_at
<
datetime
.
utcnow
()
def
begin
(
self
,
duration
):
'''
Sets the starting time and ending time for the activity,
based on the duration provided (in seconds).
'''
self
.
beginning_at
=
datetime
.
utcnow
()
modified_duration
=
self
.
_get_accommodated_duration
(
duration
)
datetime_duration
=
timedelta
(
seconds
=
modified_duration
)
self
.
ending_at
=
self
.
beginning_at
+
datetime_duration
def
get_end_time_in_ms
(
self
):
return
(
timegm
(
self
.
ending_at
.
timetuple
())
*
1000
)
def
__unicode__
(
self
):
return
'/'
.
join
([
self
.
course_id
,
self
.
student
.
username
,
self
.
module_state_key
])
lms/djangoapps/courseware/views.py
View file @
cc11dc2a
...
...
@@ -20,7 +20,7 @@ from courseware.access import has_access
from
courseware.courses
import
(
get_courses
,
get_course_with_access
,
get_courses_by_university
,
sort_by_announcement
)
import
courseware.tabs
as
tabs
from
courseware.models
import
StudentModule
Cache
,
TimedModul
e
from
courseware.models
import
StudentModule
,
StudentModuleCach
e
from
module_render
import
toc_for_course
,
get_module
,
get_instance_module
,
get_module_for_descriptor
from
django_comment_client.utils
import
get_discussion_title
...
...
@@ -31,6 +31,7 @@ from xmodule.modulestore import Location
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.exceptions
import
InvalidLocationError
,
ItemNotFoundError
,
NoPathToItem
from
xmodule.modulestore.search
import
path_to_location
#from xmodule.fixed_time_module import FixedTimeModule
import
comment_client
...
...
@@ -152,6 +153,80 @@ def save_child_position(seq_module, child_name, instance_module):
instance_module
.
state
=
seq_module
.
get_instance_state
()
instance_module
.
save
()
def
check_for_active_timelimit_module
(
request
,
course_id
,
course
):
'''
Looks for a timing module for the given user and course that is currently active.
If found, returns a context dict with timer-related values to enable display of time remaining.
'''
context
=
{}
timelimit_student_modules
=
StudentModule
.
objects
.
filter
(
student
=
request
.
user
,
course_id
=
course_id
,
module_type
=
'timelimit'
)
if
timelimit_student_modules
:
for
timelimit_student_module
in
timelimit_student_modules
:
# get the corresponding section_descriptor for the given StudentModel entry:
module_state_key
=
timelimit_student_module
.
module_state_key
timelimit_descriptor
=
modulestore
()
.
get_instance
(
course_id
,
Location
(
module_state_key
))
timelimit_module_cache
=
StudentModuleCache
.
cache_for_descriptor_descendents
(
course
.
id
,
request
.
user
,
timelimit_descriptor
,
depth
=
None
)
timelimit_module
=
get_module_for_descriptor
(
request
.
user
,
request
,
timelimit_descriptor
,
timelimit_module_cache
,
course
.
id
,
position
=
None
)
if
timelimit_module
is
not
None
and
timelimit_module
.
category
==
'timelimit'
and
\
timelimit_module
.
has_begun
and
not
timelimit_module
.
has_ended
:
location
=
timelimit_module
.
location
# determine where to go when the timer expires:
if
'time_expired_redirect_url'
not
in
timelimit_descriptor
.
metadata
:
# TODO: provide a better error
raise
Http404
time_expired_redirect_url
=
timelimit_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
=
timelimit_module
.
get_end_time_in_ms
()
context
[
'timer_expiration_datetime'
]
=
end_date
if
'suppress_toplevel_navigation'
in
timelimit_descriptor
.
metadata
:
context
[
'suppress_toplevel_navigation'
]
=
timelimit_descriptor
.
metadata
[
'suppress_toplevel_navigation'
]
return_url
=
reverse
(
'jump_to'
,
kwargs
=
{
'course_id'
:
course_id
,
'location'
:
location
})
context
[
'timer_navigation_return_url'
]
=
return_url
return
context
def
update_timelimit_module
(
user
,
course_id
,
student_module_cache
,
timelimit_descriptor
,
timelimit_module
):
'''
Updates the state of the provided timing module, starting it if it hasn't begun.
Returns dict with timer-related values to enable display of time remaining.
Returns 'timer_expiration_datetime' in dict if timer is still active, and not if timer has expired.
'''
context
=
{}
# determine where to go when the exam ends:
if
'time_expired_redirect_url'
not
in
timelimit_descriptor
.
metadata
:
# TODO: provide a better error
raise
Http404
time_expired_redirect_url
=
timelimit_descriptor
.
metadata
.
get
(
'time_expired_redirect_url'
)
context
[
'time_expired_redirect_url'
]
=
time_expired_redirect_url
if
not
timelimit_module
.
has_ended
:
if
not
timelimit_module
.
has_begun
:
# user has not started the exam, so start it now.
if
'duration'
not
in
timelimit_descriptor
.
metadata
:
# TODO: provide a better error
raise
Http404
# The user may have an accommodation that has been granted to them.
# This accommodation information should already be stored in the module's state.
duration
=
int
(
timelimit_descriptor
.
metadata
.
get
(
'duration'
))
timelimit_module
.
begin
(
duration
)
# we have changed state, so we need to persist the change:
instance_module
=
get_instance_module
(
course_id
,
user
,
timelimit_module
,
student_module_cache
)
instance_module
.
state
=
timelimit_module
.
get_instance_state
()
instance_module
.
save
()
# the exam has been started, either because the student is returning to the
# exam page, or because they have just visited it. 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.
context
[
'timer_expiration_datetime'
]
=
timelimit_module
.
get_end_time_in_ms
()
# also use the timed module to determine whether top-level navigation is visible:
if
'suppress_toplevel_navigation'
in
timelimit_descriptor
.
metadata
:
context
[
'suppress_toplevel_navigation'
]
=
timelimit_descriptor
.
metadata
[
'suppress_toplevel_navigation'
]
return
context
@login_required
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
...
...
@@ -215,43 +290,6 @@ 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
)
...
...
@@ -286,7 +324,20 @@ def index(request, course_id, chapter=None, section=None,
instance_module
=
get_instance_module
(
course_id
,
request
.
user
,
chapter_module
,
student_module_cache
)
save_child_position
(
chapter_module
,
section
,
instance_module
)
# check here if this section *is* a timed module.
if
section_module
.
category
==
'timelimit'
:
timer_context
=
update_timelimit_module
(
request
.
user
,
course_id
,
student_module_cache
,
section_descriptor
,
section_module
)
if
'timer_expiration_datetime'
in
timer_context
:
context
.
update
(
timer_context
)
else
:
# if there is no expiration defined, then we know the timer has expired:
return
HttpResponseRedirect
(
timer_context
[
'time_expired_redirect_url'
])
else
:
# check here if this page is within a course that has an active timed module running. If so, then
# add in the appropriate timer information to the rendering context:
context
.
update
(
check_for_active_timelimit_module
(
request
,
course_id
,
course
))
context
[
'content'
]
=
section_module
.
get_html
()
else
:
# section is none, so display a message
...
...
@@ -334,201 +385,6 @@ 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
timed_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'
],
'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'
)
}
# in general, we may want to disable accordion display on timed exams.
provide_accordion
=
True
if
provide_accordion
:
context
[
'accordion'
]
=
render_accordion
(
request
,
course
,
chapter
,
section
)
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
()
# determine where to go when the exam ends:
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
# figure out when the timed 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.
# get value for duration from the section's metadata:
# for now, assume that the duration is set as an integer value, indicating the number of seconds:
if
'duration'
not
in
section_descriptor
.
metadata
:
raise
Http404
duration
=
int
(
section_descriptor
.
metadata
.
get
(
'duration'
))
# get corresponding time module, if one is present:
try
:
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.
if
timed_module
.
has_ended
:
# the exam has already ended, and the student has tried to
# revisit the exam.
# TODO: determine what do we do here.
# For a Pearson exam, we want to go to the exit page.
# (Not so sure what to do in general.)
# Proposal: store URL in the section descriptor,
# along with the duration. If no such URL is set,
# just put up the error page,
if
time_expired_redirect_url
is
None
:
raise
Exception
(
"Time expired on {}"
.
format
(
timed_module
))
else
:
return
HttpResponseRedirect
(
time_expired_redirect_url
)
elif
not
timed_module
.
has_begun
:
# user has not started the exam, but may have an accommodation
# that has been granted to them.
# modified_duration = timed_module.get_accommodated_duration(duration)
# timed_module.started_at = datetime.utcnow() # time() * 1000
# timed_module.end_date = timed_module.
timed_module
.
begin
(
duration
)
timed_module
.
save
()
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
,
location
=
section_module
.
location
)
timed_module
.
begin
(
duration
)
timed_module
.
save
()
# the exam has already been started, and the student is returning to the
# exam page. Fetch the end time (in GMT) as stored
# in the module when it was started.
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
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
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
):
'''
...
...
lms/urls.py
View file @
cc11dc2a
...
...
@@ -217,16 +217,6 @@ if settings.COURSEWARE_ENABLED:
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$'
,
'courseware.views.course_about'
,
name
=
"about_course"
),
# timed exam:
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/timed_exam/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$'
,
'courseware.views.timed_exam'
,
name
=
"timed_exam"
),
# (handle hard-coded 6.002x exam explicitly as a timed exam, but without changing the URL.
# not only because Pearson doesn't want us to change its location, but because we also include it
# in the navigation accordion we display with this exam (so students can see what work they have already
# done). Those are generated automatically using reverse(courseware_section).
url
(
r'^courses/(?P<course_id>MITx/6.002x/2012_Fall)/courseware/(?P<chapter>Final_Exam)/(?P<section>Final_Exam_Fall_2012)/$'
,
'courseware.views.timed_exam'
),
#Inside the course
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/$'
,
'courseware.views.course_info'
,
name
=
"course_root"
),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment