From ca273f78bc4890a1707c7413e062cfe073e0bbb7 Mon Sep 17 00:00:00 2001 From: helmutm Date: Tue, 12 Feb 2008 18:53:22 +0000 Subject: [PATCH] first steps towards getting favorites (and other personalization stuff) to work git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2394 fd906abe-77d9-0310-91a1-e0d9ade77398 --- base.py | 3 + browser/concept.py | 2 +- browser/configure.zcml | 38 +++++- configure.zcml | 164 ++++++++------------------ interfaces.py | 17 ++- organize/configure.zcml | 4 + organize/personal/README.txt | 119 +++++++++++++++++++ organize/personal/__init__.py | 4 + organize/personal/browser/__init__.py | 4 + organize/personal/configure.zcml | 28 +++++ organize/personal/favorite.py | 35 ++++++ organize/personal/interfaces.py | 37 ++++++ organize/personal/setup.py | 38 ++++++ organize/personal/tests.py | 22 ++++ record.py | 43 +++++++ setup.py | 18 +++ 16 files changed, 456 insertions(+), 120 deletions(-) create mode 100644 organize/personal/README.txt create mode 100644 organize/personal/__init__.py create mode 100644 organize/personal/browser/__init__.py create mode 100644 organize/personal/configure.zcml create mode 100644 organize/personal/favorite.py create mode 100644 organize/personal/interfaces.py create mode 100644 organize/personal/setup.py create mode 100755 organize/personal/tests.py create mode 100644 record.py 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' +