added cybertools.scorm package
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1711 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
618ea75bb5
commit
9ca359ca67
5 changed files with 272 additions and 0 deletions
40
scorm/README.txt
Normal file
40
scorm/README.txt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
===================
|
||||||
|
A generic SCORM API
|
||||||
|
===================
|
||||||
|
|
||||||
|
($Id$)
|
||||||
|
|
||||||
|
In order to work with the SCORM API we first need a tracking storage.
|
||||||
|
|
||||||
|
>>> from cybertools.tracking.btree import TrackingStorage
|
||||||
|
>>> tracks = TrackingStorage()
|
||||||
|
|
||||||
|
We can now create a ScormAPI adapter. Note that this adapter is stateless
|
||||||
|
as it is usually created anew upon each request.
|
||||||
|
|
||||||
|
>>> from cybertools.scorm.base import ScormAPI
|
||||||
|
>>> api = ScormAPI(tracks, 'a001', 0, 'user1')
|
||||||
|
|
||||||
|
The first step is always the initialize() call - though in our case it
|
||||||
|
does not do anything.
|
||||||
|
|
||||||
|
>>> api.initialize()
|
||||||
|
'0'
|
||||||
|
|
||||||
|
Then we can set some values.
|
||||||
|
|
||||||
|
>>> rc = api.setValue('cmi.interactions.0.id', 'q007')
|
||||||
|
>>> rc = api.setValue('cmi.interactions.0.result', 'correct')
|
||||||
|
>>> rc = api.setValue('cmi.comments_from_learner', 'Hello SCORM')
|
||||||
|
>>> rc = api.setValue('cmi.interactions.1.id', 'q009')
|
||||||
|
>>> rc = api.setValue('cmi.interactions.1.result', 'incorrect')
|
||||||
|
|
||||||
|
Depending on the data elements the values entered are kept together in
|
||||||
|
one track or stored in separate track objects. So there is a separate
|
||||||
|
track for each interaction and one track for all the other elements.
|
||||||
|
|
||||||
|
>>> for t in sorted(tracks.values(), key=lambda x: x.timeStamp):
|
||||||
|
... print t.data
|
||||||
|
{'id': 'q007', 'key_prefix': 'cmi.interactions.0', 'result': 'correct'}
|
||||||
|
{'cmi.comments_from_learner': 'Hello SCORM', 'key_prefix': ''}
|
||||||
|
{'id': 'q009', 'key_prefix': 'cmi.interactions.1', 'result': 'incorrect'}
|
4
scorm/__init__.py
Normal file
4
scorm/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
110
scorm/base.py
Normal file
110
scorm/base.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base classes for providing a generic SCORM-compliant API.
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope import interface
|
||||||
|
from zope.interface import implements
|
||||||
|
|
||||||
|
from cybertools.scorm.interfaces import IScormAPI
|
||||||
|
from cybertools.tracking.btree import TrackingStorage
|
||||||
|
|
||||||
|
|
||||||
|
OK = '0'
|
||||||
|
|
||||||
|
|
||||||
|
class ScormAPI(object):
|
||||||
|
""" ScormAPI objects are temporary adapters created by
|
||||||
|
browser or XML-RPC views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
implements(IScormAPI)
|
||||||
|
|
||||||
|
def __init__(self, storage, taskId, runId, userId):
|
||||||
|
self.taskId = taskId
|
||||||
|
self.runId = runId
|
||||||
|
self.userId = userId
|
||||||
|
self.storage = storage
|
||||||
|
|
||||||
|
def initialize(self, parameter=''):
|
||||||
|
# Note that the run has already been started upon SCO launch, the runId
|
||||||
|
# usually being part of the URI or XML-RPC call arguments.
|
||||||
|
return OK
|
||||||
|
|
||||||
|
def terminate(self, parameter=''):
|
||||||
|
rc = self.commit()
|
||||||
|
if rc == OK:
|
||||||
|
self.storage.stopRun(self.taskId, self.runId)
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def commit(self, parameter=''):
|
||||||
|
return OK
|
||||||
|
|
||||||
|
def setValue(self, element, value):
|
||||||
|
tracks = self.storage.getUserTracks(self.taskId, self.runId, self.userId)
|
||||||
|
prefix, key = self._splitKey(element)
|
||||||
|
data = self._getTrackData(tracks, prefix) or {}
|
||||||
|
update = bool(data)
|
||||||
|
data['key_prefix'] = prefix
|
||||||
|
data.update({key: value})
|
||||||
|
self.storage.saveUserTrack(self.taskId, self.runId, self.userId, data,
|
||||||
|
update=update)
|
||||||
|
return OK
|
||||||
|
|
||||||
|
def setValues(self, mapping={}, **kw):
|
||||||
|
mapping.update(kw)
|
||||||
|
# TODO: optimize, i.e. retrieve existing tracks only once.
|
||||||
|
for key, value in mapping:
|
||||||
|
rc = self.setValue(key, value)
|
||||||
|
if rc != OK:
|
||||||
|
return rc
|
||||||
|
return OK
|
||||||
|
|
||||||
|
def getValue(self, element):
|
||||||
|
tracks = self.storage.getUserTracks(self.taskId, self.runId, self.userId)
|
||||||
|
prefix, key = self._splitKey(element)
|
||||||
|
data = self._getTrackData(tracks, prefix) or {}
|
||||||
|
if key in data:
|
||||||
|
return data[key], OK
|
||||||
|
else:
|
||||||
|
return '', '403'
|
||||||
|
|
||||||
|
def getErrorString(self, errorCode):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def getDiagnostic(self, code):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# helper methods
|
||||||
|
|
||||||
|
def _splitKey(self, element):
|
||||||
|
if element.startswith('cmi.interactions.'):
|
||||||
|
parts = element.split('.')
|
||||||
|
return '.'.join(parts[:3]), '.'.join(parts[3:])
|
||||||
|
return '', element
|
||||||
|
|
||||||
|
def _getTrackData(self, tracks, prefix):
|
||||||
|
track = None
|
||||||
|
for tr in reversed(sorted(tracks, key=lambda x: x.timeStamp)):
|
||||||
|
if tr and tr.data.get('key_prefix', None) == prefix:
|
||||||
|
return tr.data
|
||||||
|
return {}
|
96
scorm/interfaces.py
Normal file
96
scorm/interfaces.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
SCORM interface definitions for API_1484_11.
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.interface import Interface, Attribute
|
||||||
|
from zope import schema
|
||||||
|
|
||||||
|
|
||||||
|
class IScormAPI(Interface):
|
||||||
|
""" This interface represents a server-side adapter object for a
|
||||||
|
tracking storage and a set of key/meta data that identify a
|
||||||
|
learner session with one or more track objects. IScormAPI objects
|
||||||
|
are stateless, so they don't remember any values between calls.
|
||||||
|
|
||||||
|
In addition to the standard SCORM RTS methods there is a setValues()
|
||||||
|
method that allows setting more than one value in one call,
|
||||||
|
probably during execution of a Commit() call on the client
|
||||||
|
side.
|
||||||
|
|
||||||
|
There is no method corresponding to GetLastError() as the
|
||||||
|
methods immediately return an appropriate CMIErrorCode,
|
||||||
|
i.e. a '0' when OK.
|
||||||
|
|
||||||
|
Note that the names of the methods have been slightly modified
|
||||||
|
to correspond to the Python programming style guides.
|
||||||
|
"""
|
||||||
|
|
||||||
|
taskId = Attribute('Task ID')
|
||||||
|
runId = Attribute('Run ID (integer)')
|
||||||
|
userId = Attribute('User ID')
|
||||||
|
|
||||||
|
def initialize(parameter):
|
||||||
|
""" Corresponds to API.Initialize('').
|
||||||
|
Return CMIErrorCode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def commit(parameter):
|
||||||
|
""" Corresponds to API.Commit('').
|
||||||
|
Return CMIErrorCode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def terminate(parameter):
|
||||||
|
""" Corresponds to API.Initialize('').
|
||||||
|
Mark the run as finished.
|
||||||
|
Return CMIErrorCode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setValue(element, value):
|
||||||
|
""" Corresponds to API.SetValue(element, value).
|
||||||
|
Return CMIErrorCode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setValues(mapping={}, **kw):
|
||||||
|
""" Combine the mapping and kw arguments setting up a series of
|
||||||
|
element-value mappings that will in turn be applied to a
|
||||||
|
series of setValue() calls.
|
||||||
|
Return CMIErrorCode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def getValue(element):
|
||||||
|
""" Corresponds to API.GetValue(element).
|
||||||
|
Return a tuple with the current value of the element given
|
||||||
|
(a string, '' if not present) and a CMIErrorCode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def getErrorString(errorCode):
|
||||||
|
""" Corresponds to API.GetErrorString(errorCode).
|
||||||
|
Return the error text belonging to the errorCode
|
||||||
|
(a CMIErrorCode value) given.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def getDiagnostic(code):
|
||||||
|
""" Corresponds to API.GetDiagnostic(code).
|
||||||
|
Return an LMS-specific information text related to the code given;
|
||||||
|
code may but need not be a CMIErrorCode value.
|
||||||
|
"""
|
22
scorm/tests.py
Executable file
22
scorm/tests.py
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
# $Id$
|
||||||
|
|
||||||
|
import unittest, doctest
|
||||||
|
from zope.testing.doctestunit import DocFileSuite
|
||||||
|
|
||||||
|
|
||||||
|
class Test(unittest.TestCase):
|
||||||
|
"Basic tests for the cybertools.scorm package."
|
||||||
|
|
||||||
|
def testBasics(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_suite():
|
||||||
|
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||||
|
return unittest.TestSuite((
|
||||||
|
unittest.makeSuite(Test),
|
||||||
|
DocFileSuite('README.txt', optionflags=flags),
|
||||||
|
))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(defaultTest='test_suite')
|
Loading…
Add table
Reference in a new issue