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__':