added explicit 'run' management, will basis for SCORM-compliant API, e.g. for yeepa
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1705 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
b79c15740d
commit
747e65e265
3 changed files with 117 additions and 20 deletions
|
@ -6,6 +6,11 @@ User tracking in the loops framework
|
|||
|
||||
>>> from cybertools.tracking.btree import TrackingStorage
|
||||
|
||||
Let's create a tracking storage and store a few tracks in it. A track
|
||||
is basically an arbitrary mapping. (In the following examples we
|
||||
ignore the ``run`` argument and use a 0 value for it; thus we are just
|
||||
working with the current run of a task.)
|
||||
|
||||
>>> tracks = TrackingStorage()
|
||||
>>> tracks.saveUserTrack('a001', 0, 'u1', {'somekey': 'somevalue'})
|
||||
'0000001'
|
||||
|
@ -21,6 +26,10 @@ User tracking in the loops framework
|
|||
>>> [str(id) for id in tracks.getTaskIds()]
|
||||
['a001']
|
||||
|
||||
We can query the tracking storage using the tracks' metadata. These
|
||||
are mapped to btree indexes, so we get fast access to the resulting
|
||||
track data.
|
||||
|
||||
>>> tracks.query(taskId='a001')
|
||||
[<Track ['a001', 1, 'u1', '...-...-... ...:...']: {'somekey': 'somevalue'}>]
|
||||
|
||||
|
@ -38,14 +47,14 @@ What happens if we store more than on record for one set of keys?
|
|||
>>> [t.data for t in t2]
|
||||
[{'somekey': 'somevalue'}, {'somekey': 'newvalue'}]
|
||||
|
||||
It is also possible to retrieve the last entry for a set of keys directliy:
|
||||
It is also possible to retrieve the last entry for a set of keys directly.
|
||||
|
||||
>>> tracks.getLastUserTrack('a001', 0, 'u1')
|
||||
<Track ['a001', 1, 'u1', ...]: {'somekey': 'newvalue'}>
|
||||
|
||||
Instead of creating a new track object for each call one can also replace
|
||||
an existing one (if present). The replaced entry is always the last one
|
||||
for a given set of keys:
|
||||
for a given set of keys.
|
||||
|
||||
>>> tracks.saveUserTrack('a001', 0, 'u1', {'somekey': 'newvalue2'}, replace=True)
|
||||
'0000003'
|
||||
|
@ -63,6 +72,30 @@ The tracks of a tracking store may be reindexed:
|
|||
|
||||
>>> tracks.reindexTracks()
|
||||
|
||||
Runs
|
||||
----
|
||||
|
||||
We may explicitly start a new run for a given task. This will also replace
|
||||
the task's current run.
|
||||
|
||||
>>> tracks.startRun('a001')
|
||||
3
|
||||
>>> tracks.saveUserTrack('a001', 0, 'u1', {'k1': 'value1'})
|
||||
'0000005'
|
||||
>>> tracks.getLastUserTrack('a001', 0, 'u1')
|
||||
<Track ['a001', 3, 'u1', ...]: {'k1': 'value1'}>
|
||||
|
||||
We still have access to older runs.
|
||||
|
||||
>>> tracks.getLastUserTrack('a001', 1, 'u1')
|
||||
<Track ['a001', 1, 'u1', ...]: {'somekey': 'newvalue2'}>
|
||||
|
||||
We can also retrieve a run object with the run's data.
|
||||
|
||||
>>> run = tracks.getRun(3)
|
||||
>>> run
|
||||
<Run 3, ..., ..., False>
|
||||
|
||||
|
||||
Fin de partie
|
||||
=============
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
|
||||
# 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
|
||||
|
@ -17,7 +17,7 @@
|
|||
#
|
||||
|
||||
"""
|
||||
BTree-based implementation of user tracking.
|
||||
BTree-based implementation of user interaction tracking.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
@ -29,10 +29,27 @@ from zope.app.container.btree import BTreeContainer
|
|||
from zope.index.field import FieldIndex
|
||||
|
||||
from persistent import Persistent
|
||||
from BTrees import OOBTree
|
||||
from BTrees import OOBTree, IOBTree
|
||||
from BTrees.IFBTree import intersection
|
||||
|
||||
from interfaces import ITrackingStorage, ITrack
|
||||
from interfaces import IRun, ITrackingStorage, ITrack
|
||||
|
||||
|
||||
class Run(object):
|
||||
|
||||
implements(IRun)
|
||||
|
||||
id = start = end = 0
|
||||
finished = False
|
||||
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
def __repr__(self):
|
||||
return '<Run %s>' % ', '.join((str(self.id),
|
||||
timeStamp2ISO(self.start),
|
||||
timeStamp2ISO(self.end),
|
||||
str(self.finished)))
|
||||
|
||||
|
||||
class TrackingStorage(BTreeContainer):
|
||||
|
@ -48,6 +65,7 @@ class TrackingStorage(BTreeContainer):
|
|||
self.indexes = OOBTree.OOBTree()
|
||||
for idx in self.indexAttributes:
|
||||
self.indexes[idx] = FieldIndex()
|
||||
self.runs = IOBTree.IOBTree()
|
||||
self.currentRuns = OOBTree.OOBTree()
|
||||
self.taskUsers = OOBTree.OOBTree()
|
||||
|
||||
|
@ -56,18 +74,36 @@ class TrackingStorage(BTreeContainer):
|
|||
|
||||
def startRun(self, taskId):
|
||||
self.runId += 1
|
||||
self.currentRuns[taskId] = self.runId
|
||||
return self.runId
|
||||
|
||||
def stopRun(self, taskId):
|
||||
runId = self.currentRuns.get(taskId)
|
||||
if runId:
|
||||
del self.currentRuns[taskId]
|
||||
runId = self.runId
|
||||
self.currentRuns[taskId] = runId
|
||||
run = self.runs[runId] = Run(runId)
|
||||
run.start = run.end = getTimeStamp()
|
||||
return runId
|
||||
|
||||
def stopRun(self, taskId, runId=0, finish=True):
|
||||
currentRun = self.currentRuns.get(taskId)
|
||||
runId = runId or currentRun
|
||||
if runId and runId == currentRun:
|
||||
del self.currentRuns[taskId]
|
||||
run = self.getRun(runId)
|
||||
if run is not None:
|
||||
run.end = getTimeStamp()
|
||||
run.finished = finish
|
||||
return runId
|
||||
|
||||
def getRun(self, runId=0, taskId=None):
|
||||
if taskId and not runId:
|
||||
runId = self.currentRuns.get(taskId)
|
||||
if runId:
|
||||
return self.runs.get(runId)
|
||||
|
||||
def saveUserTrack(self, taskId, runId, userName, data, replace=False):
|
||||
if not runId:
|
||||
runId = self.currentRuns.get(taskId) or self.startRun(taskId)
|
||||
run = self.getRun(runId)
|
||||
if run is None:
|
||||
raise ValueError('Invalid run: %i.' % runId)
|
||||
run.end = getTimeStamp()
|
||||
trackNum = 0
|
||||
if replace:
|
||||
track = self.getLastUserTrack(taskId, runId, userName)
|
||||
|
@ -79,8 +115,7 @@ class TrackingStorage(BTreeContainer):
|
|||
self.trackNum += 1
|
||||
trackNum = self.trackNum
|
||||
trackId = self.idFromNum(trackNum)
|
||||
timeStamp = int(time.time())
|
||||
track = Track(taskId, runId, userName, timeStamp, data)
|
||||
track = Track(taskId, runId, userName, getTimeStamp(), data)
|
||||
self[trackId] = track
|
||||
self.indexTrack(trackNum, track)
|
||||
return trackId
|
||||
|
@ -161,3 +196,5 @@ class Track(Persistent):
|
|||
def timeStamp2ISO(ts):
|
||||
return time.strftime('%Y-%m-%d %H:%M', time.gmtime(ts))
|
||||
|
||||
def getTimeStamp():
|
||||
return int(time.time())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
|
||||
# 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
|
||||
|
@ -28,6 +28,16 @@ from zope import schema
|
|||
|
||||
# user interaction tracking
|
||||
|
||||
class IRun(Interface):
|
||||
""" A set of interactions, sort of a session.
|
||||
"""
|
||||
|
||||
id = Attribute('A unique integer that identifies a run within a tracking storage')
|
||||
start = Attribute('Timestamp of run creation')
|
||||
end = Attribute('Timestamp of last interaction or of stopping the run')
|
||||
finished = Attribute('Boolean that is set to True if run was finished explicitly')
|
||||
|
||||
|
||||
class ITrack(Interface):
|
||||
""" Result data from the interactions of a user with an task.
|
||||
"""
|
||||
|
@ -41,16 +51,33 @@ class ITrackingStorage(Interface):
|
|||
"""
|
||||
|
||||
def startRun(taskId):
|
||||
""" Creates a new run for the task given and return its id.
|
||||
""" Create a new run for the task given and return its id.
|
||||
"""
|
||||
|
||||
def stopRun(taskId):
|
||||
""" Remove the current run entry for the task given.
|
||||
def stopRun(taskId, runId=0, finish=True):
|
||||
""" Stop/finish a run.
|
||||
If the runId is 0 use the task's current run.
|
||||
If the run is the task's current one remove it from the set
|
||||
of current runs.
|
||||
Set the run's ``finished`` flag to the value of the ``finish``
|
||||
argument.
|
||||
Return the real runId.
|
||||
"""
|
||||
|
||||
def saveUserTrack(taskId, runId, userName, data):
|
||||
def getRun(runId, taskId=None):
|
||||
""" Return the run object identified by ``runId``. Return None
|
||||
if there is no corresponding run.
|
||||
If ``runId`` is 0 and a ``taskId`` is given return the
|
||||
current run of the task.
|
||||
"""
|
||||
|
||||
def saveUserTrack(taskId, runId, userName, data, replace=False):
|
||||
""" Save the data given (typically a mapping object) to the user track
|
||||
corresponding to the user name, task id, and run id given.
|
||||
If the runId is 0 use the task's current run.
|
||||
If the ``replace`` flag is set, the new track replaces the last
|
||||
one for the given set of keys.
|
||||
Return the new track item's id.
|
||||
"""
|
||||
|
||||
def query(**criteria):
|
||||
|
|
Loading…
Add table
Reference in a new issue