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:
helmutm 2007-08-23 12:40:09 +00:00
parent f0196f5cc6
commit 89a3581e5b
2 changed files with 60 additions and 44 deletions

View file

@ -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 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. 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 ... print t.data
{'id': 'q007', 'key_prefix': 'cmi.interactions.0', 'result': 'correct'} {'recnum': -1, 'cmi.comments_from_learner.comment': 'Hello SCORM'}
{'cmi.comments_from_learner.comment': 'Hello SCORM', 'key_prefix': ''} {'cmi.interactions.0.id': 'q007', 'cmi.interactions.0.result': 'correct', 'recnum': 0}
{'id': 'q009', 'key_prefix': 'cmi.interactions.1', 'result': 'incorrect'} {'cmi.interactions.1.result': 'incorrect', 'cmi.interactions.1.id': 'q009', 'recnum': 1}
Using the getValue() method we can retrieve certain values without having Using the getValue() method we can retrieve certain values without having
to care about the storage in different track objects. 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. We may also update existing tracks using the ``setValue()`` method.
>>> rc = api.setValue('cmi.comments_from_learner.location', 'q007') >>> 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 ... print t.data
{'id': 'q007', 'key_prefix': 'cmi.interactions.0', 'result': 'correct'} {'recnum': -1, 'cmi.comments_from_learner.comment': 'Hello SCORM',
{'cmi.comments_from_learner.location': 'q007', 'cmi.comments_from_learner.location': 'q007'}
'cmi.comments_from_learner.comment': 'Hello SCORM', 'key_prefix': ''} {'cmi.interactions.0.id': 'q007', 'cmi.interactions.0.result': 'correct', 'recnum': 0}
{'id': 'q009', 'key_prefix': 'cmi.interactions.1', 'result': 'incorrect'} {'cmi.interactions.1.result': 'incorrect', 'cmi.interactions.1.id': 'q009', 'recnum': 1}
With the ``setValues()`` method we may set more than one element with 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 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) >>> 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 ... print t.data
{'id': 'q007', 'key_prefix': 'cmi.interactions.0', 'result': 'correct'} {'recnum': -1, 'cmi.comments_from_learner.comment': 'Hello SCORM',
{'cmi.comments_from_learner.location': 'q007', 'cmi.comments_from_learner.location': 'q007'}
'cmi.comments_from_learner.comment': 'Hello SCORM', 'key_prefix': ''} {'cmi.interactions.0.id': 'q007', 'cmi.interactions.0.result': 'correct', 'recnum': 0}
{'id': 'q009', 'key_prefix': 'cmi.interactions.1', 'result': 'incorrect'} {'cmi.interactions.1.result': 'incorrect', 'cmi.interactions.1.id': 'q009', 'recnum': 1}
{'result': 'correct', 'key_prefix': 'cmi.interactions.2', {'cmi.interactions.2.learner_response': 'my answer',
'learner_response': 'my answer'} 'cmi.interactions.2.result': 'correct', 'recnum': 2}
>>> api.getValue('cmi.interactions.2.result') >>> api.getValue('cmi.interactions.2.result')
('correct', '0') ('correct', '0')

View file

@ -33,7 +33,9 @@ from cybertools.tracking.interfaces import ITrackingStorage
OK = '0' OK = '0'
_children = { scormInteractionsPrefixes = ['cmi.interactions.']
scormChildren = {
'cmi.comments_from_learner': ('comment', 'location', 'timestamp'), 'cmi.comments_from_learner': ('comment', 'location', 'timestamp'),
'cmi.comments_from_lms': ('comment', 'location', 'timestamp'), 'cmi.comments_from_lms': ('comment', 'location', 'timestamp'),
'cmi.interactions': ('id', 'type', 'objectives', 'timestamp', 'cmi.interactions': ('id', 'type', 'objectives', 'timestamp',
@ -43,10 +45,12 @@ _children = {
'delivery_speed', 'audio_captioning'), 'delivery_speed', 'audio_captioning'),
'cmi.objectives': ('id', 'score', 'success_status', 'completion_status', 'cmi.objectives': ('id', 'score', 'success_status', 'completion_status',
'description'), 'description'),
'score': ('scaled', 'raw', 'min', 'max'), ('cmi', 'score'): ('scaled', 'raw', 'min', 'max'),
} }
class ScormAPI(object): class ScormAPI(object):
""" ScormAPI objects are temporary adapters created by """ ScormAPI objects are temporary adapters created by
browser or XML-RPC views. browser or XML-RPC views.
@ -79,12 +83,12 @@ class ScormAPI(object):
def setValue(self, element, value): def setValue(self, element, value):
tracks = self.context.getUserTracks(self.taskId, self.runId, self.userId) tracks = self.context.getUserTracks(self.taskId, self.runId, self.userId)
prefix, key = self._splitKey(element) recnum = self._getRecnum(element)
track = self._getTrack(tracks, prefix) track = self._getTrack(tracks, recnum)
data = track is not None and track.data or {} data = track is not None and track.data or {}
data[key] = value data[element] = value
if track is None: if track is None:
data['key_prefix'] = prefix data['recnum'] = recnum
self.context.saveUserTrack(self.taskId, self.runId, self.userId, data) self.context.saveUserTrack(self.taskId, self.runId, self.userId, data)
else: else:
self.context.updateTrack(track, data) self.context.updateTrack(track, data)
@ -103,23 +107,23 @@ class ScormAPI(object):
tracks = self.context.getUserTracks(self.taskId, self.runId, self.userId) tracks = self.context.getUserTracks(self.taskId, self.runId, self.userId)
if element.endswith('._count'): if element.endswith('._count'):
base = element[:-len('._count')] base = element[:-len('._count')]
if element.startswith('cmi.interactions.'): for prefix in scormInteractionsPrefixes:
return self._countSubtracks(tracks, base), OK if element.startswith(prefix):
else: return self._countInteractionTracks(tracks), OK
track = self._getTrack(tracks, '') track = self._getTrack(tracks, -1)
if track is None: if track is None:
return 0, OK return 0, OK
return self._countSubelements(track.data, base), OK return self._countSubelements(track.data, base), OK
if element.endswith('_children'): if element.endswith('_children'):
base = element[:-len('._children')] base = element[:-len('._children')]
return self._getChildren(base) return self._getChildren(base)
prefix, key = self._splitKey(element) recnum = self._getRecnum(element)
track = self._getTrack(tracks, prefix) track = self._getTrack(tracks, recnum)
if track is None: if track is None:
return '', '403' return '', '403'
data = track.data data = track.data
if key in data: if element in data:
return data[key], OK return data[element], OK
else: else:
return '', '403' return '', '403'
@ -131,15 +135,22 @@ class ScormAPI(object):
# helper methods # 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): def _splitKey(self, element):
if element.startswith('cmi.interactions.'): if element.startswith('cmi.interactions.'):
parts = element.split('.') parts = element.split('.')
return '.'.join(parts[:3]), '.'.join(parts[3:]) return '.'.join(parts[:3]), '.'.join(parts[3:])
return '', element return '', element
def _getTrack(self, tracks, prefix): def _getTrack(self, tracks, recnum):
for tr in reversed(sorted(tracks, key=lambda x: x.timeStamp)): for tr in tracks:
if tr and tr.data.get('key_prefix', None) == prefix: if tr.data['recnum'] == recnum:
return tr return tr
return None return None
@ -150,14 +161,19 @@ class ScormAPI(object):
result.add(key) result.add(key)
return len(result) return len(result)
def _countSubtracks(self, tracks, base): def _countInteractionTracks(self, tracks):
return len([tr for tr in tracks if tr.data.get('key_prefix').startswith(base)]) return len([tr for tr in tracks
if tr.data.get('recnum', -1) >= 0])
def _getChildren(self, base): def _getChildren(self, base):
if base.endswith('.score'): if base in scormChildren:
base = 'score' return scormChildren[base], OK
if base in _children: parts = base.split('.')
return _children[base], OK if len(parts) >= 2:
else: # this may be somewhat simplistic, but should cover the
return '', '401' # most common cases
key = (parts[0], parts[-1])
if key in scormChildren:
return scormChildren[key], OK
return '', '401'