===============================================================
loops - Linked Objects for Organization and Processing Services
===============================================================
($Id$)
Note: This packages depends on cybertools.organize.
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.tests.setup import TestSite
>>> t = TestSite(site)
>>> concepts, resources, views = t.setup()
>>> from loops import util
>>> loopsRoot = site['loops']
>>> loopsId = util.getUidForObject(loopsRoot)
>>> from loops.organize.tests import setupUtilitiesAndAdapters
>>> setupData = setupUtilitiesAndAdapters(loopsRoot)
>>> type = concepts['type']
>>> person = concepts['person']
>>> from loops.concept import Concept
>>> johnC = concepts['john'] = Concept(u'John')
>>> johnC.conceptType = person
Organizations: Persons (and Users), Institutions, Addresses...
==============================================================
The classes used in this package are just adapters to IConcept.
>>> from loops.organize.interfaces import IPerson
>>> john = IPerson(johnC)
>>> john.title
u'John'
>>> john.firstName = u'John'
>>> johnC._firstName
u'John'
>>> john.lastName is None
True
>>> john.someOtherAttribute
Traceback (most recent call last):
...
AttributeError: ... someOtherAttribute
We can use the age calculations from the base Person class:
>>> from datetime import date
>>> john.birthDate = date(1980, 3, 26)
>>> john.ageAt(date(2006, 5, 12))
26
>>> john.age >= 26
True
A person can be associated with a user of the system by setting the
userId attribute; this will also register the person concept in the
corresponding principal annotations so that there is a fast way to find
the person(s) belonging to a user/principal.
For testing, we first have to provide the needed utilities and settings
(in real life this is all done during Zope startup):
>>> auth = setupData.auth
>>> principalAnnotations = setupData.principalAnnotations
>>> principal = auth.definePrincipal('users.john', u'John', login='john')
>>> john.userId = 'users.john'
>>> annotations = principalAnnotations.getAnnotationsById('users.john')
>>> from loops.organize.party import ANNOTATION_KEY
>>> annotations[ANNOTATION_KEY][loopsId] == johnC
True
Change a userId assignment:
>>> principal = auth.definePrincipal('users.johnny', u'Johnny', login='johnny')
>>> john.userId = 'users.johnny'
>>> annotations = principalAnnotations.getAnnotationsById('users.johnny')
>>> annotations[ANNOTATION_KEY][loopsId] == johnC
True
>>> annotations = principalAnnotations.getAnnotationsById('users.john')
>>> annotations[ANNOTATION_KEY][loopsId] is None
True
>>> john.userId = None
>>> annotations = principalAnnotations.getAnnotationsById('users.johnny')
>>> annotations[ANNOTATION_KEY][loopsId] is None
True
Deleting a person with a userId assigned removes the corresponding
principal annotation:
>>> from zope.app.container.interfaces import IObjectRemovedEvent
>>> from zope.app.container.contained import ObjectRemovedEvent
>>> from zope.event import notify
>>> from zope.interface import Interface
>>> from loops.interfaces import IConcept
>>> from loops.organize.party import removePersonReferenceFromPrincipal
>>> from zope.app.testing import ztapi
>>> ztapi.subscribe([IConcept, IObjectRemovedEvent], None,
... removePersonReferenceFromPrincipal)
>>> john.userId = 'users.john'
>>> annotations = principalAnnotations.getAnnotationsById('users.john')
>>> annotations[ANNOTATION_KEY][loopsId] == johnC
True
>>> del concepts['john']
>>> annotations = principalAnnotations.getAnnotationsById('users.john')
>>> annotations[ANNOTATION_KEY][loopsId] is None
True
If we try to assign a userId of a principal that already has a person
concept assigned we should get an error:
>>> johnC = concepts['john'] = Concept(u'John')
>>> johnC.conceptType = person
>>> john = IPerson(johnC)
>>> john.userId = 'users.john'
>>> marthaC = concepts['martha'] = Concept(u'Martha')
>>> marthaC.conceptType = person
>>> martha = IPerson(marthaC)
>>> martha.userId = 'users.john'
Traceback (most recent call last):
...
ValueError: ...
Member Registrations
====================
The member registration needs the whole pluggable authentication stuff
with a principal folder:
>>> 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('loops.')
>>> pau['loops'] = pFolder
>>> pau.authenticatorPlugins = ('loops',)
In addition, we have to create at least one node in the view space
and register an IMemberRegistrationManager adapter for the loops root object:
>>> from loops.view import Node
>>> menu = views['menu'] = Node('Home')
>>> menu.nodeType = 'menu'
>>> from loops.organize.member import MemberRegistrationManager
>>> component.provideAdapter(MemberRegistrationManager)
Now we can enter the registration info for a new member (after having made
sure that a principal object can be served by a corresponding factory):
>>> from zope.app.authentication.principalfolder import FoundPrincipalFactory
>>> component.provideAdapter(FoundPrincipalFactory)
>>> data = {'loginName': u'newuser',
... 'password': u'quack',
... 'passwordConfirm': u'quack',
... 'lastName': u'Sawyer',
... 'firstName': u'Tom',
... 'email': u'tommy@sawyer.com',
... 'action': 'update',}
and register it.
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest(form=data)
>>> from loops.organize.browser.member import MemberRegistration
>>> regView = MemberRegistration(menu, request)
>>> regView.update()
False
>>> from loops.common import adapted
>>> person = concepts['person.newuser']
>>> pa = adapted(person)
>>> pa.lastName, pa.userId
(u'Sawyer', u'loops.newuser')
Now we can also retrieve it from the authentication utility:
>>> pau.getPrincipal('loops.newuser').title
u'Tom Sawyer'
Change Password
---------------
>>> data = {'oldPassword': u'tiger',
... 'password': u'lion',
... 'passwordConfirm': u'lion',
... 'action': 'update'}
>>> request = TestRequest(form=data)
We need a principal for testing the login stuff:
>>> from zope.app.authentication.principalfolder import InternalPrincipal
>>> principal = InternalPrincipal('scott', 'tiger', 'Scotty')
>>> request.setPrincipal(principal)
>>> from loops.organize.browser.member import PasswordChange
>>> pwcView = PasswordChange(menu, request)
>>> pwcView.update()
False
Security
========
Automatic security settings on persons
--------------------------------------
>>> from zope.traversing.api import getName
>>> list(sorted(getName(c) for c in concepts['person'].getChildren()))
[u'john', u'martha', u'person.newuser']
Person objects that have a user assigned to them receive this user
(principal) as their owner.
>>> from zope.app.securitypolicy.interfaces import IPrincipalRoleMap
>>> IPrincipalRoleMap(concepts['john']).getPrincipalsAndRoles()
[('loops.Owner', 'users.john', PermissionSetting: Allow)]
>>> IPrincipalRoleMap(concepts['person.newuser']).getPrincipalsAndRoles()
[('loops.Owner', u'loops.newuser', PermissionSetting: Allow)]
The person ``martha`` hasn't got a user id, so there is no role assigned
to it.
>>> IPrincipalRoleMap(concepts['martha']).getPrincipalsAndRoles()
[]
Only the owner (and a few other privileged people) should be able
to edit a person object.
We also need an interaction with a participation based on the principal
whose permissions we want to check.
>>> from zope.app.authentication.principalfolder import Principal
>>> pJohn = Principal('users.john', 'xxx', u'John')
>>> from loops.tests.auth import login
>>> login(pJohn)
We also want to grant some global permissions and roles, i.e. on the site or
loops root level.
>>> rolePermissions = setupData.rolePermissions
>>> rolePermissions.grantPermissionToRole('zope.View', 'zope.Member')
>>> principalRoles = setupData.principalRoles
>>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.john')
Now we are ready to look for the real stuff - what John is allowed to do.
>>> from zope.security import canAccess, canWrite, checkPermission
>>> john = concepts['john']
>>> canAccess(john, 'title')
True
Person objects that have an owner may be modified by this owner.
>>> canWrite(john, 'title')
True
So let's try with another user with another role setting.
>>> rolePermissions.grantPermissionToRole('zope.ManageContent', 'loops.Staff')
>>> principalRoles.assignRoleToPrincipal('loops.Staff', 'users.martha')
>>> principalRoles.assignRoleToPrincipal('zope.Member', 'users.martha')
>>> pMartha = Principal('users.martha', 'xxx', u'Martha')
>>> login(pMartha)
>>> canAccess(john, 'title')
True
>>> canWrite(john, 'title')
False
If we clear the userId attribute from a person object others may be allowed
again to edit it...
>>> adapted(john).userId = ''
>>> canWrite(john, 'title')
True
... but John no more...
>>> login(pJohn)
>>> canWrite(john, 'title')
False
Tasks and Events
================
Task view with edit action
--------------------------
>>> from loops.setup import addAndConfigureObject
>>> from loops.organize.interfaces import ITask
>>> task = addAndConfigureObject(concepts, Concept, 'task', title=u'Task',
... conceptType=type, typeInterface=ITask)
>>> from loops.organize.task import Task
>>> component.provideAdapter(Task)
>>> task01 = addAndConfigureObject(concepts, Concept, 'task01',
... title=u'Task #1', conceptType=task)
>>> from loops.organize.browser.task import TaskView
>>> view = TaskView(task01, TestRequest())
>>> view.getActions('portlet')
[<loops.browser.action.DialogAction ...>]
Events listing
--------------
>>> event = addAndConfigureObject(concepts, Concept, 'event', title=u'Event',
... conceptType=type, typeInterface=ITask)
>>> event01 = addAndConfigureObject(concepts, Concept, 'event01',
... title=u'Event #1', conceptType=event,
... )
>>> from loops.organize.browser.event import Events
>>> listing = Events(johnC, TestRequest())
>>> listing.getActions('portlet')
[<loops.browser.action.DialogAction ...>]
>>> list(listing.events())
[<loops.browser.concept.ConceptRelationView ...>]
Fin de partie
=============
>>> placefulTearDown()