[Checkins] SVN: grokapps/LoginDemo/ implemented storage of e-mail as a annotation on the InternalPrincipal

Luciano Ramalho luciano at ramalho.org
Tue Jan 1 21:20:18 EST 2008


Log message for revision 82634:
  implemented storage of e-mail as a annotation on the InternalPrincipal
  

Changed:
  U   grokapps/LoginDemo/README.txt
  U   grokapps/LoginDemo/src/logindemo/app.py
  U   grokapps/LoginDemo/src/logindemo/app_templates/listing.pt
  A   grokapps/LoginDemo/src/logindemo/ftests/member.txt
  U   grokapps/LoginDemo/src/logindemo/ftests/test_functional.py
  U   grokapps/LoginDemo/src/logindemo/interfaces.py

-=-
Modified: grokapps/LoginDemo/README.txt
===================================================================
--- grokapps/LoginDemo/README.txt	2008-01-02 00:21:48 UTC (rev 82633)
+++ grokapps/LoginDemo/README.txt	2008-01-02 02:20:17 UTC (rev 82634)
@@ -14,15 +14,15 @@
 
 - member listing page is protected by permission, requiring user log in
 
+- member e-mail is stored as an annotation
+
 - good functional test coverage (see text files in src/logindemo/ftests)
 
 Todo
 ----
 
-- allow user to change password
+- allow user to edit his data (password, real name, e-mail)
 
-- store user e-mail in principal annotation (currently it's discarded)
-
 - password reset for users with a valid e-mail
 
 - allow manager to delete user accounts through the member listing

Modified: grokapps/LoginDemo/src/logindemo/app.py
===================================================================
--- grokapps/LoginDemo/src/logindemo/app.py	2008-01-02 00:21:48 UTC (rev 82633)
+++ grokapps/LoginDemo/src/logindemo/app.py	2008-01-02 02:20:17 UTC (rev 82634)
@@ -2,7 +2,8 @@
 
 from urllib import urlencode
 
-from zope.interface import Interface
+from zope.interface import Interface, implements, classImplements
+from zope.component import getUtility, provideAdapter
 from zope.app.authentication import PluggableAuthentication
 from zope.app.authentication.principalfolder import PrincipalFolder
 from zope.app.authentication.principalfolder import InternalPrincipal
@@ -10,10 +11,10 @@
 from zope.app.security.interfaces import IAuthentication
 from zope.app.security.interfaces import IUnauthenticatedPrincipal
 from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager
-from zope.component import getUtility
+from zope.annotation.interfaces import IAttributeAnnotatable
 from zope.i18n import MessageFactory
 
-from logindemo.interfaces import IUser
+from interfaces import IUser, UserDataAdapter  
 
 _ = MessageFactory('logindemo')
 
@@ -30,7 +31,11 @@
     """
     grok.local_utility(PluggableAuthentication, IAuthentication,
                        setup=setup_pau)
-    
+    # make InternalPrincipal instances annotatable
+    classImplements(InternalPrincipal,IAttributeAnnotatable)
+    # register the adapter for IInternalPrincipal which provides IUser
+    provideAdapter(UserDataAdapter)
+           
 class ViewMemberListing(grok.Permission):
     grok.name('logindemo.ViewMemberListing')
 
@@ -100,16 +105,19 @@
             msg = _(u'Duplicate login. Please choose a different one.')
             self.redirect(self.url()+'?'+urlencode({'error_msg':msg}))
         else:
+            principal = InternalPrincipal(login, data['password'], data['name'])
             # add principal to principal folder
-            principals[login] = InternalPrincipal(login, data['password'],
-                                                  data['name'])                
+            principals[login] = principal
+            # save the e-mail
+            user = IUser(principal)
+            user.email = data['email']
             # grant the user permission to view the member listing
             permission_mngr = IPrincipalPermissionManager(grok.getSite())
             permission_mngr.grantPermissionToPrincipal(
                'logindemo.ViewMemberListing', principals.prefix + login)
 
             self.redirect(self.url('login')+'?'+urlencode({'login':login}))
-
+                    
 class Account(grok.View):
     
     def render(self):
@@ -118,8 +126,18 @@
 class Listing(Master):
     grok.require('logindemo.ViewMemberListing')
 
+    def fieldNames(self):
+        return ['id'] + [f for f in IUser]
+
     def members(self):
         pau = getUtility(IAuthentication)
         principals = pau['principals']
-        return [{'id':id, 'title':principals[id].title}
-            for id in sorted(principals.keys())]       
+        roster = []
+        for id in sorted(principals.keys()):
+            user = IUser(principals[id])
+            fields = {}
+            for field in IUser:
+                fields[field] = getattr(user, field)
+            fields['id'] = id
+            roster.append(fields)
+        return roster

Modified: grokapps/LoginDemo/src/logindemo/app_templates/listing.pt
===================================================================
--- grokapps/LoginDemo/src/logindemo/app_templates/listing.pt	2008-01-02 00:21:48 UTC (rev 82633)
+++ grokapps/LoginDemo/src/logindemo/app_templates/listing.pt	2008-01-02 02:20:17 UTC (rev 82634)
@@ -4,10 +4,12 @@
     <h1>Member Listing</h1>
     
     <table>
-        <tr><th>Login</th><th>Real name</th></tr>
+        <tr><th tal:repeat="field view/fieldNames"
+                tal:content="field">Field name</th></tr>
         <tr tal:repeat="member view/members">
-            <td bgcolor="lightgray" tal:content="member/id"></td>
-            <td bgcolor="lightgray" tal:content="member/title"></td>
+            <td bgcolor="lightgray"
+                tal:repeat="field view/fieldNames"
+                tal:content="python:member[field]"></td>
         </tr>
     </table>
 </div>

Added: grokapps/LoginDemo/src/logindemo/ftests/member.txt
===================================================================
--- grokapps/LoginDemo/src/logindemo/ftests/member.txt	                        (rev 0)
+++ grokapps/LoginDemo/src/logindemo/ftests/member.txt	2008-01-02 02:20:17 UTC (rev 82634)
@@ -0,0 +1,37 @@
+=========================================
+Testing member data storage and retrieval
+=========================================
+
+In this demo we don't use the PrincipalAnnotationUtility because we use
+InternalPrincipals which are persisted in the ZODB, and therefore can use
+plain attribute annotations.
+
+Here are the attributes of a plain InternalPrincipal instance::
+
+    >>> from zope.app.authentication.principalfolder import InternalPrincipal
+    >>> ip = InternalPrincipal('ip-login','ip-passwd','ip-title',
+    ...                         passwordManagerName='SHA1')
+    >>> from zope.app.authentication.principalfolder import IInternalPrincipal
+    >>> for f in IInternalPrincipal:
+    ...     print f, getattr(ip,f)
+    description
+    title ip-title
+    setPassword <bound method ...>
+    passwordManagerName SHA1
+    password 5869cb11a7cb62e0e49aa081ea73f2e39e3df168
+    login ip-login
+    
+Now we turn it into an IUser, to set and get the an e-mail field::
+
+    >>> from logindemo.interfaces import UserDataAdapter
+    >>> from logindemo.interfaces import IUser
+    >>> from zope.component import provideAdapter
+    >>> provideAdapter(UserDataAdapter)
+    >>> user = IUser(ip)
+    >>> user.email = 'user at domain.com'
+    >>> for f in IUser:
+    ...     print f, getattr(user,f)
+    login ip-login
+    password 5869cb11a7cb62e0e49aa081ea73f2e39e3df168
+    name ip-title
+    email user at domain.com    

Modified: grokapps/LoginDemo/src/logindemo/ftests/test_functional.py
===================================================================
--- grokapps/LoginDemo/src/logindemo/ftests/test_functional.py	2008-01-02 00:21:48 UTC (rev 82633)
+++ grokapps/LoginDemo/src/logindemo/ftests/test_functional.py	2008-01-02 02:20:17 UTC (rev 82634)
@@ -12,7 +12,7 @@
 
 def test_suite():
     suite = unittest.TestSuite()
-    docfiles = ['index.txt', 'join.txt']
+    docfiles = ['index.txt', 'join.txt', 'member.txt']
 
     for docfile in docfiles:
         test = FunctionalDocFileSuite(

Modified: grokapps/LoginDemo/src/logindemo/interfaces.py
===================================================================
--- grokapps/LoginDemo/src/logindemo/interfaces.py	2008-01-02 00:21:48 UTC (rev 82633)
+++ grokapps/LoginDemo/src/logindemo/interfaces.py	2008-01-02 02:20:17 UTC (rev 82634)
@@ -1,28 +1,83 @@
 import re
 
-from zope.interface import Interface
+from zope.interface import Interface, implements
 from zope import schema
+from zope.component import adapts
+from zope.annotation.interfaces import IAnnotations
+from zope.app.authentication.principalfolder import IInternalPrincipal
+from persistent.dict import PersistentDict
 from zope.i18n import MessageFactory
 
 _ = MessageFactory('logindemo')
 
+USER_DATA_KEY = 'logindemo.iuser.data' 
+
 class NotAnEmailAddress(schema.ValidationError):
     __doc__ = _(u"Invalid email address")
 
 check_email = re.compile(r"[a-zA-Z0-9._%-]+@([a-zA-Z0-9-]+\.)*[a-zA-Z]{2,4}").match
 def validate_email(value):
-    if not check_email(value):
-        raise NotAnEmailAddress(value)
-    return True
+    if check_email(value):
+        return True
+    raise NotAnEmailAddress(value)
 
 class IUser(Interface):
     """Basic user data."""
-    login = schema.TextLine(title=u"Login",
+    login = schema.TextLine(title=_(u"Login"),
                             required=True)
-    password = schema.Password(title=u"Password",
+    password = schema.Password(title=_(u"Password"),
                             required=True)
-    name = schema.TextLine(title=u"Real name",
+    name = schema.TextLine(title=_(u"Full name"),
                             required=False)
-    email = schema.ASCIILine(title=u"E-mail",
+    email = schema.ASCIILine(title=_(u"E-mail"),
                             required=False,
                             constraint=validate_email)
+
+class UserDataAdapter(object): 
+    """
+    Principal Information Adapter
+    
+    Fields which are common to both IUser and IInternalPrincipal are stored
+    in the context, an InternalPrincipal instance.
+    
+    The IUser.name field is stored in the title attr of the InternalPrincipal.
+    
+    Remaining fields of IUser (such as email) are stored as annotations on the
+    InternalPrincipal instance.
+    """ 
+    implements(IUser)
+    adapts(IInternalPrincipal)
+
+    def __init__(self , context):
+        annotations = IAnnotations(context)
+        self.context = context
+        self.data = annotations.get(USER_DATA_KEY)
+        if  self.data is None:
+            self.data = PersistentDict()
+            for field in IUser:
+                if field not in IInternalPrincipal:
+                    self.data[field] = u''
+            annotations[USER_DATA_KEY] = self.data
+        
+    def __getattr__(self, name):
+        if name in IUser:
+            if name == 'name':
+                return self.context.title
+            elif name in IInternalPrincipal:
+                return getattr(self.context, name)
+            return self.data.get(name)
+        else:
+            raise AttributeError, '%s not in IUser'
+        
+    def __setattr__(self, name, value):
+        if name in ['context','data']:
+            super(UserDataAdapter,self).__setattr__(name , value)
+        elif name in IUser:
+            if name in IInternalPrincipal:
+                setattr(self.context, name, value)
+            elif name == 'name':
+                setattr(self.context, 'title', value)
+            else:
+                self.data[name] = value
+        else:
+            raise AttributeError, '%s not in IUser'



More information about the Checkins mailing list