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
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
83 additions
and
291 deletions
+83
-291
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
+0
-0
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
This diff is collapsed.
Click to expand it.
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