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:
parent
2c6cdafe10
commit
4d5bfed47f
24 changed files with 328 additions and 81 deletions
22
common.py
22
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
|
||||
|
|
|
@ -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" />
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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>
|
|
@ -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()
|
|
@ -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"
|
|
@ -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."
|
||||
|
|
64
organize/tracking/README.txt
Normal file
64
organize/tracking/README.txt
Normal 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()
|
3
organize/tracking/__init__.py
Normal file
3
organize/tracking/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
$Id$
|
||||
"""
|
91
organize/tracking/change.py
Normal file
91
organize/tracking/change.py
Normal 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)
|
24
organize/tracking/configure.zcml
Normal file
24
organize/tracking/configure.zcml
Normal 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>
|
38
organize/tracking/setup.py
Normal file
38
organize/tracking/setup.py
Normal 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
22
organize/tracking/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 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')
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue