From 5f290c072383f5d572b1943b9cec3c683e8543e5 Mon Sep 17 00:00:00 2001 From: helmutm Date: Sun, 12 Aug 2007 08:58:34 +0000 Subject: [PATCH] some bug fixes concerning update of SCORM tracks git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1910 fd906abe-77d9-0310-91a1-e0d9ade77398 --- scorm/README.txt | 43 ++++++++++++++++++++++++++++++++++++++++--- scorm/base.py | 33 ++++++++++++++++++++------------- tracking/btree.py | 22 ++++++++++++++-------- 3 files changed, 74 insertions(+), 24 deletions(-) diff --git a/scorm/README.txt b/scorm/README.txt index ca46679..c0b0e46 100644 --- a/scorm/README.txt +++ b/scorm/README.txt @@ -29,7 +29,7 @@ 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.comments_from_learner.comment', 'Hello SCORM') >>> rc = api.setValue('cmi.interactions.1.id', 'q009') >>> rc = api.setValue('cmi.interactions.1.result', 'incorrect') @@ -40,13 +40,13 @@ track for each interaction and one additional 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': ''} + {'cmi.comments_from_learner.comment': 'Hello SCORM', 'key_prefix': ''} {'id': 'q009', 'key_prefix': 'cmi.interactions.1', 'result': 'incorrect'} Using the getValue() method we can retrieve certain values without having to care about the storage in different track objects. - >>> api.getValue('cmi.comments_from_learner') + >>> api.getValue('cmi.comments_from_learner.comment') ('Hello SCORM', '0') >>> api.getValue('cmi.interactions.0.id') ('q007', '0') @@ -66,3 +66,40 @@ We can also query special elements like _count and _children. >>> api.getValue('cmi.objectives.5.score._children') (('scaled', 'raw', 'min', 'max'), '0') +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): + ... 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'} + +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 +reasons as it allows us to update a bunch of elements with just one +XML-RPC call.) + + >>> data = {'cmi.interactions.2.result': 'correct', + ... 'cmi.interactions.2.learner_response': 'my answer', + ... } + >>> rc = api.setValues(data) + + >>> 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.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'} + + >>> api.getValue('cmi.interactions.2.result') + ('correct', '0') + >>> api.getValue('cmi.interactions.2.learner_response') + ('my answer', '0') + >>> api.getValue('cmi.comments_from_learner.comment') + ('Hello SCORM', '0') + >>> api.getValue('cmi.comments_from_learner.location') + ('q007', '0') diff --git a/scorm/base.py b/scorm/base.py index e20c0bf..d9041c4 100644 --- a/scorm/base.py +++ b/scorm/base.py @@ -80,18 +80,20 @@ class ScormAPI(object): def setValue(self, element, value): tracks = self.context.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.context.saveUserTrack(self.taskId, self.runId, self.userId, data, - update=update) + track = self._getTrack(tracks, prefix) + data = track is not None and track.data or {} + data[key] = value + if track is None: + data['key_prefix'] = prefix + self.context.saveUserTrack(self.taskId, self.runId, self.userId, data) + else: + self.context.updateTrack(track, data) return OK def setValues(self, mapping={}, **kw): mapping.update(kw) # TODO: optimize, i.e. retrieve existing tracks only once. - for key, value in mapping: + for key, value in mapping.items(): rc = self.setValue(key, value) if rc != OK: return rc @@ -104,13 +106,18 @@ class ScormAPI(object): if element.startswith('cmi.interactions.'): return self._countSubtracks(tracks, base), OK else: - data = self._getTrackData(tracks, '') - return self._countSubelements(data, base), OK + track = self._getTrack(tracks, '') + 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) - data = self._getTrackData(tracks, prefix) + track = self._getTrack(tracks, prefix) + if track is None: + return '', '403' + data = track.data if key in data: return data[key], OK else: @@ -130,11 +137,11 @@ class ScormAPI(object): return '.'.join(parts[:3]), '.'.join(parts[3:]) return '', element - def _getTrackData(self, tracks, prefix): + 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: - return tr.data - return {} + return tr + return None def _countSubelements(self, data, element): result = set() diff --git a/tracking/btree.py b/tracking/btree.py index f240a7b..3dc02fd 100644 --- a/tracking/btree.py +++ b/tracking/btree.py @@ -113,12 +113,8 @@ class TrackingStorage(BTreeContainer): trackNum = 0 if update: track = self.getLastUserTrack(taskId, runId, userName) - if track: - trackId = str(track.__name__) - trackNum = int(trackId) - track.data.update(data) - self.indexTrack(trackNum, track) - return trackId + if track is not None: + return self.updateTrack(track, data) if not trackNum: self.trackNum += 1 trackNum = self.trackNum @@ -128,6 +124,13 @@ class TrackingStorage(BTreeContainer): self.indexTrack(trackNum, track) return trackId + def updateTrack(self, track, data): + trackId = str(track.__name__) + trackNum = int(trackId) + track.update(data) + self.indexTrack(trackNum, track) + return trackId + def indexTrack(self, trackNum, track): md = track.metadata for attr in self.indexAttributes: @@ -152,7 +155,8 @@ class TrackingStorage(BTreeContainer): tracks = self.getUserTracks(taskId, runId, userName) if tracks: return sorted(tracks, key=lambda x: x.timeStamp)[-1] - else: return None + else: + return None def query(self, **kw): result = None @@ -195,8 +199,10 @@ class Track(Persistent): self.timeStamp = getTimeStamp() self.data = data - def update(self, data): + def update(self, newData): self.timeStamp = getTimeStamp() + data = self.data + data.update(newData) self.data = data def __repr__(self):