diff --git a/organize/README.txt b/organize/README.txt new file mode 100644 index 0000000..a0872c6 --- /dev/null +++ b/organize/README.txt @@ -0,0 +1,129 @@ +=============================================================== +loops - Linked Objects for Organization and Processing Services +=============================================================== + + ($Id$) + +Note: This packages depends on cybertools.organize. + +Letz's do some basic set up + + >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown + >>> site = placefulSetUp(True) + + >>> from zope import component, interface + >>> from zope.app import zapi + +and setup a simple loops site with a concept manager and some concepts: + + >>> from loops import Loops + >>> site['loops'] = Loops() + >>> loopsRoot = site['loops'] + + >>> from cybertools.relation.interfaces import IRelationRegistry + >>> from cybertools.relation.registry import DummyRelationRegistry + >>> relations = DummyRelationRegistry() + >>> component.provideUtility(relations, IRelationRegistry) + + >>> from loops.concept import ConceptManager, Concept + >>> loopsRoot['concepts'] = ConceptManager() + >>> concepts = loopsRoot['concepts'] + + >>> concepts['hasType'] = Concept(u'has type') + >>> concepts['type'] = Concept(u'Type') + >>> type = concepts['type'] + >>> type.conceptType = type + + >>> concepts['person'] = Concept(u'Person') + >>> person = concepts['person'] + >>> person.conceptType = type + + >>> johnC = Concept(u'John') + >>> concepts['john'] = johnC + >>> johnC.conceptType = person + + +Organizations: Persons (and Users), Institutions, Addresses... +============================================================== + +The classes used in this package are just adapters to IConcept. + + >>> from loops.interfaces import IConcept + >>> from loops.organize.interfaces import IPerson + >>> from loops.organize.party import Person + >>> component.provideAdapter(Person, (IConcept,), 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): + + >>> from zope.app.security.interfaces import IAuthentication + >>> from zope.app.security.principalregistry import PrincipalRegistry + >>> auth = PrincipalRegistry() + >>> component.provideUtility(auth, IAuthentication) + + >>> from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility + >>> from zope.app.principalannotation import PrincipalAnnotationUtility + >>> principalAnnotations = PrincipalAnnotationUtility() + >>> component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility) + + >>> principal = auth.definePrincipal('users.john', u'John', login='john') + + >>> john.userId = 'users.john' + + >>> annotations = principalAnnotations.getAnnotationsById('users.john') + >>> annotations.get('loops.organize.person') == relations.getUniqueIdForObject(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.get('loops.organize.person') == relations.getUniqueIdForObject(johnC) + True + >>> annotations = principalAnnotations.getAnnotationsById('users.john') + >>> annotations.get('loops.organize.person') is None + True + + >>> john.userId = None + >>> annotations = principalAnnotations.getAnnotationsById('users.johnny') + >>> annotations.get('loops.organize.person') is None + True + +Deleting a person with a userId assigned removes the corresponding +principal annotation: + + +Fin de partie +============= + + >>> placefulTearDown() + diff --git a/organize/configure.zcml b/organize/configure.zcml index 34f2d7d..e975e8e 100644 --- a/organize/configure.zcml +++ b/organize/configure.zcml @@ -9,13 +9,14 @@ + interface="loops.organize.interfaces.IPerson" /> + set_schema="loops.organize.interfaces.IPerson" /> diff --git a/organize/interfaces.py b/organize/interfaces.py new file mode 100644 index 0000000..184ede5 --- /dev/null +++ b/organize/interfaces.py @@ -0,0 +1,44 @@ +# +# Copyright (c) 2006 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 +# + +""" +Interfaces for organizational stuff like persons and addresses. + +$Id$ +""" + +from zope.interface import Interface, Attribute +from zope import schema +from zope.i18nmessageid import MessageFactory +from cybertools.organize.interfaces import IPerson as IBasePerson + +_ = MessageFactory('zope') + + +class IPerson(IBasePerson): + """ Resembles a human being with a name (first and last name), + a birth date, and a set of addresses. This interface only + lists fields used in addidtion to those provided by the + basic cybertools.organize package. + """ + + userId = schema.TextLine( + title=_(u'User ID'), + description=_(u'The principal id of a user that should ' + 'be associated with this person.'), + required=False,) diff --git a/organize/party.py b/organize/party.py index 348b557..a50c124 100644 --- a/organize/party.py +++ b/organize/party.py @@ -22,48 +22,91 @@ Adapters for IConcept providing interfaces from the cybertools.organize package. $Id$ """ +from zope import interface, component from zope.app import zapi +from zope.app.principalannotation import annotations +from zope.app.security.interfaces import IAuthentication, PrincipalLookupError from zope.component import adapts from zope.interface import implements from zope.cachedescriptors.property import Lazy from zope.security.proxy import removeSecurityProxy -from cybertools.organize.interfaces import IPerson +#from cybertools.organize.interfaces import IPerson +from cybertools.organize.party import Person as BasePerson +from cybertools.relation.interfaces import IRelationRegistry from loops.interfaces import IConcept from loops.type import TypeInterfaceSourceList +from loops.organize.interfaces import IPerson +# register IPerson as a type interface - (TODO: use a function for this) + TypeInterfaceSourceList.typeInterfaces += (IPerson,) -class Person(object): +class Person(BasePerson): """ typeInterface adapter for concepts of type 'person'. """ implements(IPerson) adapts(IConcept) + __attributes = ('context', '__parent__', 'userId',) + def __init__(self, context): - self.context = context - self.__parent__ = context # to get the permission stuff right + self.context = context # to get the permission stuff right + self.__parent__ = context - def getFirstName(self): - return getattr(self.context, '_firstName', u'') - def setFirstName(self, firstName): - self.context._firstName = firstName - firstName = property(getFirstName, setFirstName) + def __getattr__(self, attr): + self.checkAttr(attr) + return getattr(self.context, '_' + attr, None) - def getLastName(self): - return getattr(self.context, '_lastName', u'') - def setLastName(self, lastName): - self.context._lastName = lastName - lastName = property(getLastName, setLastName) - - def getBirthDate(self): - return getattr(self.context, '_birthDate', u'') - def setBirthDate(self, birthDate): - self.context._birthDate = birthDate - birthDate = property(getBirthDate, setBirthDate) + def __setattr__(self, attr, value): + if attr in self.__attributes: + object.__setattr__(self, attr, value) + else: + self.checkAttr(attr) + setattr(self.context, '_' + attr, value) + def checkAttr(self, attr): + if attr not in list(IPerson) + list(IConcept): + raise AttributeError(attr) + + def getUserId(self): + return getattr(self.context, '_userId', None) + def setUserId(self, userId): + auth = self.authentication + oldUserId = self.userId + if oldUserId and oldUserId != userId: + principal = self.getPrincipalForUserId(oldUserId) + if principal is not None: + pa = annotations(principal) + pa['loops.organize.person'] = None + if userId: + principal = auth.getPrincipal(userId) + pa = annotations(principal) + relations = component.getUtility(IRelationRegistry) + uid = relations.getUniqueIdForObject(self.context) + pa['loops.organize.person'] = uid + self.context._userId = userId + userId = property(getUserId, setUserId) + + @Lazy + def authentication(self): + return component.getUtility(IAuthentication, context=self.context) + + @Lazy + def principal(self): + return self.getPrincipalForUserId() + + def getPrincipalForUserId(self, userId=None): + userId = userId or self.userId + if not userId: + return None + auth = self.authentication + try: + return auth.getPrincipal(userId) + except PrincipalLookupError: + return None diff --git a/organize/tests.py b/organize/tests.py index 63413f5..8a4e5b5 100755 --- a/organize/tests.py +++ b/organize/tests.py @@ -17,8 +17,7 @@ def test_suite(): flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS return unittest.TestSuite(( unittest.makeSuite(Test), - #DocFileSuite('../README.txt', optionflags=flags), - #DocFileSuite('../helpers.txt', optionflags=flags), + DocFileSuite('README.txt', optionflags=flags), )) if __name__ == '__main__':