From 4d5bfed47fe4ea28be995a9b49de341316a8e96f Mon Sep 17 00:00:00 2001 From: helmutm Date: Wed, 9 Apr 2008 10:03:52 +0000 Subject: [PATCH] move stateful and process to organize; work in progress: organize.tracking git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2509 fd906abe-77d9-0310-91a1-e0d9ade77398 --- common.py | 22 ++--- configure.zcml | 3 +- organize/configure.zcml | 4 +- organize/personal/README.txt | 64 +++---------- {process => organize/process}/README.txt | 4 +- {process => organize/process}/__init__.py | 0 {process => organize/process}/configure.zcml | 7 +- {process => organize/process}/definition.py | 0 {process => organize/process}/interfaces.py | 0 {process => organize/process}/setup.py | 0 {process => organize/process}/tests.py | 0 {stateful => organize/stateful}/README.txt | 8 +- {stateful => organize/stateful}/__init__.py | 0 {stateful => organize/stateful}/base.py | 0 .../stateful}/configure.zcml | 4 +- {stateful => organize/stateful}/tests.py | 0 organize/tests.py | 41 ++++++++- organize/tracking/README.txt | 64 +++++++++++++ organize/tracking/__init__.py | 3 + organize/tracking/change.py | 91 +++++++++++++++++++ organize/tracking/configure.zcml | 24 +++++ organize/tracking/setup.py | 38 ++++++++ organize/tracking/tests.py | 22 +++++ organize/util.py | 10 +- 24 files changed, 328 insertions(+), 81 deletions(-) rename {process => organize/process}/README.txt (95%) rename {process => organize/process}/__init__.py (100%) rename {process => organize/process}/configure.zcml (75%) rename {process => organize/process}/definition.py (100%) rename {process => organize/process}/interfaces.py (100%) rename {process => organize/process}/setup.py (100%) rename {process => organize/process}/tests.py (100%) rename {stateful => organize/stateful}/README.txt (94%) rename {stateful => organize/stateful}/__init__.py (100%) rename {stateful => organize/stateful}/base.py (100%) rename {stateful => organize/stateful}/configure.zcml (81%) rename {stateful => organize/stateful}/tests.py (100%) create mode 100644 organize/tracking/README.txt create mode 100644 organize/tracking/__init__.py create mode 100644 organize/tracking/change.py create mode 100644 organize/tracking/configure.zcml create mode 100644 organize/tracking/setup.py create mode 100755 organize/tracking/tests.py diff --git a/common.py b/common.py index fccb711..3229b09 100644 --- a/common.py +++ b/common.py @@ -58,29 +58,23 @@ def adapted(obj, langInfo=None): # helper functions for specifying automatic attribute handling -def adapterAttributes(*args): +def collectAttributeNames(lst, name): attrs = [] - for arg in args: + for arg in lst: if isinstance(arg, basestring): attrs.append(arg) elif isinstance(arg, type): - attrs.extend(list(arg._adapterAttributes)) + attrs.extend(list(getattr(arg, name))) else: raise ValueError("Argument must be string or class, '%s' is '%s'." % (arg, type(arg))) return tuple(attrs) +def adapterAttributes(*args): + return collectAttributeNames(args, '_adapterAttributes') + def contextAttributes(*args): - attrs = [] - for arg in args: - if isinstance(arg, basestring): - attrs.append(arg) - elif isinstance(arg, type): - attrs.extend(list(arg._contextAttributes)) - else: - raise ValueError("Argument must be string or class, '%s' is '%s'." % - (arg, type(arg))) - return attrs + return collectAttributeNames(args, '_contextAttributes') # type interface adapters @@ -133,7 +127,7 @@ class ResourceAdapterBase(AdapterBase): implements(IStorageInfo) adapts(IResource) - _adapterAttributes = ('storageName', 'storageParams', ) + AdapterBase._adapterAttributes + _adapterAttributes = adapterAttributes('storageName', 'storageParams', AdapterBase) _contextAttributes = list(IResourceAdapter) storageName = None diff --git a/configure.zcml b/configure.zcml index 8910dcc..f237c72 100644 --- a/configure.zcml +++ b/configure.zcml @@ -331,6 +331,7 @@ --> + - - diff --git a/organize/configure.zcml b/organize/configure.zcml index 8a68ee2..e54ca82 100644 --- a/organize/configure.zcml +++ b/organize/configure.zcml @@ -43,7 +43,6 @@ - @@ -70,5 +69,8 @@ + + + diff --git a/organize/personal/README.txt b/organize/personal/README.txt index e076c97..e4e2dfe 100644 --- a/organize/personal/README.txt +++ b/organize/personal/README.txt @@ -38,55 +38,9 @@ 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) - -One step is still missing: As we are now working with a real principal -the security checks e.g. in views are active. So we have to provide -our user with the necessary permissions. - - >>> grantPermission = setupData.rolePermissions.grantPermissionToRole - >>> assignRole = setupData.principalRoles.assignRoleToPrincipal - >>> grantPermission('zope.View', 'zope.Member') - >>> assignRole('zope.Member', 'users.john') + >>> from loops.organize.tests import setupObjectsForTesting + >>> setupData = setupObjectsForTesting(site, concepts) + >>> johnC = setupData.johnC Working with the favorites storage ---------------------------------- @@ -116,7 +70,7 @@ The adapter provides convenience methods for accessing the favorites storage. So we are now ready to query the favorites. - >>> favs = favorites.query(userName=johnCId) + >>> favs = list(favorites.query(userName=johnCId)) >>> favs [] @@ -149,7 +103,7 @@ Let's now trigger the saving of a favorite. >>> view.add() - >>> len(favorites.query(userName=johnCId)) + >>> len(list(favorites.query(userName=johnCId))) 2 >>> d002Id = util.getUidForObject(resources['d001.txt']) @@ -157,5 +111,11 @@ Let's now trigger the saving of a favorite. >>> view = FavoriteView(home, request) >>> view.remove() - >>> len(favorites.query(userName=johnCId)) + >>> len(list(favorites.query(userName=johnCId))) 1 + + +Fin de partie +============= + + >>> placefulTearDown() diff --git a/process/README.txt b/organize/process/README.txt similarity index 95% rename from process/README.txt rename to organize/process/README.txt index dd1bf66..340912f 100644 --- a/process/README.txt +++ b/organize/process/README.txt @@ -31,7 +31,7 @@ ZCML setup): >>> from loops.interfaces import ILoops >>> from loops.setup import ISetupManager - >>> from loops.process.setup import SetupManager + >>> from loops.organize.process.setup import SetupManager >>> component.provideAdapter(SetupManager, (ILoops,), ISetupManager, ... name='process') @@ -53,7 +53,7 @@ Manage processes The classes used in this package are just adapters to IConcept. - >>> from loops.process.definition import Process + >>> from loops.organize.process.definition import Process >>> from cybertools.process.interfaces import IProcess >>> component.provideAdapter(Process, (IConcept,), IProcess) diff --git a/process/__init__.py b/organize/process/__init__.py similarity index 100% rename from process/__init__.py rename to organize/process/__init__.py diff --git a/process/configure.zcml b/organize/process/configure.zcml similarity index 75% rename from process/configure.zcml rename to organize/process/configure.zcml index a406305..71c2583 100644 --- a/process/configure.zcml +++ b/organize/process/configure.zcml @@ -8,11 +8,10 @@ - - - + - diff --git a/process/definition.py b/organize/process/definition.py similarity index 100% rename from process/definition.py rename to organize/process/definition.py diff --git a/process/interfaces.py b/organize/process/interfaces.py similarity index 100% rename from process/interfaces.py rename to organize/process/interfaces.py diff --git a/process/setup.py b/organize/process/setup.py similarity index 100% rename from process/setup.py rename to organize/process/setup.py diff --git a/process/tests.py b/organize/process/tests.py similarity index 100% rename from process/tests.py rename to organize/process/tests.py diff --git a/stateful/README.txt b/organize/stateful/README.txt similarity index 94% rename from stateful/README.txt rename to organize/stateful/README.txt index 5eaca2a..384eefd 100644 --- a/stateful/README.txt +++ b/organize/stateful/README.txt @@ -32,7 +32,7 @@ making an object statful we'll use an adapter. >>> component.provideUtility(simplePublishing, IStatesDefinition, ... name='loops.simple_publishing') - >>> from loops.stateful.base import SimplePublishable + >>> from loops.organize.stateful.base import SimplePublishable >>> component.provideAdapter(SimplePublishable, name='loops.simple_publishing') We may now take a document and adapt it to IStateful so that we may @@ -57,3 +57,9 @@ not just kept in the adapter. >>> statefulDoc01.state 'published' + + +Fin de partie +============= + + >>> placefulTearDown() diff --git a/stateful/__init__.py b/organize/stateful/__init__.py similarity index 100% rename from stateful/__init__.py rename to organize/stateful/__init__.py diff --git a/stateful/base.py b/organize/stateful/base.py similarity index 100% rename from stateful/base.py rename to organize/stateful/base.py diff --git a/stateful/configure.zcml b/organize/stateful/configure.zcml similarity index 81% rename from stateful/configure.zcml rename to organize/stateful/configure.zcml index d541a00..37923c9 100644 --- a/stateful/configure.zcml +++ b/organize/stateful/configure.zcml @@ -10,9 +10,9 @@ name="loops.simple_publishing" /> - + >> 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.tracking.setup import SetupManager + >>> component.provideAdapter(SetupManager, name='organize.tracking') + + >>> from loops.tests.setup import TestSite + >>> t = TestSite(site) + >>> concepts, resources, views = t.setup() + + +Tracking Changes and Object Access +================================== + + >>> loopsRoot = concepts.getLoopsRoot() + >>> records = loopsRoot.getRecordManager() + >>> changes = records['changes'] + +User management setup +--------------------- + +In order to be able to login and store 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 setupObjectsForTesting + >>> setupData = setupObjectsForTesting(site, concepts) + >>> johnC = setupData.johnC + +Recording changes to objects +---------------------------- + + >>> from loops.organize.tracking.change import recordModification + >>> component.provideHandler(recordModification) + + >>> tTask = concepts['task'] + >>> from loops.concept import Concept + >>> from loops.setup import addAndConfigureObject + >>> t01 = addAndConfigureObject(concepts, Concept, 't01', conceptType=tTask, + ... title='Develop change tracking') + + >>> len(changes) + 1 + + +Fin de partie +============= + + >>> placefulTearDown() diff --git a/organize/tracking/__init__.py b/organize/tracking/__init__.py new file mode 100644 index 0000000..38314f3 --- /dev/null +++ b/organize/tracking/__init__.py @@ -0,0 +1,3 @@ +""" +$Id$ +""" diff --git a/organize/tracking/change.py b/organize/tracking/change.py new file mode 100644 index 0000000..5a22ff7 --- /dev/null +++ b/organize/tracking/change.py @@ -0,0 +1,91 @@ +# +# 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 +# + +""" +Recording changes to loops objects. + +$Id$ +""" + +from zope.app.container.interfaces import IObjectAddedEvent, IObjectRemovedEvent +from zope.app.container.interfaces import IObjectMovedEvent +from zope.cachedescriptors.property import Lazy +from zope.component import adapter +from zope.lifecycleevent.interfaces import IObjectModifiedEvent, IObjectCreatedEvent + +from cybertools.tracking.btree import Track, getTimeStamp +from loops.concept import ConceptManager +from loops.resource import ResourceManager +from loops.interfaces import IAssignmentEvent, IDeassignmentEvent +from loops.interfaces import ILoopsObject +from loops.organize.party import getPersonForUser +from loops.security.common import getCurrentPrincipal +from loops import util + + +class ChangeManager(object): + + context = None + + def __init__(self, context): + if isinstance(context, (ConceptManager, ResourceManager)): + return + self.context = context + + @Lazy + def valid(self): + return not (self.context is None or + self.storage is None or + self.person is None) + + @Lazy + def loopsRoot(self): + return self.context.getLoopsRoot() + + @Lazy + def storage(self): + records = self.loopsRoot.getRecordManager() + if records is not None: + return records.get('changes') + return None + + @Lazy + def person(self): + principal = getCurrentPrincipal() + if principal is not None: + return getPersonForUser(self.context, principal=principal) + return None + + def recordModification(self, event=None): + if not self.valid: + return + uid = util.getUidForObject(self.context) + personUid = util.getUidForObject(self.person) + last = self.storage.getLastUserTrack(uid, 0, personUid) + if last is None or last.metadata['timeStamp'] < getTimeStamp() - 5: + self.storage.saveUserTrack(uid, 0, personUid, dict(action='modify')) + + +class ChangeRecord(Track): + + typeName = 'ChangeRecord' + + +@adapter(ILoopsObject, IObjectModifiedEvent) +def recordModification(obj, event): + ChangeManager(obj).recordModification(event) diff --git a/organize/tracking/configure.zcml b/organize/tracking/configure.zcml new file mode 100644 index 0000000..c192bec --- /dev/null +++ b/organize/tracking/configure.zcml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/organize/tracking/setup.py b/organize/tracking/setup.py new file mode 100644 index 0000000..6f4fbdf --- /dev/null +++ b/organize/tracking/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.tracking package. + +$Id$ +""" + +from zope.component import adapts +from zope.interface import implements, Interface + +from cybertools.tracking.btree import TrackingStorage +from loops.organize.tracking.change import ChangeRecord +from loops.setup import SetupManager as BaseSetupManager + + +class SetupManager(BaseSetupManager): + + def setup(self): + records = self.context.getRecordManager() + changes = self.addObject(records, TrackingStorage, 'changes', + trackFactory=ChangeRecord) diff --git a/organize/tracking/tests.py b/organize/tracking/tests.py new file mode 100755 index 0000000..310c225 --- /dev/null +++ b/organize/tracking/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.tracking 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/organize/util.py b/organize/util.py index 6189896..d304342 100644 --- a/organize/util.py +++ b/organize/util.py @@ -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 @@ -82,3 +82,11 @@ def getInternalPrincipal(id, context=None): if next is not None: return next.getPrincipal(pau.prefix + id) raise PrincipalLookupError(id) + + +def getTrackingStorage(obj, name): + records = obj.getLoopsRoot().getRecordManager() + if records is not None: + return records.get(name) + return None +