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
1685f302
Commit
1685f302
authored
Feb 04, 2013
by
Brian Wilson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add TimerModule to courseware
parent
9d98b705
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
246 additions
and
12 deletions
+246
-12
lms/djangoapps/courseware/migrations/0006_add_timed_module.py
+120
-0
lms/djangoapps/courseware/models.py
+82
-6
lms/djangoapps/courseware/views.py
+44
-6
No files found.
lms/djangoapps/courseware/migrations/0006_add_timed_module.py
0 → 100644
View file @
1685f302
# -*- 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
)),
(
'module_state_key'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
255
,
db_column
=
'module_id'
,
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', 'module_state_key', 'course_id']
db
.
create_unique
(
'courseware_timedmodule'
,
[
'student_id'
,
'module_id'
,
'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'
])
# 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', 'module_state_key', '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'
}),
'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']"
})
}
}
complete_apps
=
[
'courseware'
]
\ No newline at end of file
lms/djangoapps/courseware/models.py
View file @
1685f302
...
...
@@ -12,15 +12,12 @@ file and check it in at the same time as your model changes. To do that,
ASSUMPTIONS: modules have unique IDs, even across different module_types
"""
from
datetime
import
datetime
,
timedelta
from
calendar
import
timegm
from
django.db
import
models
#from django.core.cache import cache
from
django.contrib.auth.models
import
User
#from cache_toolbox import cache_model, cache_relation
#CACHE_TIMEOUT = 60 * 60 * 4 # Set the cache timeout to be four hours
class
StudentModule
(
models
.
Model
):
"""
Keeps student state for a particular module in a particular course.
...
...
@@ -214,3 +211,82 @@ 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'
)
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'
),)
# 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'
),
)
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
)
# 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 @
1685f302
...
...
@@ -2,7 +2,6 @@ import logging
import
urllib
from
functools
import
partial
from
time
import
time
from
django.conf
import
settings
from
django.core.context_processors
import
csrf
...
...
@@ -21,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
StudentModuleCache
from
courseware.models
import
StudentModuleCache
,
TimedModule
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
...
...
@@ -402,16 +401,55 @@ def timed_exam(request, course_id, chapter, section):
# 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
# for now, assume that the duration is set as an integer value, indicating the number of seconds:
duration
=
int
(
section_descriptor
.
metadata
.
get
(
'duration'
))
# get corresponding time module, if one is present:
# TODO: determine what to use for module_key...
try
:
timed_module
=
TimedModule
.
objects
.
get
(
student
=
request
.
user
,
course_id
=
course_id
)
# 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,
raise
Exception
(
"Time expired on {}"
.
format
(
timed_module
))
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.
# TODO: determine what to use for module_key...
timed_module
=
TimedModule
(
student
=
request
.
user
,
course_id
=
course_id
)
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'
]
=
(
time
()
+
duration
)
*
1000
context
[
'end_date'
]
=
end_date
result
=
render_to_response
(
'courseware/testcenter_exam.html'
,
context
)
except
Exception
as
e
:
...
...
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