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):
template = NamedTemplate('pageform')
template = NamedTemplate('loops.pageform')
def deleteObjectAction(self):
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.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():

View file

@ -61,12 +61,27 @@
permission="zope.View"
/>
<!-- named template(s) -->
<!-- named templates - forms and macro templates -->
<zope:adapter
factory="loops.browser.util.pageform"
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 -->

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.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):

View file

@ -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):

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
>>> 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')

View file

@ -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

View file

@ -6,7 +6,7 @@
i18n_domain="zope"
>
<!-- adapters -->
<!-- party: person, ... -->
<zope:adapter factory="loops.organize.party.Person"
provides="loops.organize.interfaces.IPerson"
@ -19,19 +19,44 @@
set_schema="loops.organize.interfaces.IPerson" />
</zope:class>
<zope:adapter
name="myconcepts"
for="loops.interfaces.IConcept
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.interface.Interface"
factory="loops.organize.browser.MyConcepts"
permission="zope.View"
/>
<zope:subscriber
for="loops.interfaces.IConcept
zope.app.container.interfaces.IObjectRemovedEvent"
handler="loops.organize.party.removePersonReferenceFromPrincipal"
/>
<!-- my stuff and other views -->
<zope:adapter
name="myconcepts"
for="loops.interfaces.IConcept
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.interface.Interface"
factory="loops.organize.browser.MyConcepts"
permission="zope.View"
/>
<!-- member registration -->
<zope:adapter factory="loops.organize.member.MemberRegistrationManager"
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>

View file

@ -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.
"""

View file

@ -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):