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 | # helper functions for specifying automatic attribute handling | ||||||
| 
 | 
 | ||||||
| def adapterAttributes(*args): | def collectAttributeNames(lst, name): | ||||||
|     attrs = [] |     attrs = [] | ||||||
|     for arg in args: |     for arg in lst: | ||||||
|         if isinstance(arg, basestring): |         if isinstance(arg, basestring): | ||||||
|             attrs.append(arg) |             attrs.append(arg) | ||||||
|         elif isinstance(arg, type): |         elif isinstance(arg, type): | ||||||
|             attrs.extend(list(arg._adapterAttributes)) |             attrs.extend(list(getattr(arg, name))) | ||||||
|         else: |         else: | ||||||
|             raise ValueError("Argument must be string or class, '%s' is '%s'." % |             raise ValueError("Argument must be string or class, '%s' is '%s'." % | ||||||
|                              (arg, type(arg))) |                              (arg, type(arg))) | ||||||
|     return tuple(attrs) |     return tuple(attrs) | ||||||
| 
 | 
 | ||||||
|  | def adapterAttributes(*args): | ||||||
|  |     return collectAttributeNames(args, '_adapterAttributes') | ||||||
|  | 
 | ||||||
| def contextAttributes(*args): | def contextAttributes(*args): | ||||||
|     attrs = [] |     return collectAttributeNames(args, '_contextAttributes') | ||||||
|     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 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # type interface adapters | # type interface adapters | ||||||
|  | @ -133,7 +127,7 @@ class ResourceAdapterBase(AdapterBase): | ||||||
|     implements(IStorageInfo) |     implements(IStorageInfo) | ||||||
|     adapts(IResource) |     adapts(IResource) | ||||||
| 
 | 
 | ||||||
|     _adapterAttributes = ('storageName', 'storageParams', ) + AdapterBase._adapterAttributes |     _adapterAttributes = adapterAttributes('storageName', 'storageParams', AdapterBase) | ||||||
|     _contextAttributes = list(IResourceAdapter) |     _contextAttributes = list(IResourceAdapter) | ||||||
| 
 | 
 | ||||||
|     storageName = None |     storageName = None | ||||||
|  |  | ||||||
|  | @ -331,6 +331,7 @@ | ||||||
|   <adapter factory="loops.external.NodesExporter" /> |   <adapter factory="loops.external.NodesExporter" /> | ||||||
|   <adapter factory="loops.external.NodesImporter" />--> |   <adapter factory="loops.external.NodesImporter" />--> | ||||||
| 
 | 
 | ||||||
|  |   <!-- obsolete - remove: --> | ||||||
|   <adapter factory="loops.target.DocumentProxy" |   <adapter factory="loops.target.DocumentProxy" | ||||||
|            permission="zope.ManageContent" /> |            permission="zope.ManageContent" /> | ||||||
|   <adapter factory="loops.target.MediaAssetProxy" |   <adapter factory="loops.target.MediaAssetProxy" | ||||||
|  | @ -405,11 +406,9 @@ | ||||||
|   <include package=".integrator" /> |   <include package=".integrator" /> | ||||||
|   <include package=".knowledge" /> |   <include package=".knowledge" /> | ||||||
|   <include package=".organize" /> |   <include package=".organize" /> | ||||||
|   <include package=".process" /> |  | ||||||
|   <include package=".rest" /> |   <include package=".rest" /> | ||||||
|   <include package=".search" /> |   <include package=".search" /> | ||||||
|   <include package=".security" /> |   <include package=".security" /> | ||||||
|   <include package=".stateful" /> |  | ||||||
|   <include package=".versioning" /> |   <include package=".versioning" /> | ||||||
|   <include package=".xmlrpc" /> |   <include package=".xmlrpc" /> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -43,7 +43,6 @@ | ||||||
| 
 | 
 | ||||||
|   <zope:adapter factory="loops.organize.member.MemberRegistrationManager" |   <zope:adapter factory="loops.organize.member.MemberRegistrationManager" | ||||||
|                 trusted="True" /> |                 trusted="True" /> | ||||||
| 
 |  | ||||||
|   <zope:class class="loops.organize.member.MemberRegistrationManager"> |   <zope:class class="loops.organize.member.MemberRegistrationManager"> | ||||||
|     <require permission ="zope.Public" |     <require permission ="zope.Public" | ||||||
|              interface="loops.organize.interfaces.IMemberRegistrationManager" /> |              interface="loops.organize.interfaces.IMemberRegistrationManager" /> | ||||||
|  | @ -70,5 +69,8 @@ | ||||||
| 
 | 
 | ||||||
|   <include package=".browser" /> |   <include package=".browser" /> | ||||||
|   <include package=".personal" /> |   <include package=".personal" /> | ||||||
|  |   <include package=".process" /> | ||||||
|  |   <include package=".stateful" /> | ||||||
|  |   <include package=".tracking" /> | ||||||
| 
 | 
 | ||||||
| </configure> | </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, | we have to prepare our environment. We need some basic adapter registrations, | ||||||
| and a pluggable authentication utility with a principal folder. | and a pluggable authentication utility with a principal folder. | ||||||
| 
 | 
 | ||||||
|   >>> from loops.organize.tests import setupUtilitiesAndAdapters |   >>> from loops.organize.tests import setupObjectsForTesting | ||||||
|   >>> setupData = setupUtilitiesAndAdapters(loopsRoot) |   >>> setupData = setupObjectsForTesting(site, concepts) | ||||||
| 
 |   >>> johnC = setupData.johnC | ||||||
|   >>> 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') |  | ||||||
| 
 | 
 | ||||||
| Working with the favorites storage | 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. | So we are now ready to query the favorites. | ||||||
| 
 | 
 | ||||||
|   >>> favs = favorites.query(userName=johnCId) |   >>> favs = list(favorites.query(userName=johnCId)) | ||||||
|   >>> favs |   >>> favs | ||||||
|   [<Favorite ['29', 1, '35', '...']: {}>] |   [<Favorite ['29', 1, '35', '...']: {}>] | ||||||
| 
 | 
 | ||||||
|  | @ -149,7 +103,7 @@ Let's now trigger the saving of a favorite. | ||||||
| 
 | 
 | ||||||
|   >>> view.add() |   >>> view.add() | ||||||
| 
 | 
 | ||||||
|   >>> len(favorites.query(userName=johnCId)) |   >>> len(list(favorites.query(userName=johnCId))) | ||||||
|   2 |   2 | ||||||
| 
 | 
 | ||||||
|   >>> d002Id = util.getUidForObject(resources['d001.txt']) |   >>> d002Id = util.getUidForObject(resources['d001.txt']) | ||||||
|  | @ -157,5 +111,11 @@ Let's now trigger the saving of a favorite. | ||||||
|   >>> view = FavoriteView(home, request) |   >>> view = FavoriteView(home, request) | ||||||
|   >>> view.remove() |   >>> view.remove() | ||||||
| 
 | 
 | ||||||
|   >>> len(favorites.query(userName=johnCId)) |   >>> len(list(favorites.query(userName=johnCId))) | ||||||
|   1 |   1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Fin de partie | ||||||
|  | ============= | ||||||
|  | 
 | ||||||
|  |   >>> placefulTearDown() | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ ZCML setup): | ||||||
| 
 | 
 | ||||||
|   >>> from loops.interfaces import ILoops |   >>> from loops.interfaces import ILoops | ||||||
|   >>> from loops.setup import ISetupManager |   >>> from loops.setup import ISetupManager | ||||||
|   >>> from loops.process.setup import SetupManager |   >>> from loops.organize.process.setup import SetupManager | ||||||
|   >>> component.provideAdapter(SetupManager, (ILoops,), ISetupManager, |   >>> component.provideAdapter(SetupManager, (ILoops,), ISetupManager, | ||||||
|   ...                           name='process') |   ...                           name='process') | ||||||
| 
 | 
 | ||||||
|  | @ -53,7 +53,7 @@ Manage processes | ||||||
| 
 | 
 | ||||||
| The classes used in this package are just adapters to IConcept. | 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 |   >>> from cybertools.process.interfaces import IProcess | ||||||
|   >>> component.provideAdapter(Process, (IConcept,), IProcess) |   >>> component.provideAdapter(Process, (IConcept,), IProcess) | ||||||
| 
 | 
 | ||||||
|  | @ -8,11 +8,10 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- process/workflow management stuff --> |   <!-- process/workflow management stuff --> | ||||||
| 
 | 
 | ||||||
|   <zope:adapter factory="loops.process.definition.Process" |   <zope:adapter factory="loops.organize.process.definition.Process" | ||||||
|                 provides="cybertools.process.interfaces.IProcess" |                 provides="cybertools.process.interfaces.IProcess" | ||||||
|                 trusted="True" /> |                 trusted="True" /> | ||||||
| 
 |   <zope:class class="loops.organize.process.definition.Process"> | ||||||
|   <zope:class class="loops.process.definition.Process"> |  | ||||||
|     <require permission="zope.View" |     <require permission="zope.View" | ||||||
|              interface="cybertools.process.interfaces.IProcess" /> |              interface="cybertools.process.interfaces.IProcess" /> | ||||||
|     <require permission="zope.ManageContent" |     <require permission="zope.ManageContent" | ||||||
|  | @ -21,7 +20,7 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- setup --> |   <!-- setup --> | ||||||
| 
 | 
 | ||||||
|   <zope:adapter factory="loops.process.setup.SetupManager" |   <zope:adapter factory="loops.organize.process.setup.SetupManager" | ||||||
|                 name="process" /> |                 name="process" /> | ||||||
| 
 | 
 | ||||||
| </configure> | </configure> | ||||||
|  | @ -32,7 +32,7 @@ making an object statful we'll use an adapter. | ||||||
|   >>> component.provideUtility(simplePublishing, IStatesDefinition, |   >>> component.provideUtility(simplePublishing, IStatesDefinition, | ||||||
|   ...                          name='loops.simple_publishing') |   ...                          name='loops.simple_publishing') | ||||||
| 
 | 
 | ||||||
|   >>> from loops.stateful.base import SimplePublishable |   >>> from loops.organize.stateful.base import SimplePublishable | ||||||
|   >>> component.provideAdapter(SimplePublishable, name='loops.simple_publishing') |   >>> component.provideAdapter(SimplePublishable, name='loops.simple_publishing') | ||||||
| 
 | 
 | ||||||
| We may now take a document and adapt it to IStateful so that we may | 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 |   >>> statefulDoc01.state | ||||||
|   'published' |   'published' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Fin de partie | ||||||
|  | ============= | ||||||
|  | 
 | ||||||
|  |   >>> placefulTearDown() | ||||||
|  | @ -10,9 +10,9 @@ | ||||||
|         name="loops.simple_publishing" /> |         name="loops.simple_publishing" /> | ||||||
| 
 | 
 | ||||||
|   <zope:adapter |   <zope:adapter | ||||||
|         factory="loops.stateful.base.SimplePublishable" |         factory="loops.organize.stateful.base.SimplePublishable" | ||||||
|         name="loops.simple_publishing" trusted="True" /> |         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" |     <require permission="zope.View" | ||||||
|              interface="cybertools.stateful.interfaces.IStateful" /> |              interface="cybertools.stateful.interfaces.IStateful" /> | ||||||
|     <require permission="zope.ManageContent" |     <require permission="zope.ManageContent" | ||||||
|  | @ -2,21 +2,32 @@ | ||||||
| 
 | 
 | ||||||
| import unittest, doctest | import unittest, doctest | ||||||
| from zope.testing.doctestunit import DocFileSuite | from zope.testing.doctestunit import DocFileSuite | ||||||
| from zope.app.testing import ztapi |  | ||||||
| from zope.interface.verify import verifyClass | from zope.interface.verify import verifyClass | ||||||
| 
 | 
 | ||||||
| from zope import component | 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 PrincipalAnnotationUtility | ||||||
| from zope.app.principalannotation import annotations | from zope.app.principalannotation import annotations | ||||||
|  | from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility | ||||||
| from zope.app.security.interfaces import IAuthentication | from zope.app.security.interfaces import IAuthentication | ||||||
| from zope.app.security.principalregistry import PrincipalRegistry | from zope.app.security.principalregistry import PrincipalRegistry | ||||||
| from zope.app.securitypolicy.interfaces import IRolePermissionManager | from zope.app.securitypolicy.interfaces import IRolePermissionManager | ||||||
| from zope.app.securitypolicy.interfaces import IPrincipalRoleManager | from zope.app.securitypolicy.interfaces import IPrincipalRoleManager | ||||||
| 
 | 
 | ||||||
| from cybertools.util.jeep import Jeep | 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.interfaces import IPerson | ||||||
| from loops.organize.party import Person | 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): | def setupUtilitiesAndAdapters(loopsRoot): | ||||||
|  | @ -25,6 +36,8 @@ def setupUtilitiesAndAdapters(loopsRoot): | ||||||
|     principalAnnotations = PrincipalAnnotationUtility() |     principalAnnotations = PrincipalAnnotationUtility() | ||||||
|     component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility) |     component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility) | ||||||
|     component.provideAdapter(Person, provides=IPerson) |     component.provideAdapter(Person, provides=IPerson) | ||||||
|  |     component.provideAdapter(Task) | ||||||
|  |     component.provideAdapter(FoundPrincipalFactory) | ||||||
|     return Jeep(( |     return Jeep(( | ||||||
|             ('auth', auth), |             ('auth', auth), | ||||||
|             ('principalAnnotations', principalAnnotations), |             ('principalAnnotations', principalAnnotations), | ||||||
|  | @ -32,6 +45,30 @@ def setupUtilitiesAndAdapters(loopsRoot): | ||||||
|             ('principalRoles', IPrincipalRoleManager(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): | class Test(unittest.TestCase): | ||||||
|     "Basic tests for the organize sub-package." |     "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 | #  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 | #  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: |     if next is not None: | ||||||
|         return next.getPrincipal(pau.prefix + id) |         return next.getPrincipal(pau.prefix + id) | ||||||
|     raise PrincipalLookupError(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
	
	 helmutm
						helmutm