scorm interface: add flexibility/configurability for identification of interaction records
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1952 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
f0196f5cc6
commit
89a3581e5b
2 changed files with 60 additions and 44 deletions
|
@ -37,11 +37,11 @@ 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 additional track for all the other elements.
|
||||
|
||||
>>> for t in sorted(tracks.values(), key=lambda x: x.timeStamp):
|
||||
>>> for t in sorted(tracks.values(), key=lambda x: x.data['recnum']):
|
||||
... print t.data
|
||||
{'id': 'q007', 'key_prefix': 'cmi.interactions.0', 'result': 'correct'}
|
||||
{'cmi.comments_from_learner.comment': 'Hello SCORM', 'key_prefix': ''}
|
||||
{'id': 'q009', 'key_prefix': 'cmi.interactions.1', 'result': 'incorrect'}
|
||||
{'recnum': -1, 'cmi.comments_from_learner.comment': 'Hello SCORM'}
|
||||
{'cmi.interactions.0.id': 'q007', 'cmi.interactions.0.result': 'correct', 'recnum': 0}
|
||||
{'cmi.interactions.1.result': 'incorrect', 'cmi.interactions.1.id': 'q009', 'recnum': 1}
|
||||
|
||||
Using the getValue() method we can retrieve certain values without having
|
||||
to care about the storage in different track objects.
|
||||
|
@ -69,12 +69,12 @@ We can also query special elements like _count and _children.
|
|||
We may also update existing tracks using the ``setValue()`` method.
|
||||
|
||||
>>> rc = api.setValue('cmi.comments_from_learner.location', 'q007')
|
||||
>>> for t in sorted(tracks.values(), key=lambda x: x.timeStamp):
|
||||
>>> for t in sorted(tracks.values(), key=lambda x: x.data['recnum']):
|
||||
... print t.data
|
||||
{'id': 'q007', 'key_prefix': 'cmi.interactions.0', 'result': 'correct'}
|
||||
{'cmi.comments_from_learner.location': 'q007',
|
||||
'cmi.comments_from_learner.comment': 'Hello SCORM', 'key_prefix': ''}
|
||||
{'id': 'q009', 'key_prefix': 'cmi.interactions.1', 'result': 'incorrect'}
|
||||
{'recnum': -1, 'cmi.comments_from_learner.comment': 'Hello SCORM',
|
||||
'cmi.comments_from_learner.location': 'q007'}
|
||||
{'cmi.interactions.0.id': 'q007', 'cmi.interactions.0.result': 'correct', 'recnum': 0}
|
||||
{'cmi.interactions.1.result': 'incorrect', 'cmi.interactions.1.id': 'q009', 'recnum': 1}
|
||||
|
||||
With the ``setValues()`` method we may set more than one element with
|
||||
one call. (This is not a SCORM-compliant call but is provided for efficiency
|
||||
|
@ -86,14 +86,14 @@ XML-RPC call.)
|
|||
... }
|
||||
>>> rc = api.setValues(data)
|
||||
|
||||
>>> for t in sorted(tracks.values(), key=lambda x: x.timeStamp):
|
||||
>>> for t in sorted(tracks.values(), key=lambda x: x.data['recnum']):
|
||||
... print t.data
|
||||
{'id': 'q007', 'key_prefix': 'cmi.interactions.0', 'result': 'correct'}
|
||||
{'cmi.comments_from_learner.location': 'q007',
|
||||
'cmi.comments_from_learner.comment': 'Hello SCORM', 'key_prefix': ''}
|
||||
{'id': 'q009', 'key_prefix': 'cmi.interactions.1', 'result': 'incorrect'}
|
||||
{'result': 'correct', 'key_prefix': 'cmi.interactions.2',
|
||||
'learner_response': 'my answer'}
|
||||
{'recnum': -1, 'cmi.comments_from_learner.comment': 'Hello SCORM',
|
||||
'cmi.comments_from_learner.location': 'q007'}
|
||||
{'cmi.interactions.0.id': 'q007', 'cmi.interactions.0.result': 'correct', 'recnum': 0}
|
||||
{'cmi.interactions.1.result': 'incorrect', 'cmi.interactions.1.id': 'q009', 'recnum': 1}
|
||||
{'cmi.interactions.2.learner_response': 'my answer',
|
||||
'cmi.interactions.2.result': 'correct', 'recnum': 2}
|
||||
|
||||
>>> api.getValue('cmi.interactions.2.result')
|
||||
('correct', '0')
|
||||
|
|
|
@ -33,7 +33,9 @@ from cybertools.tracking.interfaces import ITrackingStorage
|
|||
|
||||
OK = '0'
|
||||
|
||||
_children = {
|
||||
scormInteractionsPrefixes = ['cmi.interactions.']
|
||||
|
||||
scormChildren = {
|
||||
'cmi.comments_from_learner': ('comment', 'location', 'timestamp'),
|
||||
'cmi.comments_from_lms': ('comment', 'location', 'timestamp'),
|
||||
'cmi.interactions': ('id', 'type', 'objectives', 'timestamp',
|
||||
|
@ -43,10 +45,12 @@ _children = {
|
|||
'delivery_speed', 'audio_captioning'),
|
||||
'cmi.objectives': ('id', 'score', 'success_status', 'completion_status',
|
||||
'description'),
|
||||
'score': ('scaled', 'raw', 'min', 'max'),
|
||||
('cmi', 'score'): ('scaled', 'raw', 'min', 'max'),
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class ScormAPI(object):
|
||||
""" ScormAPI objects are temporary adapters created by
|
||||
browser or XML-RPC views.
|
||||
|
@ -79,12 +83,12 @@ class ScormAPI(object):
|
|||
|
||||
def setValue(self, element, value):
|
||||
tracks = self.context.getUserTracks(self.taskId, self.runId, self.userId)
|
||||
prefix, key = self._splitKey(element)
|
||||
track = self._getTrack(tracks, prefix)
|
||||
recnum = self._getRecnum(element)
|
||||
track = self._getTrack(tracks, recnum)
|
||||
data = track is not None and track.data or {}
|
||||
data[key] = value
|
||||
data[element] = value
|
||||
if track is None:
|
||||
data['key_prefix'] = prefix
|
||||
data['recnum'] = recnum
|
||||
self.context.saveUserTrack(self.taskId, self.runId, self.userId, data)
|
||||
else:
|
||||
self.context.updateTrack(track, data)
|
||||
|
@ -103,23 +107,23 @@ class ScormAPI(object):
|
|||
tracks = self.context.getUserTracks(self.taskId, self.runId, self.userId)
|
||||
if element.endswith('._count'):
|
||||
base = element[:-len('._count')]
|
||||
if element.startswith('cmi.interactions.'):
|
||||
return self._countSubtracks(tracks, base), OK
|
||||
else:
|
||||
track = self._getTrack(tracks, '')
|
||||
for prefix in scormInteractionsPrefixes:
|
||||
if element.startswith(prefix):
|
||||
return self._countInteractionTracks(tracks), OK
|
||||
track = self._getTrack(tracks, -1)
|
||||
if track is None:
|
||||
return 0, OK
|
||||
return self._countSubelements(track.data, base), OK
|
||||
if element.endswith('_children'):
|
||||
base = element[:-len('._children')]
|
||||
return self._getChildren(base)
|
||||
prefix, key = self._splitKey(element)
|
||||
track = self._getTrack(tracks, prefix)
|
||||
recnum = self._getRecnum(element)
|
||||
track = self._getTrack(tracks, recnum)
|
||||
if track is None:
|
||||
return '', '403'
|
||||
data = track.data
|
||||
if key in data:
|
||||
return data[key], OK
|
||||
if element in data:
|
||||
return data[element], OK
|
||||
else:
|
||||
return '', '403'
|
||||
|
||||
|
@ -131,15 +135,22 @@ class ScormAPI(object):
|
|||
|
||||
# helper methods
|
||||
|
||||
def _getRecnum(self, element):
|
||||
for prefix in scormInteractionsPrefixes:
|
||||
if element.startswith(prefix):
|
||||
# interaction record
|
||||
return int(element[len(prefix):].split('.', 1)[0])
|
||||
return -1 # base record
|
||||
|
||||
def _splitKey(self, element):
|
||||
if element.startswith('cmi.interactions.'):
|
||||
parts = element.split('.')
|
||||
return '.'.join(parts[:3]), '.'.join(parts[3:])
|
||||
return '', element
|
||||
|
||||
def _getTrack(self, tracks, prefix):
|
||||
for tr in reversed(sorted(tracks, key=lambda x: x.timeStamp)):
|
||||
if tr and tr.data.get('key_prefix', None) == prefix:
|
||||
def _getTrack(self, tracks, recnum):
|
||||
for tr in tracks:
|
||||
if tr.data['recnum'] == recnum:
|
||||
return tr
|
||||
return None
|
||||
|
||||
|
@ -150,14 +161,19 @@ class ScormAPI(object):
|
|||
result.add(key)
|
||||
return len(result)
|
||||
|
||||
def _countSubtracks(self, tracks, base):
|
||||
return len([tr for tr in tracks if tr.data.get('key_prefix').startswith(base)])
|
||||
def _countInteractionTracks(self, tracks):
|
||||
return len([tr for tr in tracks
|
||||
if tr.data.get('recnum', -1) >= 0])
|
||||
|
||||
def _getChildren(self, base):
|
||||
if base.endswith('.score'):
|
||||
base = 'score'
|
||||
if base in _children:
|
||||
return _children[base], OK
|
||||
else:
|
||||
if base in scormChildren:
|
||||
return scormChildren[base], OK
|
||||
parts = base.split('.')
|
||||
if len(parts) >= 2:
|
||||
# this may be somewhat simplistic, but should cover the
|
||||
# most common cases
|
||||
key = (parts[0], parts[-1])
|
||||
if key in scormChildren:
|
||||
return scormChildren[key], OK
|
||||
return '', '401'
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue