diff --git a/tracking/btree.py b/tracking/btree.py index ccdc7af..04f580b 100644 --- a/tracking/btree.py +++ b/tracking/btree.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2007 Helmut Merz helmutm@cy55.de +# Copyright (c) 2008 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 interaction tracking. +ZODB-/BTree-based implementation of user interaction tracking. $Id$ """ @@ -84,6 +84,7 @@ class Track(Persistent): return '' % (`[md[a] for a in self.metadata_attributes]`, `self.data`) + class TrackingStorage(BTreeContainer): implements(ITrackingStorage) @@ -93,10 +94,13 @@ class TrackingStorage(BTreeContainer): trackNum = runId = 0 runs = None - #indexAttributes = ('taskId', 'runId', 'userName', 'timeStamp') indexAttributes = Track.index_attributes def __init__(self, *args, **kw): + trackFactory = kw.pop('trackFactory', None) + if trackFactory is not None: + self.trackFactory = trackFactory + self.indexAttributes = trackFactory.index_attributes super(TrackingStorage, self).__init__(*args, **kw) self.indexes = OOBTree.OOBTree() for idx in self.indexAttributes: @@ -117,10 +121,11 @@ class TrackingStorage(BTreeContainer): def idFromNum(self, num): return '%07i' % (num) - def startRun(self, taskId): + def startRun(self, taskId=None): self.runId += 1 runId = self.runId - self.currentRuns[taskId] = runId + if taskId is not None: + self.currentRuns[taskId] = runId run = self.runs[runId] = Run(runId) run.start = run.end = getTimeStamp() return runId @@ -147,6 +152,11 @@ class TrackingStorage(BTreeContainer): return self.runs.get(runId) return None + def generateTrackId(self): + self.trackNum += 1 + trackId = self.idFromNum(self.trackNum) + return trackId, self.trackNum + def saveUserTrack(self, taskId, runId, userName, data, update=False): if not runId: runId = self.currentRuns.get(taskId) or self.startRun(taskId) @@ -154,15 +164,11 @@ class TrackingStorage(BTreeContainer): if run is None: raise ValueError('Invalid run: %i.' % runId) run.end = getTimeStamp() - trackNum = 0 if update: track = self.getLastUserTrack(taskId, runId, userName) if track is not None: return self.updateTrack(track, data) - if not trackNum: - self.trackNum += 1 - trackNum = self.trackNum - trackId = self.idFromNum(trackNum) + trackId, trackNum = self.generateTrackId() track = self.trackFactory(taskId, runId, userName, data) self[trackId] = track self.indexTrack(trackNum, track) @@ -211,12 +217,10 @@ class TrackingStorage(BTreeContainer): if idx in self.indexAttributes: if type(value) not in (list, tuple): value = [value] - # TODO: handle a list of values, provide union of results resultx = None for v in value: resultx = self.union(resultx, self.indexes[idx].apply((v, v))) result = self.intersect(result, resultx) - #result = self.intersect(result, self.indexes[idx].apply((value, value))) elif idx == 'timeFrom': result = self.intersect(result, self.indexes['timeStamp'].apply((value, None))) diff --git a/tracking/comment/README.txt b/tracking/comment/README.txt new file mode 100644 index 0000000..06e5e38 --- /dev/null +++ b/tracking/comment/README.txt @@ -0,0 +1,10 @@ +============= +Notifications +============= + + ($Id$) + + >>> from cybertools.tracking.btree import TrackingStorage + >>> from cybertools.tracking.notify.base import Notification + + >>> comments = TrackingStorage(trackFactory=Notification) diff --git a/tracking/comment/__init__.py b/tracking/comment/__init__.py new file mode 100644 index 0000000..4bc90fb --- /dev/null +++ b/tracking/comment/__init__.py @@ -0,0 +1,4 @@ +""" +$Id$ +""" + diff --git a/tracking/comment/base.py b/tracking/comment/base.py new file mode 100644 index 0000000..9318e7f --- /dev/null +++ b/tracking/comment/base.py @@ -0,0 +1,33 @@ +# +# Copyright (c) 2008 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Basic implementation of comments / discussions. + +$Id$ +""" + +from zope.interface import implements + +from cybertools.tracking.btree import Track +from cybertools.tracking.comment.interfaces import IComment + + +class Comment(Track): + + implements(IComment) diff --git a/tracking/comment/interfaces.py b/tracking/comment/interfaces.py new file mode 100644 index 0000000..be9b750 --- /dev/null +++ b/tracking/comment/interfaces.py @@ -0,0 +1,73 @@ +# +# Copyright (c) 2008 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Interface definitions for comments - discussions - forums. + +$Id$ +""" + +from zope.interface import Interface, Attribute +from zope import schema + +from cybertools.tracking.interfaces import ITrack + + +class IComment(ITrack): + """ A comment is a piece of text provided by a user and related + to a content object. + The object the comment is related to is referenced via the + task id attribute; interdependent comments (i.e. comments in a + parent/child hierarchy related to the same object share the + run id; the user name references the user/person that created + the comment. + """ + + parent = Attribute('The id of the parent comment; None for ' + 'the top-level comment.') + + subject = schema.TextLine( + title=u'Subject', + description=u'A short informative line of text.', + default=u'', # should be taken from the parent + required=True) + text = schema.Text( + title=u'Text', + description=u'The text of the comment.', + default=u'', + required=True) + contentType = schema.BytesLine( + title=u'Content Type', + description=u'Content type (format) of the text field', + # TODO: provide a source/vocabulary + default='text/restructured', + required=True) + + def getChildren(sort='default'): + """ Returns the immediate children in the order specified by the sort + argument; the return value is a sequence of track ids. + The default sorting is by timestamp, newest first. + """ + + def getChildrenTree(sort='default', depth=0): + """ Returns the children as a tree, in the order specified by + the sort argument. If depth is greater than 0 only that level + of children will be returned. + The return value is a nested sequence of track ids. + """ + diff --git a/tracking/comment/tests.py b/tracking/comment/tests.py new file mode 100755 index 0000000..6c7ef8b --- /dev/null +++ b/tracking/comment/tests.py @@ -0,0 +1,22 @@ +# $Id$ + +import unittest, doctest +from zope.testing.doctestunit import DocFileSuite + + +class Test(unittest.TestCase): + "Basic tests for the cybertools.tracking.notify package." + + def testBasics(self): + pass + + +def test_suite(): + flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + return unittest.TestSuite(( + unittest.makeSuite(Test), + DocFileSuite('README.txt', optionflags=flags), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/tracking/interfaces.py b/tracking/interfaces.py index 162924e..249b3cf 100644 --- a/tracking/interfaces.py +++ b/tracking/interfaces.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2007 Helmut Merz helmutm@cy55.de +# Copyright (c) 2008 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 @@ # """ -loops tracking interface definitions. +Interface definitions for tracking of user interactions. $Id$ """ @@ -51,8 +51,9 @@ class ITrackingStorage(Interface): """ A utility for storing user tracks. """ - def startRun(taskId): - """ Create a new run for the task given and return its id. + def startRun(taskId=None): + """ Create a new run and return its id. + If a taskId is given, record the runId as the tasks current run. """ def stopRun(taskId=None, runId=0, finish=True): diff --git a/tracking/notify/README.txt b/tracking/notify/README.txt new file mode 100644 index 0000000..8fd7fc7 --- /dev/null +++ b/tracking/notify/README.txt @@ -0,0 +1,10 @@ +======== +Comments +======== + + ($Id$) + + >>> from cybertools.tracking.btree import TrackingStorage + >>> from cybertools.tracking.comment.base import Comment + + >>> comments = TrackingStorage(trackFactory=Comment) diff --git a/tracking/notify/__init__.py b/tracking/notify/__init__.py new file mode 100644 index 0000000..4bc90fb --- /dev/null +++ b/tracking/notify/__init__.py @@ -0,0 +1,4 @@ +""" +$Id$ +""" + diff --git a/tracking/notify/base.py b/tracking/notify/base.py new file mode 100644 index 0000000..bbe9155 --- /dev/null +++ b/tracking/notify/base.py @@ -0,0 +1,33 @@ +# +# Copyright (c) 2008 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Base classes for a notification framework. + +$Id$ +""" + +from zope.interface import implements + +from cybertools.tracking.btree import Track +from cybertools.tracking.notify.interfaces import INotification + + +class Notification(Track): + + implements(INotification) diff --git a/tracking/notify/interfaces.py b/tracking/notify/interfaces.py new file mode 100644 index 0000000..cb98233 --- /dev/null +++ b/tracking/notify/interfaces.py @@ -0,0 +1,70 @@ +# +# Copyright (c) 2008 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Interface definitions for a notification framework. + +$Id$ +""" + +from zope.interface import Interface, Attribute +from zope import schema + +from cybertools.tracking.interfaces import ITrack + + +class INotification(ITrack): + """ A notification carries information necessary to inform a + receiver (typically a user or person, but possibly also an other + entity) about an event. + The object the notification is related to is referenced via the + task id attribute; interdependent notifications (i.e. notifications + that are triggered by the identical event or have been created + by another notification) share the run id; the user name references + the user/person that will be notified. + """ + + type = Attribute('A string (token) that specifies the ' + 'type of the notification.') # TODO: vocabulary + state = Attribute('A string (token) specifying the ' + 'current state of the notification.') # TODO: vocabulary + parent = Attribute('The id of the parent notification, i.e. the ' + 'notification that triggered the creation of this one. ' + 'None if there is not parent notification.') + eventType = Attribute('A string (token or title) that specifies the ' + 'type of the event that triggered this notification.') + media = Attribute('A string (token) specifying the ' + 'media type that will be used for presenting ' + 'the notification.') # TODO: vocabulary + timingType = Attribute('A string (token) specifying the ' + 'type of timing that will be used for presenting ' + 'the notification.') # TODO: vocabulary + timing = Attribute('A list of (typically) time/date values specifying ' + 'the time range for presentation. The possible values and ' + 'their interpretation depend on the timing type.') + deliveryInfo = Attribute('Additional information needed for the ' + 'delivery of the notification, e.g. an email address.') + actionsPossible = Attribute('A collection of possible actions the receiver ' + 'may take in response to the notification.') + actionsTaken = Attribute('One or more actions that have been carried ' + 'out in response to the notification.') + + # media + timingType + timing + deliveryInfo could be combined to + # a `deliverySpec` attribute. + + # TODO: Action class (type + instance); vocabulary of action types diff --git a/tracking/notify/tests.py b/tracking/notify/tests.py new file mode 100755 index 0000000..33448a6 --- /dev/null +++ b/tracking/notify/tests.py @@ -0,0 +1,22 @@ +# $Id$ + +import unittest, doctest +from zope.testing.doctestunit import DocFileSuite + + +class Test(unittest.TestCase): + "Basic tests for the cybertools.tracking.comment package." + + def testBasics(self): + pass + + +def test_suite(): + flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + return unittest.TestSuite(( + unittest.makeSuite(Test), + DocFileSuite('README.txt', optionflags=flags), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite')