diff --git a/base.py b/base.py
index 35aa69d..f19b13e 100644
--- a/base.py
+++ b/base.py
@@ -71,6 +71,9 @@ class Loops(Folder):
def getViewManager(self):
return self['views']
+ def getRecordManager(self):
+ return self['records']
+
def getLoopsUri(self, obj):
return str(loopsPrefix + getPath(obj)[len(getPath(self)):])
diff --git a/browser/concept.py b/browser/concept.py
index 420ff61..0097b29 100644
--- a/browser/concept.py
+++ b/browser/concept.py
@@ -211,7 +211,7 @@ class ConceptView(BaseView):
def description(self):
return self.adapted.description
- def fieldData(self):
+ def xxx_fieldData(self):
# obsolete - use getData() instead
# TODO: change view macros accordingly
ti = IType(self.context).typeInterface
diff --git a/browser/configure.zcml b/browser/configure.zcml
index f6e8db2..1176ebe 100644
--- a/browser/configure.zcml
+++ b/browser/configure.zcml
@@ -18,6 +18,12 @@
+
+
+
+
+
+
+
+
-
+ />-->
-->
-
+ />-->
+
+
+
+
diff --git a/configure.zcml b/configure.zcml
index 7bb881b..c76f812 100644
--- a/configure.zcml
+++ b/configure.zcml
@@ -24,41 +24,23 @@
type="zope.app.content.interfaces.IContentType" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -67,28 +49,21 @@
type="zope.app.content.interfaces.IContentType" />
-
-
-
+
-
-
-
-
+
@@ -115,91 +90,59 @@
type="zope.app.content.interfaces.IContentType" />
-
-
-
+
-
-
-
+
-
-
-
-
-
+
+
-
-
-
+
-
-
-
-
-
+
+
-
-
-
+
-
-
-
-
-
+
+
-
-
-
-
-
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
diff --git a/interfaces.py b/interfaces.py
index b7381a7..f419c02 100644
--- a/interfaces.py
+++ b/interfaces.py
@@ -31,10 +31,11 @@ from zope.app.file.interfaces import IImage as IBaseAsset
from zope.app.folder.interfaces import IFolder
from zope.component.interfaces import IObjectEvent
from zope.size.interfaces import ISized
-from cybertools.relation.interfaces import IDyadicRelation
-import util
-from util import _
+from cybertools.relation.interfaces import IDyadicRelation
+from cybertools.tracking.interfaces import ITrackingStorage
+from loops import util
+from loops.util import _
# common interfaces
@@ -516,6 +517,12 @@ class INodeContained(Interface):
containers(INode, IViewManager)
+# record manager interfaces
+
+class IRecordManager(ILoopsObject):
+ contains(ITrackingStorage)
+
+
# the loops top-level container
class ILoops(ILoopsObject):
@@ -557,6 +564,10 @@ class ILoops(ILoopsObject):
""" Return the (default) view manager.
"""
+ def getRecordManager():
+ """ Return the (default) record manager.
+ """
+
class ILoopsContained(Interface):
containers(ILoops)
diff --git a/organize/configure.zcml b/organize/configure.zcml
index 22e3fd6..77be6e7 100644
--- a/organize/configure.zcml
+++ b/organize/configure.zcml
@@ -108,4 +108,8 @@
+
+
+
+
diff --git a/organize/personal/README.txt b/organize/personal/README.txt
new file mode 100644
index 0000000..b4ab241
--- /dev/null
+++ b/organize/personal/README.txt
@@ -0,0 +1,119 @@
+===============================================================
+loops - Linked Objects for Organization and Processing Services
+===============================================================
+
+ ($Id$)
+
+Let's do some basic setup
+
+ >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
+ >>> site = placefulSetUp(True)
+ >>> from zope import component, interface
+
+and set up a simple loops site with a concept manager and some concepts
+(with all the type machinery, what in real life is done via standard
+ZCML setup):
+
+ >>> from loops.organize.setup import SetupManager
+ >>> component.provideAdapter(SetupManager, name='organize')
+ >>> from loops.organize.personal.setup import SetupManager
+ >>> component.provideAdapter(SetupManager, name='organize.personal')
+
+ >>> from loops.tests.setup import TestSite
+ >>> t = TestSite(site)
+ >>> concepts, resources, views = t.setup()
+
+
+Favorites - Managed by a Tracking Storage
+=========================================
+
+ >>> loopsRoot = concepts.getLoopsRoot()
+ >>> records = loopsRoot.getRecordManager()
+ >>> favorites = records['favorites']
+
+User management setup
+---------------------
+
+In order to be able to login and store favorites and other personal data
+we have to prepare our environment. We need some basic adapter registrations,
+and a pluggable authentication utility with a principal folder.
+
+ >>> from loops.organize.tests import setupUtilitiesAndAdapters
+ >>> setupData = setupUtilitiesAndAdapters(loopsRoot)
+
+ >>> from zope.app.appsetup.bootstrap import ensureUtility
+ >>> from zope.app.authentication.authentication import PluggableAuthentication
+ >>> from zope.app.security.interfaces import IAuthentication
+ >>> ensureUtility(site, IAuthentication, '', PluggableAuthentication,
+ ... copy_to_zlog=False, asObject=True)
+ <...PluggableAuthentication...>
+ >>> pau = component.getUtility(IAuthentication, context=site)
+
+ >>> from zope.app.authentication.principalfolder import PrincipalFolder
+ >>> from zope.app.authentication.interfaces import IAuthenticatorPlugin
+ >>> pFolder = PrincipalFolder('users.')
+ >>> pau['users'] = pFolder
+ >>> pau.authenticatorPlugins = ('users',)
+
+So we can now register a user ...
+
+ >>> from zope.app.authentication.principalfolder import InternalPrincipal
+ >>> pFolder['john'] = InternalPrincipal('john', 'xx', u'John')
+ >>> from zope.app.authentication.principalfolder import FoundPrincipalFactory
+ >>> component.provideAdapter(FoundPrincipalFactory)
+
+... and create a corresponding person.
+
+ >>> from loops.concept import Concept
+ >>> johnC = concepts['john'] = Concept(u'John')
+ >>> person = concepts['person']
+ >>> johnC.conceptType = person
+ >>> from loops.common import adapted
+ >>> adapted(johnC).userId = 'users.john'
+
+Finally, we log in as the newly created user.
+
+ >>> from zope.app.authentication.principalfolder import Principal
+ >>> pJohn = Principal('users.john', 'xxx', u'John')
+
+ >>> from loops.tests.auth import login
+ >>> login(pJohn)
+
+Working with the favorites storage
+----------------------------------
+
+The setup has provided us with a few resources, so there are objects we
+can remember as favorites.
+
+ >>> list(resources.keys())
+ [u'd001.txt', u'd002.txt', u'd003.txt']
+
+ >>> from loops import util
+ >>> d001Id = util.getUidForObject(resources['d001.txt'])
+ >>> d003Id = util.getUidForObject(resources['d003.txt'])
+ >>> johnCId = util.getUidForObject(johnC)
+
+(We always need a "run" - can we try to ignore this for favorites?)
+
+ >>> runId = favorites.startRun()
+
+For favorites we don't need any data...
+
+ >>> favorites.saveUserTrack(d001Id, runId, johnCId, {})
+ '0000001'
+ >>> favorites.saveUserTrack(d003Id, runId, johnCId, {})
+ '0000002'
+
+So we are now ready to query the favorites.
+
+ >>> favs = favorites.query(userName=johnCId)
+ >>> favs
+ [,
+ ]
+
+ >>> util.getObjectForUid(favs[0].taskId) is resources['d001.txt']
+ True
+
+User interface
+--------------
+
diff --git a/organize/personal/__init__.py b/organize/personal/__init__.py
new file mode 100644
index 0000000..4bc90fb
--- /dev/null
+++ b/organize/personal/__init__.py
@@ -0,0 +1,4 @@
+"""
+$Id$
+"""
+
diff --git a/organize/personal/browser/__init__.py b/organize/personal/browser/__init__.py
new file mode 100644
index 0000000..4bc90fb
--- /dev/null
+++ b/organize/personal/browser/__init__.py
@@ -0,0 +1,4 @@
+"""
+$Id$
+"""
+
diff --git a/organize/personal/configure.zcml b/organize/personal/configure.zcml
new file mode 100644
index 0000000..bfcbd68
--- /dev/null
+++ b/organize/personal/configure.zcml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/organize/personal/favorite.py b/organize/personal/favorite.py
new file mode 100644
index 0000000..826bd2a
--- /dev/null
+++ b/organize/personal/favorite.py
@@ -0,0 +1,35 @@
+#
+# 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 loops.organize.personal.interfaces import IFavorite
+
+
+class Favorite(Track):
+
+ implements(IFavorite)
+
+ typeName = 'Favorite'
diff --git a/organize/personal/interfaces.py b/organize/personal/interfaces.py
new file mode 100644
index 0000000..b3fe7ad
--- /dev/null
+++ b/organize/personal/interfaces.py
@@ -0,0 +1,37 @@
+#
+# 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 favorites/clipboard/navigation history.
+
+$Id$
+"""
+
+from zope.interface import Interface, Attribute
+from zope import schema
+
+from cybertools.tracking.interfaces import ITrack
+
+
+class IFavorite(ITrack):
+ """ A favorite references a content object via the
+ task id attribute; the user name references
+ the user/person for which the favorite is to be stored.
+ The tracking storage's run management is not used.
+ """
+
diff --git a/organize/personal/setup.py b/organize/personal/setup.py
new file mode 100644
index 0000000..75be581
--- /dev/null
+++ b/organize/personal/setup.py
@@ -0,0 +1,38 @@
+#
+# 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
+#
+
+"""
+Automatic setup of a loops site for the organize.personal package.
+
+$Id$
+"""
+
+from zope.component import adapts
+from zope.interface import implements, Interface
+
+from cybertools.tracking.btree import TrackingStorage
+from loops.organize.personal.favorite import Favorite
+from loops.setup import SetupManager as BaseSetupManager
+
+
+class SetupManager(BaseSetupManager):
+
+ def setup(self):
+ records = self.context.getRecordManager()
+ favorites = self.addObject(records, TrackingStorage, 'favorites',
+ trackFactory=Favorite)
diff --git a/organize/personal/tests.py b/organize/personal/tests.py
new file mode 100755
index 0000000..91fa113
--- /dev/null
+++ b/organize/personal/tests.py
@@ -0,0 +1,22 @@
+# $Id$
+
+import unittest, doctest
+from zope.testing.doctestunit import DocFileSuite
+
+
+class Test(unittest.TestCase):
+ "Basic tests for the loops.organize.personal 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/record.py b/record.py
new file mode 100644
index 0000000..704c4bf
--- /dev/null
+++ b/record.py
@@ -0,0 +1,43 @@
+#
+# 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
+#
+
+"""
+Definition of the View and related classses.
+
+$Id$
+"""
+
+from zope.app.container.btree import BTreeContainer
+from zope.interface import implements
+from zope.traversing.api import getParent
+
+from cybertools.util.jeep import Jeep
+from loops.interfaces import ILoopsContained
+from loops.interfaces import IRecordManager
+
+
+class RecordManager(BTreeContainer):
+
+ implements(IRecordManager, ILoopsContained)
+
+ def getLoopsRoot(self):
+ return getParent(self)
+
+ def getAllParents(self, collectGrants=False):
+ return Jeep()
+
diff --git a/setup.py b/setup.py
index 12f07ef..958c983 100644
--- a/setup.py
+++ b/setup.py
@@ -36,6 +36,7 @@ from loops.concept import ConceptManager, Concept
from loops.interfaces import ILoops, ITypeConcept
from loops.interfaces import IFile, IImage, ITextDocument, INote
from loops.query import IQueryConcept
+from loops.record import RecordManager
from loops.resource import ResourceManager, Resource
from loops.view import ViewManager, Node
@@ -72,6 +73,7 @@ class SetupManager(object):
concepts = self.addObject(loopsRoot, ConceptManager, 'concepts')
resources = self.addObject(loopsRoot, ResourceManager, 'resources')
views = self.addObject(loopsRoot, ViewManager, 'views')
+ records = self.addObject(loopsRoot, RecordManager, 'records')
return concepts, resources, views
def setupCoreConcepts(self, conceptManager):
@@ -222,3 +224,19 @@ def addAndConfigureObject(container, class_, name, **kw):
setattr(adapted, attr, kw[attr])
notify(ObjectModifiedEvent(obj))
return obj
+
+
+class SetupView(object):
+ """ Allows to carry out setup actions manually.
+ """
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ self.manager = ISetupManager(context)
+
+ def setupLoopsSite(self):
+ #self.manager.setupManagers()
+ self.manager.setup()
+ return 'Done'
+