member registration basically usable

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1221 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-05-23 11:59:10 +00:00
parent a70fe7d830
commit 00e27595ca
11 changed files with 353 additions and 57 deletions

View file

@ -44,7 +44,7 @@ from loops import util
class EditForm(BaseEditForm): class EditForm(BaseEditForm):
template = NamedTemplate('pageform') template = NamedTemplate('loops.pageform')
def deleteObjectAction(self): def deleteObjectAction(self):
return None # better not to show the edit button at the moment return None # better not to show the edit button at the moment

View file

@ -33,6 +33,7 @@ from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve from zope.dottedname.resolve import resolve
from zope.event import notify from zope.event import notify
from zope.formlib.form import EditForm, FormFields from zope.formlib.form import EditForm, FormFields
from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import implements from zope.interface import implements
from zope.publisher.interfaces import BadRequest from zope.publisher.interfaces import BadRequest
from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IBrowserRequest
@ -63,8 +64,11 @@ class ConceptEditForm(EditForm):
class ConceptView(BaseView): class ConceptView(BaseView):
template = ViewPageTemplateFile('concept_macros.pt') template = NamedTemplate('loops.concept_macros')
macro = template.macros['conceptlisting']
@Lazy
def macro(self):
return self.template.macros['conceptlisting']
def children(self): def children(self):
for r in self.context.getChildRelations(): for r in self.context.getChildRelations():

View file

@ -61,12 +61,27 @@
permission="zope.View" permission="zope.View"
/> />
<!-- named template(s) --> <!-- named templates - forms and macro templates -->
<zope:adapter <zope:adapter
factory="loops.browser.util.pageform" factory="loops.browser.util.pageform"
for="zope.interface.Interface" for="zope.interface.Interface"
name="pageform" /> name="loops.pageform" />
<zope:adapter
factory="loops.browser.util.dataform"
for="zope.interface.Interface"
name="loops.dataform" />
<zope:adapter
factory="loops.browser.util.concept_macros"
for="loops.browser.common.BaseView"
name="loops.concept_macros" />
<zope:adapter
factory="loops.browser.util.node_macros"
for="loops.browser.common.BaseView"
name="loops.node_macros" />
<!-- loops top-level container --> <!-- loops top-level container -->

169
browser/dataform.pt Normal file
View file

@ -0,0 +1,169 @@
<html metal:extend-macro="context/@@standard_macros/view"
metal:define-macro="main">
<head></head>
<body>
<metal:settings fill-slot="settings"
tal:define="dummy python:
controller.macros.register('css', resourceName='node.css', media='all');" />
<div class="box" metal:fill-slot="navigators">
<h4>Navigation</h4>
<tal:menu define="item view/menu;
level level|python: 1"
condition="item">
<div class="body">
<metal:menu use-macro="views/node_macros/menu" />
</div>
</tal:menu>
</div>
<div metal:fill-slot="body">
<div metal:define-macro="form">
<form action="." metal:define-macro="master"
tal:attributes="action request/URL" method="post"
class="edit-form" enctype="multipart/form-data"
id="zc.page.browser_form">
<script type="text/javascript"><!--
function toggleFormFieldHelp(ob,state) {
// ob is the label element
var field = ob.form[ob.htmlFor];
if (field) {
var viz = state && 'hidden' || 'visible';
if (field.length == null) {
field.style.visibility = viz;
}
else {
for (var i = 0; i < field.length; ++i) {
var e = field.item(i);
e.style.visibility = viz;
}
}
var help = document.getElementById("field-help-for-" + field.name);
if (help) {
help.style.visibility = state && 'visible' || 'hidden';
}
}
}
//-->
</script>
<div id="viewspace" metal:define-slot="viewspace">
<h1 tal:content="view/label">Edit ...</h1>
<metal:block define-macro="header">
<div class="form-status"
tal:define="status view/status"
tal:condition="status">
<div class="summary"
i18n:translate=""
tal:content="view/status">
Form status summary
</div>
<ul class="errors" tal:condition="view/errors">
<li tal:repeat="error view/error_views">
<span tal:replace="structure error">Error Type</span>
</li>
</ul>
</div>
<br />
</metal:block>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<table class="form-fields">
<tr class="row" metal:define-slot="extra_top" tal:replace="nothing">
<td class="label">Extra top</td>
<td class="label"><input type="text" /></td>
</tr>
<tbody metal:define-slot="formbody" tal:omit-tag="">
<tr tal:repeat="widget view/widgets">
<td class="label" tal:define="hint widget/hint"
metal:define-macro="labelcell">
<label tal:condition="python:hint"
tal:attributes="for widget/name"
onmousedown="toggleFormFieldHelp(this,1)"
onmouseup="toggleFormFieldHelp(this,0)"
onmouseout="toggleFormFieldHelp(this,0)"
style="cursor: help">
<span class="required" tal:condition="widget/required"
>*</span><span i18n:translate=""
tal:content="widget/label">label</span>
</label>
<label tal:condition="python:not hint"
tal:attributes="for widget/name">
<span class="required" tal:condition="widget/required"
>*</span><span i18n:translate=""
tal:content="widget/label">label</span>
</label>
</td>
<td class="field" tal:define="hint widget/hint"
metal:define-macro="widgetcell">
<div class="form-fields-help"
i18n:translate=""
tal:content="hint"
tal:condition="hint"
tal:attributes="id string:field-help-for-${widget/name}"
onclick="this.style.visibility='hidden';"
style="visibility: hidden; position: absolute;"
>Title of this content object.</div>
<div class="widget" tal:content="structure widget">
<input type="text" /></div>
<div class="error"
tal:condition="widget/error"
>
<!-- TODO Put this back, the Zope3 way.
<img src="alert.gif" alt="Error"
tal:replace="structure context/alert.gif" />
-->
<span tal:replace="structure widget/error">error</span>
</div>
</td>
</tr>
</tbody>
<tr class="row" metal:define-slot="extra_bottom" tal:replace="nothing">
<td class="label">Extra bottom</td>
<td class="label"><input type="text" /></td>
</tr>
</table>
<metal:block define-slot="above_buttons" />
</div>
<div id="actionsView">
<br />
<span class="actionButtons"
tal:condition="view/availableActions"
metal:define-slot="bottom_buttons">
<input tal:repeat="action view/actions"
tal:replace="structure action/render"
/>
</span>
</div>
</form>
<form metal:use-macro="views/resource_macros/delete_object" />
<script type="text/javascript" metal:define-slot="trackChanges">
zc_trackChanges(document.getElementById('zc.page.browser_form'));
</script>
<script type="text/javascript"
tal:define="extra_script view/extra_script | nothing"
tal:condition="extra_script"
tal:content="structure extra_script" />
</div></div></body></html>

View file

@ -34,6 +34,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.intid.interfaces import IIntIds from zope.app.intid.interfaces import IIntIds
from zope.dottedname.resolve import resolve from zope.dottedname.resolve import resolve
from zope.event import notify from zope.event import notify
from zope.formlib.namedtemplate import NamedTemplate
from zope.proxy import removeAllProxies from zope.proxy import removeAllProxies
from zope.security import canAccess, canWrite from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
@ -50,7 +51,7 @@ from loops.browser.concept import ConceptView
class NodeView(BaseView): class NodeView(BaseView):
template = ViewPageTemplateFile('node_macros.pt') template = NamedTemplate('loops.node_macros')
@Lazy @Lazy
def macro(self): def macro(self):

View file

@ -30,6 +30,10 @@ from zope.formlib.namedtemplate import NamedTemplateImplementation
pageform = NamedTemplateImplementation(ViewPageTemplateFile('pageform.pt')) 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): class LoopsMenu(BrowserMenu):

View file

@ -208,19 +208,19 @@ sure that a principal object can be served by a corresponding factory):
>>> from zope.app.authentication.principalfolder import FoundPrincipalFactory >>> from zope.app.authentication.principalfolder import FoundPrincipalFactory
>>> component.provideAdapter(FoundPrincipalFactory) >>> component.provideAdapter(FoundPrincipalFactory)
>>> form = {'field.userId': u'newuser', >>> data = {'loginName': u'newuser',
... 'field.passwd': u'quack', ... 'password': u'quack',
... 'field.passwdConfirm': u'quack', ... 'passwordConfirm': u'quack',
... 'field.lastName': u'Sawyer', ... 'lastName': u'Sawyer',
... 'field.firstName': u'Tom'} ... 'firstName': u'Tom'}
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest(form=form)
and register it and register it
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> from loops.organize.browser import MemberRegistration >>> from loops.organize.browser import MemberRegistration
>>> regView = MemberRegistration(menu, request) >>> regView = MemberRegistration(menu, request)
>>> personAdapter = regView.register() >>> personAdapter = regView.register(data)
>>> personAdapter.context.__name__, personAdapter.lastName, personAdapter.userId >>> personAdapter.context.__name__, personAdapter.lastName, personAdapter.userId
(u'newuser', u'Sawyer', u'loops.newuser') (u'newuser', u'Sawyer', u'loops.newuser')

View file

@ -25,23 +25,31 @@ $Id$
from zope import interface, component from zope import interface, component
from zope.app import zapi 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.pagetemplate import ViewPageTemplateFile
from zope.app.principalannotation import annotations from zope.app.principalannotation import annotations
from zope.cachedescriptors.property import Lazy 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 zope.i18nmessageid import MessageFactory
from loops.browser.common import BaseView from loops.browser.common import BaseView
from loops.browser.node import NodeView
from loops.browser.concept import ConceptRelationView from loops.browser.concept import ConceptRelationView
from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager
from loops.organize.interfaces import raiseValidationError from loops.organize.interfaces import IMemberRegistration, raiseValidationError
_ = MessageFactory('zope') _ = MessageFactory('zope')
class MyConcepts(BaseView): class MyConcepts(BaseView):
template = ViewPageTemplateFile('../browser/concept_macros.pt') template = NamedTemplate('loops.concept_macros')
macro = template.macros['conceptlisting']
@Lazy
def macro(self):
return self.template.macros['conceptlisting']
def __init__(self, context, request): def __init__(self, context, request):
self.context = context self.context = context
@ -70,21 +78,42 @@ class MyConcepts(BaseView):
yield ConceptRelationView(r, self.request, contextIsSecond=True) 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): def __init__(self, context, request):
self.context = context NodeView.__init__(self, context, request)
self.request = request
def register(self): @action(_(u'Register'))
form = self.request.form def handle_register_action(self, action, data):
pw = form.get('field.passwd') self.register(data)
if form.get('field.passwdConfirm') != pw:
raiseValidationError(_(u'Password and password confirmation ' def register(self, data=None):
'do not match.')) 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()) regMan = IMemberRegistrationManager(self.context.getLoopsRoot())
person = regMan.register(form.get('field.userId'), pw, person = regMan.register(form.get('loginName'), pw,
form.get('field.lastName'), form.get('lastName'),
form.get('field.firstName')) 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 return person

View file

@ -6,7 +6,7 @@
i18n_domain="zope" i18n_domain="zope"
> >
<!-- adapters --> <!-- party: person, ... -->
<zope:adapter factory="loops.organize.party.Person" <zope:adapter factory="loops.organize.party.Person"
provides="loops.organize.interfaces.IPerson" provides="loops.organize.interfaces.IPerson"
@ -19,6 +19,14 @@
set_schema="loops.organize.interfaces.IPerson" /> set_schema="loops.organize.interfaces.IPerson" />
</zope:class> </zope:class>
<zope:subscriber
for="loops.interfaces.IConcept
zope.app.container.interfaces.IObjectRemovedEvent"
handler="loops.organize.party.removePersonReferenceFromPrincipal"
/>
<!-- my stuff and other views -->
<zope:adapter <zope:adapter
name="myconcepts" name="myconcepts"
for="loops.interfaces.IConcept for="loops.interfaces.IConcept
@ -28,10 +36,27 @@
permission="zope.View" permission="zope.View"
/> />
<zope:subscriber <!-- member registration -->
for="loops.interfaces.IConcept
zope.app.container.interfaces.IObjectRemovedEvent" <zope:adapter factory="loops.organize.member.MemberRegistrationManager"
handler="loops.organize.party.removePersonReferenceFromPrincipal" trusted="True" />
<zope:class class="loops.organize.member.MemberRegistrationManager">
<require permission ="zope.Public"
interface="loops.organize.interfaces.IMemberRegistrationManager" />
</zope:class>
<browser:page name="registration.html"
for="loops.interfaces.IView"
class="loops.organize.browser.MemberRegistration"
permission="zope.Public" />
<zope:view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="loops.organize.interfaces.Password"
provides="zope.app.form.interfaces.IInputWidget"
factory="loops.organize.browser.PasswordWidget"
permission="zope.Public"
/> />
</configure> </configure>

View file

@ -23,7 +23,7 @@ $Id$
""" """
from zope.interface import Interface, Attribute 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 import zapi
from zope.app.principalannotation import annotations from zope.app.principalannotation import annotations
from zope.app.security.interfaces import IAuthentication, PrincipalLookupError from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
@ -65,22 +65,68 @@ class UserId(schema.TextLine):
userId=userId))) 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): class IPerson(IBasePerson):
""" Resembles a human being with a name (first and last name), """ Resembles a human being with a name (first and last name),
a birth date, and a set of addresses. This interface only 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. basic cybertools.organize package.
""" """
userId = UserId( userId = UserId(title=_(u'User ID'),
title=_(u'User ID'), description=_(u'The principal id (including prinicipal '
description=_(u'The principal id of a user that should ' 'folder prefix) of a user that should '
'be associated with this person.'), 'be associated with this person.'),
required=False,) 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): 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 ' 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): def register(userId, password, lastName, firstName=u'', **kw):
""" Register a new member for this loops site. """ Register a new member for this loops site.
Return the person adapter for the concept created. 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. """ 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. current password.
""" """

View file

@ -23,13 +23,15 @@ $Id$
""" """
from zope.app import zapi from zope.app import zapi
from zope import interface, component from zope import interface, component, schema
from zope.component import adapts from zope.component import adapts
from zope.interface import implements from zope.interface import implements
from zope.app.authentication.interfaces import IPluggableAuthentication from zope.app.authentication.interfaces import IPluggableAuthentication
from zope.app.authentication.interfaces import IAuthenticatorPlugin from zope.app.authentication.interfaces import IAuthenticatorPlugin
from zope.app.authentication.principalfolder import InternalPrincipal from zope.app.authentication.principalfolder import InternalPrincipal
from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.security.interfaces import IAuthentication from zope.app.security.interfaces import IAuthentication
from zope.event import notify
from zope.i18nmessageid import MessageFactory from zope.i18nmessageid import MessageFactory
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
@ -37,7 +39,6 @@ from cybertools.typology.interfaces import IType
from loops.interfaces import ILoops from loops.interfaces import ILoops
from loops.concept import Concept from loops.concept import Concept
from loops.organize.interfaces import IMemberRegistrationManager from loops.organize.interfaces import IMemberRegistrationManager
from loops.organize.interfaces import raiseValidationError
_ = MessageFactory('zope') _ = MessageFactory('zope')
@ -56,24 +57,24 @@ class MemberRegistrationManager(object):
# step 1: create an internal principal in the loops principal folder: # step 1: create an internal principal in the loops principal folder:
pau = zapi.getUtility(IAuthentication, context=self.context) pau = zapi.getUtility(IAuthentication, context=self.context)
if not IPluggableAuthentication.providedBy(pau): if not IPluggableAuthentication.providedBy(pau):
raiseValidationError(_(u'There is no pluggable authentication ' raise ValueError(u'There is no pluggable authentication '
'utility available.')) 'utility available.')
if not self.authPluginId in pau.authenticatorPlugins: if not self.authPluginId in pau.authenticatorPlugins:
raiseValidationError(_(u'There is no loops authenticator ' raise ValueError(u'There is no loops authenticator '
'plugin available.')) 'plugin available.')
pFolder = component.queryUtility(IAuthenticatorPlugin, self.authPluginId, pFolder = component.queryUtility(IAuthenticatorPlugin, self.authPluginId,
context=pau) context=pau)
title = firstName and ' '.join((firstName, lastName)) or lastName title = firstName and ' '.join((firstName, lastName)) or lastName
# TODO: encrypt password: # TODO: care for password encryption:
principal = InternalPrincipal(userId, password, title) principal = InternalPrincipal(userId, password, title)
pFolder[userId] = principal pFolder[userId] = principal
# step 1: create a corresponding person concept: # step 2: create a corresponding person concept:
cm = self.context.getConceptManager() cm = self.context.getConceptManager()
id = userId id = userId
num = 0 num = 0
while id in cm: while id in cm:
num +=1 num +=1
id = userid + str(num) id = userId + str(num)
person = cm[id] = Concept(title) person = cm[id] = Concept(title)
# TODO: the name of the person type object must be kept flexible! # TODO: the name of the person type object must be kept flexible!
person.conceptType = cm['person'] person.conceptType = cm['person']
@ -81,6 +82,8 @@ class MemberRegistrationManager(object):
personAdapter.firstName = firstName personAdapter.firstName = firstName
personAdapter.lastName = lastName personAdapter.lastName = lastName
personAdapter.userId = '.'.join((self.authPluginId, userId)) personAdapter.userId = '.'.join((self.authPluginId, userId))
notify(ObjectCreatedEvent(person))
notify(ObjectModifiedEvent(person))
return personAdapter return personAdapter
def changePassword(self, oldPw, newPw): def changePassword(self, oldPw, newPw):