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
|
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')
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue