started work on comment (discussions) and notify (notifications)
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@2389 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
7ec9bbdc15
commit
f9418c1df6
12 changed files with 302 additions and 16 deletions
|
@ -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
|
# 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
|
# 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$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
@ -84,6 +84,7 @@ class Track(Persistent):
|
||||||
return '<Track %s: %s>' % (`[md[a] for a in self.metadata_attributes]`,
|
return '<Track %s: %s>' % (`[md[a] for a in self.metadata_attributes]`,
|
||||||
`self.data`)
|
`self.data`)
|
||||||
|
|
||||||
|
|
||||||
class TrackingStorage(BTreeContainer):
|
class TrackingStorage(BTreeContainer):
|
||||||
|
|
||||||
implements(ITrackingStorage)
|
implements(ITrackingStorage)
|
||||||
|
@ -93,10 +94,13 @@ class TrackingStorage(BTreeContainer):
|
||||||
trackNum = runId = 0
|
trackNum = runId = 0
|
||||||
runs = None
|
runs = None
|
||||||
|
|
||||||
#indexAttributes = ('taskId', 'runId', 'userName', 'timeStamp')
|
|
||||||
indexAttributes = Track.index_attributes
|
indexAttributes = Track.index_attributes
|
||||||
|
|
||||||
def __init__(self, *args, **kw):
|
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)
|
super(TrackingStorage, self).__init__(*args, **kw)
|
||||||
self.indexes = OOBTree.OOBTree()
|
self.indexes = OOBTree.OOBTree()
|
||||||
for idx in self.indexAttributes:
|
for idx in self.indexAttributes:
|
||||||
|
@ -117,10 +121,11 @@ class TrackingStorage(BTreeContainer):
|
||||||
def idFromNum(self, num):
|
def idFromNum(self, num):
|
||||||
return '%07i' % (num)
|
return '%07i' % (num)
|
||||||
|
|
||||||
def startRun(self, taskId):
|
def startRun(self, taskId=None):
|
||||||
self.runId += 1
|
self.runId += 1
|
||||||
runId = self.runId
|
runId = self.runId
|
||||||
self.currentRuns[taskId] = runId
|
if taskId is not None:
|
||||||
|
self.currentRuns[taskId] = runId
|
||||||
run = self.runs[runId] = Run(runId)
|
run = self.runs[runId] = Run(runId)
|
||||||
run.start = run.end = getTimeStamp()
|
run.start = run.end = getTimeStamp()
|
||||||
return runId
|
return runId
|
||||||
|
@ -147,6 +152,11 @@ class TrackingStorage(BTreeContainer):
|
||||||
return self.runs.get(runId)
|
return self.runs.get(runId)
|
||||||
return None
|
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):
|
def saveUserTrack(self, taskId, runId, userName, data, update=False):
|
||||||
if not runId:
|
if not runId:
|
||||||
runId = self.currentRuns.get(taskId) or self.startRun(taskId)
|
runId = self.currentRuns.get(taskId) or self.startRun(taskId)
|
||||||
|
@ -154,15 +164,11 @@ class TrackingStorage(BTreeContainer):
|
||||||
if run is None:
|
if run is None:
|
||||||
raise ValueError('Invalid run: %i.' % runId)
|
raise ValueError('Invalid run: %i.' % runId)
|
||||||
run.end = getTimeStamp()
|
run.end = getTimeStamp()
|
||||||
trackNum = 0
|
|
||||||
if update:
|
if update:
|
||||||
track = self.getLastUserTrack(taskId, runId, userName)
|
track = self.getLastUserTrack(taskId, runId, userName)
|
||||||
if track is not None:
|
if track is not None:
|
||||||
return self.updateTrack(track, data)
|
return self.updateTrack(track, data)
|
||||||
if not trackNum:
|
trackId, trackNum = self.generateTrackId()
|
||||||
self.trackNum += 1
|
|
||||||
trackNum = self.trackNum
|
|
||||||
trackId = self.idFromNum(trackNum)
|
|
||||||
track = self.trackFactory(taskId, runId, userName, data)
|
track = self.trackFactory(taskId, runId, userName, data)
|
||||||
self[trackId] = track
|
self[trackId] = track
|
||||||
self.indexTrack(trackNum, track)
|
self.indexTrack(trackNum, track)
|
||||||
|
@ -211,12 +217,10 @@ class TrackingStorage(BTreeContainer):
|
||||||
if idx in self.indexAttributes:
|
if idx in self.indexAttributes:
|
||||||
if type(value) not in (list, tuple):
|
if type(value) not in (list, tuple):
|
||||||
value = [value]
|
value = [value]
|
||||||
# TODO: handle a list of values, provide union of results
|
|
||||||
resultx = None
|
resultx = None
|
||||||
for v in value:
|
for v in value:
|
||||||
resultx = self.union(resultx, self.indexes[idx].apply((v, v)))
|
resultx = self.union(resultx, self.indexes[idx].apply((v, v)))
|
||||||
result = self.intersect(result, resultx)
|
result = self.intersect(result, resultx)
|
||||||
#result = self.intersect(result, self.indexes[idx].apply((value, value)))
|
|
||||||
elif idx == 'timeFrom':
|
elif idx == 'timeFrom':
|
||||||
result = self.intersect(result,
|
result = self.intersect(result,
|
||||||
self.indexes['timeStamp'].apply((value, None)))
|
self.indexes['timeStamp'].apply((value, None)))
|
||||||
|
|
10
tracking/comment/README.txt
Normal file
10
tracking/comment/README.txt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
=============
|
||||||
|
Notifications
|
||||||
|
=============
|
||||||
|
|
||||||
|
($Id$)
|
||||||
|
|
||||||
|
>>> from cybertools.tracking.btree import TrackingStorage
|
||||||
|
>>> from cybertools.tracking.notify.base import Notification
|
||||||
|
|
||||||
|
>>> comments = TrackingStorage(trackFactory=Notification)
|
4
tracking/comment/__init__.py
Normal file
4
tracking/comment/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
33
tracking/comment/base.py
Normal file
33
tracking/comment/base.py
Normal file
|
@ -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)
|
73
tracking/comment/interfaces.py
Normal file
73
tracking/comment/interfaces.py
Normal file
|
@ -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.
|
||||||
|
"""
|
||||||
|
|
22
tracking/comment/tests.py
Executable file
22
tracking/comment/tests.py
Executable file
|
@ -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')
|
|
@ -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
|
# 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
|
# 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$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
@ -51,8 +51,9 @@ class ITrackingStorage(Interface):
|
||||||
""" A utility for storing user tracks.
|
""" A utility for storing user tracks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def startRun(taskId):
|
def startRun(taskId=None):
|
||||||
""" Create a new run for the task given and return its id.
|
""" 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):
|
def stopRun(taskId=None, runId=0, finish=True):
|
||||||
|
|
10
tracking/notify/README.txt
Normal file
10
tracking/notify/README.txt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
========
|
||||||
|
Comments
|
||||||
|
========
|
||||||
|
|
||||||
|
($Id$)
|
||||||
|
|
||||||
|
>>> from cybertools.tracking.btree import TrackingStorage
|
||||||
|
>>> from cybertools.tracking.comment.base import Comment
|
||||||
|
|
||||||
|
>>> comments = TrackingStorage(trackFactory=Comment)
|
4
tracking/notify/__init__.py
Normal file
4
tracking/notify/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
33
tracking/notify/base.py
Normal file
33
tracking/notify/base.py
Normal file
|
@ -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)
|
70
tracking/notify/interfaces.py
Normal file
70
tracking/notify/interfaces.py
Normal file
|
@ -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
|
22
tracking/notify/tests.py
Executable file
22
tracking/notify/tests.py
Executable file
|
@ -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')
|
Loading…
Add table
Reference in a new issue