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
This commit is contained in:
helmutm 2008-04-09 10:03:52 +00:00
parent 2c6cdafe10
commit 4d5bfed47f
24 changed files with 328 additions and 81 deletions

View file

@ -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

View file

@ -331,6 +331,7 @@
<adapter factory="loops.external.NodesExporter" />
<adapter factory="loops.external.NodesImporter" />-->
<!-- obsolete - remove: -->
<adapter factory="loops.target.DocumentProxy"
permission="zope.ManageContent" />
<adapter factory="loops.target.MediaAssetProxy"
@ -405,11 +406,9 @@
<include package=".integrator" />
<include package=".knowledge" />
<include package=".organize" />
<include package=".process" />
<include package=".rest" />
<include package=".search" />
<include package=".security" />
<include package=".stateful" />
<include package=".versioning" />
<include package=".xmlrpc" />

View file

@ -43,7 +43,6 @@
<zope:adapter factory="loops.organize.member.MemberRegistrationManager"
trusted="True" />
<zope:class class="loops.organize.member.MemberRegistrationManager">
<require permission ="zope.Public"
interface="loops.organize.interfaces.IMemberRegistrationManager" />
@ -70,5 +69,8 @@
<include package=".browser" />
<include package=".personal" />
<include package=".process" />
<include package=".stateful" />
<include package=".tracking" />
</configure>

View file

@ -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
[<Favorite ['29', 1, '35', '...']: {}>]
@ -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()

View file

@ -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)

View file

@ -8,11 +8,10 @@
<!-- process/workflow management stuff -->
<zope:adapter factory="loops.process.definition.Process"
<zope:adapter factory="loops.organize.process.definition.Process"
provides="cybertools.process.interfaces.IProcess"
trusted="True" />
<zope:class class="loops.process.definition.Process">
<zope:class class="loops.organize.process.definition.Process">
<require permission="zope.View"
interface="cybertools.process.interfaces.IProcess" />
<require permission="zope.ManageContent"
@ -21,7 +20,7 @@
<!-- setup -->
<zope:adapter factory="loops.process.setup.SetupManager"
<zope:adapter factory="loops.organize.process.setup.SetupManager"
name="process" />
</configure>

View file

@ -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()

View file

@ -10,9 +10,9 @@
name="loops.simple_publishing" />
<zope:adapter
factory="loops.stateful.base.SimplePublishable"
factory="loops.organize.stateful.base.SimplePublishable"
name="loops.simple_publishing" trusted="True" />
<zope:class class="loops.stateful.base.SimplePublishable">
<zope:class class="loops.organize.stateful.base.SimplePublishable">
<require permission="zope.View"
interface="cybertools.stateful.interfaces.IStateful" />
<require permission="zope.ManageContent"

View file

@ -2,21 +2,32 @@
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
from zope.app.testing import ztapi
from zope.interface.verify import verifyClass
from zope import component
from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
from zope.app.appsetup.bootstrap import ensureUtility
from zope.app.authentication.authentication import PluggableAuthentication
from zope.app.authentication.interfaces import IAuthenticatorPlugin
from zope.app.authentication.principalfolder import FoundPrincipalFactory
from zope.app.authentication.principalfolder import InternalPrincipal
from zope.app.authentication.principalfolder import Principal
from zope.app.authentication.principalfolder import PrincipalFolder
from zope.app.principalannotation import PrincipalAnnotationUtility
from zope.app.principalannotation import annotations
from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
from zope.app.security.interfaces import IAuthentication
from zope.app.security.principalregistry import PrincipalRegistry
from zope.app.securitypolicy.interfaces import IRolePermissionManager
from zope.app.securitypolicy.interfaces import IPrincipalRoleManager
from cybertools.util.jeep import Jeep
from loops.common import adapted
from loops.concept import Concept
from loops.organize.interfaces import IPerson
from loops.organize.party import Person
from loops.organize.task import Task
from loops.setup import addAndConfigureObject
from loops.tests.auth import login
def setupUtilitiesAndAdapters(loopsRoot):
@ -25,6 +36,8 @@ def setupUtilitiesAndAdapters(loopsRoot):
principalAnnotations = PrincipalAnnotationUtility()
component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility)
component.provideAdapter(Person, provides=IPerson)
component.provideAdapter(Task)
component.provideAdapter(FoundPrincipalFactory)
return Jeep((
('auth', auth),
('principalAnnotations', principalAnnotations),
@ -32,6 +45,30 @@ def setupUtilitiesAndAdapters(loopsRoot):
('principalRoles', IPrincipalRoleManager(loopsRoot)),
))
def setupObjectsForTesting(site, concepts):
loopsRoot = concepts.getLoopsRoot()
setupData = setupUtilitiesAndAdapters(loopsRoot)
ensureUtility(site, IAuthentication, '', PluggableAuthentication,
copy_to_zlog=False, asObject=True)
pau = component.getUtility(IAuthentication, context=site)
# create principal folder and user(s)
pFolder = PrincipalFolder('users.')
pau['users'] = pFolder
pau.authenticatorPlugins = ('users',)
pFolder['john'] = InternalPrincipal('john', 'xx', u'John')
# create person and log in
johnC = addAndConfigureObject(concepts, Concept, 'john',
conceptType=concepts['person'], title=u'john', userId='users.john')
pJohn = Principal('users.john', 'xxx', u'John')
login(pJohn)
# grant roles and permissions
grantPermission = setupData.rolePermissions.grantPermissionToRole
assignRole = setupData.principalRoles.assignRoleToPrincipal
grantPermission('zope.View', 'zope.Member')
assignRole('zope.Member', 'users.john')
setupData.update(dict(johnC=johnC))
return setupData
class Test(unittest.TestCase):
"Basic tests for the organize sub-package."

View file

@ -0,0 +1,64 @@
===============================================================
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.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()

View file

@ -0,0 +1,3 @@
"""
$Id$
"""

View file

@ -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)

View file

@ -0,0 +1,24 @@
<!-- $Id$ -->
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="loops">
<class class="loops.organize.tracking.change.ChangeRecord">
<require permission="zope.View"
interface="cybertools.tracking.interfaces.ITrack" />
<require permission="zope.ManageContent"
set_schema="cybertools.tracking.interfaces.ITrack" />
</class>
<class class="loops.organize.tracking.change.ChangeRecord">
<require permission="zope.View"
interface="cybertools.tracking.interfaces.ITrackingStorage" />
</class>
<subscriber handler="loops.organize.tracking.change.recordModification" />
<adapter factory="loops.organize.tracking.setup.SetupManager"
name="organize.tracking" />
</configure>

View file

@ -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)

22
organize/tracking/tests.py Executable file
View file

@ -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')

View file

@ -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