[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