diff --git a/browser/common.py b/browser/common.py
index 9b7c3c2..a324b29 100644
--- a/browser/common.py
+++ b/browser/common.py
@@ -44,7 +44,7 @@ from loops import util
class EditForm(BaseEditForm):
- template = NamedTemplate('pageform')
+ template = NamedTemplate('loops.pageform')
def deleteObjectAction(self):
return None # better not to show the edit button at the moment
diff --git a/browser/concept.py b/browser/concept.py
index b51ee4a..65b530c 100644
--- a/browser/concept.py
+++ b/browser/concept.py
@@ -33,6 +33,7 @@ from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve
from zope.event import notify
from zope.formlib.form import EditForm, FormFields
+from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import implements
from zope.publisher.interfaces import BadRequest
from zope.publisher.interfaces.browser import IBrowserRequest
@@ -63,8 +64,11 @@ class ConceptEditForm(EditForm):
class ConceptView(BaseView):
- template = ViewPageTemplateFile('concept_macros.pt')
- macro = template.macros['conceptlisting']
+ template = NamedTemplate('loops.concept_macros')
+
+ @Lazy
+ def macro(self):
+ return self.template.macros['conceptlisting']
def children(self):
for r in self.context.getChildRelations():
diff --git a/browser/configure.zcml b/browser/configure.zcml
index 20f37e5..2b1cbbf 100644
--- a/browser/configure.zcml
+++ b/browser/configure.zcml
@@ -61,12 +61,27 @@
permission="zope.View"
/>
-
+
+ name="loops.pageform" />
+
+
+
+
+
+
diff --git a/browser/dataform.pt b/browser/dataform.pt
new file mode 100644
index 0000000..aeadd37
--- /dev/null
+++ b/browser/dataform.pt
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/node.py b/browser/node.py
index 73dd0f5..adf30b5 100644
--- a/browser/node.py
+++ b/browser/node.py
@@ -34,6 +34,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.intid.interfaces import IIntIds
from zope.dottedname.resolve import resolve
from zope.event import notify
+from zope.formlib.namedtemplate import NamedTemplate
from zope.proxy import removeAllProxies
from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy
@@ -50,7 +51,7 @@ from loops.browser.concept import ConceptView
class NodeView(BaseView):
- template = ViewPageTemplateFile('node_macros.pt')
+ template = NamedTemplate('loops.node_macros')
@Lazy
def macro(self):
diff --git a/browser/util.py b/browser/util.py
index bdad30d..ef3e873 100644
--- a/browser/util.py
+++ b/browser/util.py
@@ -30,6 +30,10 @@ from zope.formlib.namedtemplate import NamedTemplateImplementation
pageform = NamedTemplateImplementation(ViewPageTemplateFile('pageform.pt'))
+dataform = NamedTemplateImplementation(ViewPageTemplateFile('dataform.pt'))
+
+concept_macros = NamedTemplateImplementation(ViewPageTemplateFile('concept_macros.pt'))
+node_macros = NamedTemplateImplementation(ViewPageTemplateFile('node_macros.pt'))
class LoopsMenu(BrowserMenu):
diff --git a/organize/README.txt b/organize/README.txt
index 4a485b4..86c1c19 100644
--- a/organize/README.txt
+++ b/organize/README.txt
@@ -208,19 +208,19 @@ sure that a principal object can be served by a corresponding factory):
>>> from zope.app.authentication.principalfolder import FoundPrincipalFactory
>>> component.provideAdapter(FoundPrincipalFactory)
- >>> form = {'field.userId': u'newuser',
- ... 'field.passwd': u'quack',
- ... 'field.passwdConfirm': u'quack',
- ... 'field.lastName': u'Sawyer',
- ... 'field.firstName': u'Tom'}
- >>> from zope.publisher.browser import TestRequest
- >>> request = TestRequest(form=form)
+ >>> data = {'loginName': u'newuser',
+ ... 'password': u'quack',
+ ... 'passwordConfirm': u'quack',
+ ... 'lastName': u'Sawyer',
+ ... 'firstName': u'Tom'}
and register it
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
>>> from loops.organize.browser import MemberRegistration
>>> regView = MemberRegistration(menu, request)
- >>> personAdapter = regView.register()
+ >>> personAdapter = regView.register(data)
>>> personAdapter.context.__name__, personAdapter.lastName, personAdapter.userId
(u'newuser', u'Sawyer', u'loops.newuser')
diff --git a/organize/browser.py b/organize/browser.py
index 8d82d42..1a6d76e 100644
--- a/organize/browser.py
+++ b/organize/browser.py
@@ -25,23 +25,31 @@ $Id$
from zope import interface, component
from zope.app import zapi
+from zope.app.form.browser.textwidgets import PasswordWidget as BasePasswordWidget
+from zope.app.form.interfaces import WidgetInputError
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.principalannotation import annotations
from zope.cachedescriptors.property import Lazy
+from zope.formlib.form import Form, FormFields, action
+from zope.formlib.namedtemplate import NamedTemplate
from zope.i18nmessageid import MessageFactory
from loops.browser.common import BaseView
+from loops.browser.node import NodeView
from loops.browser.concept import ConceptRelationView
from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager
-from loops.organize.interfaces import raiseValidationError
+from loops.organize.interfaces import IMemberRegistration, raiseValidationError
_ = MessageFactory('zope')
class MyConcepts(BaseView):
- template = ViewPageTemplateFile('../browser/concept_macros.pt')
- macro = template.macros['conceptlisting']
+ template = NamedTemplate('loops.concept_macros')
+
+ @Lazy
+ def macro(self):
+ return self.template.macros['conceptlisting']
def __init__(self, context, request):
self.context = context
@@ -70,21 +78,42 @@ class MyConcepts(BaseView):
yield ConceptRelationView(r, self.request, contextIsSecond=True)
-class MemberRegistration(object):
+class PasswordWidget(BasePasswordWidget):
+
+ def getInputValue(self):
+ value = super(PasswordWidget, self).getInputValue()
+ confirm = self.request.get('form.passwordConfirm')
+ if confirm != value:
+ v = _(u'Password and password confirmation do not match.')
+ self._error = WidgetInputError(
+ self.context.__name__, self.label, v)
+ raise self._error
+ return value
+
+
+class MemberRegistration(Form, NodeView):
+
+ form_fields = FormFields(IMemberRegistration)
+ template = NamedTemplate('loops.dataform')
+ label = _(u'Member Registration')
def __init__(self, context, request):
- self.context = context
- self.request = request
+ NodeView.__init__(self, context, request)
- def register(self):
- form = self.request.form
- pw = form.get('field.passwd')
- if form.get('field.passwdConfirm') != pw:
- raiseValidationError(_(u'Password and password confirmation '
- 'do not match.'))
+ @action(_(u'Register'))
+ def handle_register_action(self, action, data):
+ self.register(data)
+
+ def register(self, data=None):
+ form = data or self.request.form
+ pw = form.get('password')
+ if form.get('passwordConfirm') != pw:
+ raise ValueError(u'Password and password confirmation do not match.')
regMan = IMemberRegistrationManager(self.context.getLoopsRoot())
- person = regMan.register(form.get('field.userId'), pw,
- form.get('field.lastName'),
- form.get('field.firstName'))
+ person = regMan.register(form.get('loginName'), pw,
+ form.get('lastName'),
+ form.get('firstName'))
+ message = _(u'You have been registered and can now login.')
+ self.request.response.redirect('%s?message=%s' % (self.url, message))
return person
diff --git a/organize/configure.zcml b/organize/configure.zcml
index 7882a04..1e60f2c 100644
--- a/organize/configure.zcml
+++ b/organize/configure.zcml
@@ -6,7 +6,7 @@
i18n_domain="zope"
>
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/organize/interfaces.py b/organize/interfaces.py
index fcfe63c..e663378 100644
--- a/organize/interfaces.py
+++ b/organize/interfaces.py
@@ -23,7 +23,7 @@ $Id$
"""
from zope.interface import Interface, Attribute
-from zope import component, schema
+from zope import interface, component, schema
from zope.app import zapi
from zope.app.principalannotation import annotations
from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
@@ -65,22 +65,68 @@ class UserId(schema.TextLine):
userId=userId)))
+class LoginName(schema.TextLine):
+
+ def _validate(self, userId):
+ super(LoginName, self)._validate(userId)
+
+
+class Password(schema.Password):
+
+ def _validate(self, pw):
+ super(Password, self)._validate(pw)
+
+
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
+ lists fields used in addition to those provided by the
basic cybertools.organize package.
"""
- userId = UserId(
- title=_(u'User ID'),
- description=_(u'The principal id of a user that should '
+ userId = UserId(title=_(u'User ID'),
+ description=_(u'The principal id (including prinicipal '
+ 'folder prefix) of a user that should '
'be associated with this person.'),
required=False,)
+class IPasswordEntry(Interface):
+
+ password = Password(title=_(u'Password'),
+ description=_(u'Enter password.'),
+ required=True,)
+ passwordConfirm = schema.Password(title=_(u'Confirm password'),
+ description=_(u'Please repeat the password.'),
+ required=True,)
+
+ #@interface.invariant
+ #def passwordMatchConfirm(data):
+ # if data.password != data.passwordConfirm:
+ # raise interface.Invalid(_(u'Password and password confirmation '
+ # 'do not match.'))
+
+
+class IPasswordChange(IPasswordEntry):
+
+ oldPassword = schema.Password(title=_(u'Old password'),
+ description=_(u'Enter old password.'),
+ required=True,)
+
+
+class IMemberRegistration(IBasePerson, IPasswordEntry):
+ """ Schema for registering a new member (user + person).
+ """
+
+ loginName = LoginName(
+ title=_(u'User ID'),
+ description=_(u'Enter a user id.'),
+ required=True,)
+
+
class IMemberRegistrationManager(Interface):
- """ Knows what to do for registrating a new member (portal user).
+ """ Knows what to do for registrating a new member (portal user),
+ change password, etc.
"""
authPluginId = Attribute(u'The id of an authentication plugin to be '
@@ -89,11 +135,11 @@ class IMemberRegistrationManager(Interface):
def register(userId, password, lastName, firstName=u'', **kw):
""" Register a new member for this loops site.
Return the person adapter for the concept created.
- Raise ValidationError if the user could not be created.
+ Raise Validation Error (?) if the user could not be created.
"""
- def changePassword(oldPw, newPw):
+ def changePassword(newPw):
""" Change the password of the user currently logged-in.
- Raise a ValidationError if the oldPw does not match the
+ Raise a Validation Error (?) if the oldPw does not match the
current password.
"""
diff --git a/organize/member.py b/organize/member.py
index 66ab744..5714a2f 100644
--- a/organize/member.py
+++ b/organize/member.py
@@ -23,13 +23,15 @@ $Id$
"""
from zope.app import zapi
-from zope import interface, component
+from zope import interface, component, schema
from zope.component import adapts
from zope.interface import implements
from zope.app.authentication.interfaces import IPluggableAuthentication
from zope.app.authentication.interfaces import IAuthenticatorPlugin
from zope.app.authentication.principalfolder import InternalPrincipal
+from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.security.interfaces import IAuthentication
+from zope.event import notify
from zope.i18nmessageid import MessageFactory
from zope.cachedescriptors.property import Lazy
@@ -37,7 +39,6 @@ from cybertools.typology.interfaces import IType
from loops.interfaces import ILoops
from loops.concept import Concept
from loops.organize.interfaces import IMemberRegistrationManager
-from loops.organize.interfaces import raiseValidationError
_ = MessageFactory('zope')
@@ -56,24 +57,24 @@ class MemberRegistrationManager(object):
# step 1: create an internal principal in the loops principal folder:
pau = zapi.getUtility(IAuthentication, context=self.context)
if not IPluggableAuthentication.providedBy(pau):
- raiseValidationError(_(u'There is no pluggable authentication '
- 'utility available.'))
+ raise ValueError(u'There is no pluggable authentication '
+ 'utility available.')
if not self.authPluginId in pau.authenticatorPlugins:
- raiseValidationError(_(u'There is no loops authenticator '
- 'plugin available.'))
+ raise ValueError(u'There is no loops authenticator '
+ 'plugin available.')
pFolder = component.queryUtility(IAuthenticatorPlugin, self.authPluginId,
context=pau)
title = firstName and ' '.join((firstName, lastName)) or lastName
- # TODO: encrypt password:
+ # TODO: care for password encryption:
principal = InternalPrincipal(userId, password, title)
pFolder[userId] = principal
- # step 1: create a corresponding person concept:
+ # step 2: create a corresponding person concept:
cm = self.context.getConceptManager()
id = userId
num = 0
while id in cm:
num +=1
- id = userid + str(num)
+ id = userId + str(num)
person = cm[id] = Concept(title)
# TODO: the name of the person type object must be kept flexible!
person.conceptType = cm['person']
@@ -81,6 +82,8 @@ class MemberRegistrationManager(object):
personAdapter.firstName = firstName
personAdapter.lastName = lastName
personAdapter.userId = '.'.join((self.authPluginId, userId))
+ notify(ObjectCreatedEvent(person))
+ notify(ObjectModifiedEvent(person))
return personAdapter
def changePassword(self, oldPw, newPw):