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:
helmutm 2007-05-04 13:11:27 +00:00
parent 618ea75bb5
commit 9ca359ca67
5 changed files with 272 additions and 0 deletions

40
scorm/README.txt Normal file
View 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
View file

@ -0,0 +1,4 @@
"""
$Id$
"""

110
scorm/base.py Normal file
View 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
View 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
View 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')