extend/improve form/processing for registering users, clean up options
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@3329 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
55b231e8f9
commit
6a45e33890
8 changed files with 78 additions and 46 deletions
|
@ -8,6 +8,8 @@ $Id$
|
||||||
|
|
||||||
New features
|
New features
|
||||||
|
|
||||||
|
- form for user registration (``register_user.html``), controlled by options:
|
||||||
|
``registration_principalfolder``, ``registration_groups``
|
||||||
- new action: ``create task``
|
- new action: ``create task``
|
||||||
- quick search field
|
- quick search field
|
||||||
- external file/media asset type: options for hiding or showing fields on editing
|
- external file/media asset type: options for hiding or showing fields on editing
|
||||||
|
|
|
@ -197,7 +197,7 @@ class ConceptView(BaseView):
|
||||||
if (cont is not None and not IUnauthenticatedPrincipal.providedBy(
|
if (cont is not None and not IUnauthenticatedPrincipal.providedBy(
|
||||||
self.request.principal)):
|
self.request.principal)):
|
||||||
cont.macros.register('portlet_right', 'parents', title=_(u'Parents'),
|
cont.macros.register('portlet_right', 'parents', title=_(u'Parents'),
|
||||||
subMacro=self.template.macros['parents'],
|
subMacro=concept_macros.macros['parents'],
|
||||||
priority=20, info=self)
|
priority=20, info=self)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
renderer field/displayRenderer"
|
renderer field/displayRenderer"
|
||||||
tal:condition="nocall:value">
|
tal:condition="nocall:value">
|
||||||
<td><b tal:content="field/title" i18n:translate="" />:</td>
|
<td><b tal:content="field/title" i18n:translate="" />:</td>
|
||||||
<td><span metal:use-macro="item/template/macros/?renderer" /></td>
|
<td><span metal:use-macro="view/concept_macros/?renderer" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tal:row>
|
</tal:row>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -407,7 +407,8 @@ Send Email to Members
|
||||||
>>> from loops.organize.browser.party import SendEmailForm
|
>>> from loops.organize.browser.party import SendEmailForm
|
||||||
>>> form = SendEmailForm(menu, TestRequest())
|
>>> form = SendEmailForm(menu, TestRequest())
|
||||||
>>> form.members
|
>>> form.members
|
||||||
[{'email': 'john@loopz.org', 'title': u'John'}]
|
[{'email': 'john@loopz.org', 'title': u'John'},
|
||||||
|
{'email': u'tommy@sawyer.com', 'title': u'Tom Sawyer'}]
|
||||||
>>> form.subject
|
>>> form.subject
|
||||||
u"loops Notification from '$site'"
|
u"loops Notification from '$site'"
|
||||||
>>> form.mailBody
|
>>> form.mailBody
|
||||||
|
|
|
@ -27,6 +27,7 @@ from zope.app.container.contained import Contained
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.interface import Interface, implements
|
from zope.interface import Interface, implements
|
||||||
from zope.app.authentication.interfaces import IAuthenticatorPlugin
|
from zope.app.authentication.interfaces import IAuthenticatorPlugin
|
||||||
|
from zope.app.authentication.principalfolder import IInternalPrincipal
|
||||||
from zope.app.authentication.principalfolder import PrincipalInfo
|
from zope.app.authentication.principalfolder import PrincipalInfo
|
||||||
from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
|
from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
|
||||||
from zope.app.security.interfaces import IAuthentication
|
from zope.app.security.interfaces import IAuthentication
|
||||||
|
|
|
@ -76,15 +76,20 @@ class PersonalInfo(ConceptView):
|
||||||
class MemberRegistration(NodeView, CreateForm):
|
class MemberRegistration(NodeView, CreateForm):
|
||||||
|
|
||||||
interface = IMemberRegistration
|
interface = IMemberRegistration
|
||||||
message = _(u'You have been registered.')
|
message = _(u'The user account has been created.')
|
||||||
|
|
||||||
formErrors = dict(
|
formErrors = dict(
|
||||||
confirm_nomatch=FormError(_(u'Password and password confirmation do not match.')),
|
confirm_nomatch=FormError(_(u'Password and password confirmation do not match.')),
|
||||||
|
duplicate_loginname=FormError(_('Login name already taken.')),
|
||||||
)
|
)
|
||||||
|
|
||||||
label = _(u'Member Registration')
|
label = _(u'Member Registration')
|
||||||
label_submit = _(u'Register')
|
label_submit = _(u'Register')
|
||||||
|
|
||||||
|
permissions_key = u'registration.permissions'
|
||||||
|
roles_key = u'registration.roles'
|
||||||
|
registration_adapter_key = u'registration.adapter'
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macro(self):
|
||||||
return schema_macros.macros['form']
|
return schema_macros.macros['form']
|
||||||
|
@ -123,11 +128,20 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
return True
|
return True
|
||||||
login = form.get('loginName')
|
login = form.get('loginName')
|
||||||
regMan = IMemberRegistrationManager(self.context.getLoopsRoot())
|
regMan = IMemberRegistrationManager(self.context.getLoopsRoot())
|
||||||
self.object = regMan.register(login, pw,
|
result = regMan.register(login, pw,
|
||||||
form.get('lastName'), form.get('firstName'))
|
form.get('lastName'), form.get('firstName'),
|
||||||
|
email=form.get('email'),
|
||||||
|
phoneNumbers=form.get('phoneNumbers'))
|
||||||
|
if isinstance(result, dict):
|
||||||
|
fi = formState.fieldInstances[result['fieldName']]
|
||||||
|
fi.setError(result['error'], self.formErrors)
|
||||||
|
formState.severity = max(formState.severity, fi.severity)
|
||||||
|
return True
|
||||||
|
self.object = result
|
||||||
msg = self.message
|
msg = self.message
|
||||||
self.request.response.redirect('%s/login.html?login=%s&message=%s'
|
#self.request.response.redirect('%s/login.html?login=%s&message=%s'
|
||||||
% (self.url, login, msg))
|
# % (self.url, login, msg))
|
||||||
|
self.request.response.redirect('%s?message=%s' % (self.url, msg))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,13 @@ $Id$
|
||||||
|
|
||||||
from zope import interface, component, schema
|
from zope import interface, component, schema
|
||||||
from zope.app.component import queryNextUtility
|
from zope.app.component import queryNextUtility
|
||||||
|
from zope.app.container.interfaces import INameChooser
|
||||||
|
from zope.cachedescriptors.property import Lazy
|
||||||
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 IInternalPrincipal
|
||||||
from zope.app.authentication.principalfolder import InternalPrincipal
|
from zope.app.authentication.principalfolder import InternalPrincipal
|
||||||
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
||||||
from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
|
from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
|
||||||
|
@ -35,10 +38,12 @@ 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
|
||||||
|
|
||||||
|
from cybertools.meta.interfaces import IOptions
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
from loops.concept import Concept
|
from loops.concept import Concept
|
||||||
from loops.interfaces import ILoops
|
from loops.interfaces import ILoops
|
||||||
|
from loops.organize.auth import IPersonBasedAuthenticator
|
||||||
from loops.organize.interfaces import IMemberRegistrationManager
|
from loops.organize.interfaces import IMemberRegistrationManager
|
||||||
from loops.organize.util import getPrincipalFolder, getGroupsFolder
|
from loops.organize.util import getPrincipalFolder, getGroupsFolder
|
||||||
from loops.organize.util import getInternalPrincipal
|
from loops.organize.util import getInternalPrincipal
|
||||||
|
@ -51,54 +56,60 @@ class MemberRegistrationManager(object):
|
||||||
implements(IMemberRegistrationManager)
|
implements(IMemberRegistrationManager)
|
||||||
adapts(ILoops)
|
adapts(ILoops)
|
||||||
|
|
||||||
|
person_typeName = 'person'
|
||||||
|
default_principalfolder = 'loops'
|
||||||
|
principalfolder_key = u'registration.principalfolder'
|
||||||
|
groups_key = u'registration.groups'
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
def register(self, userId, password, lastName, firstName=u'',
|
def register(self, userId, password, lastName, firstName=u'',
|
||||||
groups=[], useExisting=False, **kw):
|
groups=[], useExisting=False, **kw):
|
||||||
|
concepts = self.context.getConceptManager()
|
||||||
|
personType = adapted(concepts[self.person_typeName])
|
||||||
|
options = IOptions(personType)
|
||||||
|
pfName = options(self.principalfolder_key,
|
||||||
|
(self.default_principalfolder,))[0]
|
||||||
# step 1: create an internal principal in the loops principal folder:
|
# step 1: create an internal principal in the loops principal folder:
|
||||||
pFolder = getPrincipalFolder(self.context)
|
pFolder = getPrincipalFolder(self.context, pfName)
|
||||||
# if isinstance(pFolder, PersonBasedAuthenticator):
|
if IPersonBasedAuthenticator.providedBy(pFolder):
|
||||||
# pFolder.setPassword(userId, password)
|
pFolder.setPassword(userId, password)
|
||||||
# else:
|
|
||||||
title = firstName and ' '.join((firstName, lastName)) or lastName
|
|
||||||
principal = InternalPrincipal(userId, password, title)
|
|
||||||
if useExisting:
|
|
||||||
if userId not in pFolder:
|
|
||||||
pFolder[userId] = principal
|
|
||||||
else:
|
else:
|
||||||
pFolder[userId] = principal
|
title = firstName and ' '.join((firstName, lastName)) or lastName
|
||||||
|
principal = InternalPrincipal(userId, password, title)
|
||||||
|
if useExisting:
|
||||||
|
if userId not in pFolder:
|
||||||
|
pFolder[userId] = principal
|
||||||
|
else:
|
||||||
|
if userId in pFolder:
|
||||||
|
return dict(fieldName='loginName', error='duplicate_loginname')
|
||||||
|
else:
|
||||||
|
pFolder[userId] = principal
|
||||||
# step 2 (optional): assign to group(s)
|
# step 2 (optional): assign to group(s)
|
||||||
personType = self.context.getLoopsRoot().getConceptManager()['person']
|
groups = options(self.groups_key, ())
|
||||||
od = getOptionsDict(adapted(personType).options)
|
for groupInfo in groups:
|
||||||
groupInfo = od.get('group')
|
names = groupInfo.split(':')
|
||||||
if groupInfo:
|
if len(names) == 1:
|
||||||
gfName, groupNames = groupInfo.split(':')
|
gName, gfName = names[0], None
|
||||||
|
else:
|
||||||
|
gName, gfName = names
|
||||||
gFolder = getGroupsFolder(gfName)
|
gFolder = getGroupsFolder(gfName)
|
||||||
if not groups:
|
if gFolder is not None:
|
||||||
groups = groupNames.split(',')
|
group = gFolder.get(gName)
|
||||||
else:
|
|
||||||
gFolder = getGroupsFolder()
|
|
||||||
if gFolder is not None:
|
|
||||||
for g in groups:
|
|
||||||
group = gFolder.get(g)
|
|
||||||
if group is not None:
|
if group is not None:
|
||||||
members = list(group.principals)
|
members = list(group.principals)
|
||||||
members.append(pFolder.prefix + userId)
|
members.append(pFolder.prefix + userId)
|
||||||
group.principals = members
|
group.principals = members
|
||||||
# step 3: create a corresponding person concept:
|
# step 3: create a corresponding person concept:
|
||||||
cm = self.context.getConceptManager()
|
name = baseId = 'person.' + userId
|
||||||
id = baseId = 'person.' + userId
|
if useExisting and name in concepts:
|
||||||
# TODO: use NameChooser
|
person = concepts[name]
|
||||||
if useExisting and id in cm:
|
|
||||||
person = cm[id]
|
|
||||||
else:
|
else:
|
||||||
num = 0
|
person = Concept(title)
|
||||||
while id in cm:
|
name = INameChooser(concepts).chooseName(name, person)
|
||||||
num +=1
|
concepts[name] = person
|
||||||
id = baseId + str(num)
|
person.conceptType = personType.context
|
||||||
person = cm[id] = Concept(title)
|
|
||||||
person.conceptType = cm['person']
|
|
||||||
personAdapter = adapted(person)
|
personAdapter = adapted(person)
|
||||||
personAdapter.firstName = firstName
|
personAdapter.firstName = firstName
|
||||||
personAdapter.lastName = lastName
|
personAdapter.lastName = lastName
|
||||||
|
@ -110,7 +121,7 @@ class MemberRegistrationManager(object):
|
||||||
return personAdapter
|
return personAdapter
|
||||||
|
|
||||||
def changePassword(self, principal, oldPw, newPw):
|
def changePassword(self, principal, oldPw, newPw):
|
||||||
if not isinstance(principal, InternalPrincipal):
|
if not IInternalPrincipal.providedBy(principal):
|
||||||
principal = getInternalPrincipal(principal.id)
|
principal = getInternalPrincipal(principal.id)
|
||||||
if not principal.checkPassword(oldPw):
|
if not principal.checkPassword(oldPw):
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -59,8 +59,9 @@ def getGroupsFolder(context=None, name='gloops'):
|
||||||
return getPrincipalFolder(authPluginId=name, ignoreErrors=True)
|
return getPrincipalFolder(authPluginId=name, ignoreErrors=True)
|
||||||
|
|
||||||
|
|
||||||
def getInternalPrincipal(id, context=None):
|
def getInternalPrincipal(id, context=None, pau=None):
|
||||||
pau = component.getUtility(IAuthentication, context=context)
|
if pau is None:
|
||||||
|
pau = component.getUtility(IAuthentication, context=context)
|
||||||
if not IPluggableAuthentication.providedBy(pau):
|
if not IPluggableAuthentication.providedBy(pau):
|
||||||
raise ValueError(u'There is no pluggable authentication '
|
raise ValueError(u'There is no pluggable authentication '
|
||||||
'utility available.')
|
'utility available.')
|
||||||
|
@ -68,7 +69,8 @@ def getInternalPrincipal(id, context=None):
|
||||||
next = queryNextUtility(pau, IAuthentication)
|
next = queryNextUtility(pau, IAuthentication)
|
||||||
if next is None:
|
if next is None:
|
||||||
raise PrincipalLookupError(id)
|
raise PrincipalLookupError(id)
|
||||||
return next.getPrincipal(id)
|
#return next.getPrincipal(id)
|
||||||
|
return getInternalPrincipal(id, context, pau=next)
|
||||||
id = id[len(pau.prefix):]
|
id = id[len(pau.prefix):]
|
||||||
for name, authplugin in pau.getAuthenticatorPlugins():
|
for name, authplugin in pau.getAuthenticatorPlugins():
|
||||||
if not id.startswith(authplugin.prefix):
|
if not id.startswith(authplugin.prefix):
|
||||||
|
@ -79,7 +81,8 @@ def getInternalPrincipal(id, context=None):
|
||||||
return principal
|
return principal
|
||||||
next = queryNextUtility(pau, IAuthentication)
|
next = queryNextUtility(pau, IAuthentication)
|
||||||
if next is not None:
|
if next is not None:
|
||||||
return next.getPrincipal(pau.prefix + id)
|
#return next.getPrincipal(pau.prefix + id)
|
||||||
|
return getInternalPrincipal(id, context, pau=next)
|
||||||
raise PrincipalLookupError(id)
|
raise PrincipalLookupError(id)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue