appy.gen: improvements in user management.
This commit is contained in:
parent
9394490d33
commit
9c5f92337b
|
@ -335,8 +335,7 @@ class ZopeInstaller:
|
||||||
(translation.id, poName))
|
(translation.id, poName))
|
||||||
|
|
||||||
# Execute custom installation code if any
|
# Execute custom installation code if any
|
||||||
if hasattr(appyTool, 'install'):
|
if hasattr(appyTool, 'onInstall'): appyTool.onInstall()
|
||||||
tool.executeAppyAction('install', reindex=False)
|
|
||||||
|
|
||||||
def configureSessions(self):
|
def configureSessions(self):
|
||||||
'''Configure the session machinery.'''
|
'''Configure the session machinery.'''
|
||||||
|
|
|
@ -102,6 +102,16 @@ class ToolMixin(BaseMixin):
|
||||||
for elem in path.split('/'): res = res._getOb(elem)
|
for elem in path.split('/'): res = res._getOb(elem)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def showLanguageSelector(self):
|
||||||
|
'''We must show the language selector if the app config requires it and
|
||||||
|
it there is more than 2 supported languages. Moreover, on some pages,
|
||||||
|
switching the language is not allowed.'''
|
||||||
|
cfg = self.getProductConfig()
|
||||||
|
if not cfg.languageSelector: return
|
||||||
|
if len(cfg.languages) < 2: return
|
||||||
|
page = self.REQUEST.get('ACTUAL_URL').split('/')[-1]
|
||||||
|
return page not in ('edit', 'query', 'search')
|
||||||
|
|
||||||
def getLanguages(self):
|
def getLanguages(self):
|
||||||
'''Returns the supported languages. First one is the default.'''
|
'''Returns the supported languages. First one is the default.'''
|
||||||
return self.getProductConfig().languages
|
return self.getProductConfig().languages
|
||||||
|
@ -111,6 +121,14 @@ class ToolMixin(BaseMixin):
|
||||||
p_code.'''
|
p_code.'''
|
||||||
return languages.get(code)[2]
|
return languages.get(code)[2]
|
||||||
|
|
||||||
|
def getCssJs(self):
|
||||||
|
'''Returns the list of CSS and JS files to include in the main template.
|
||||||
|
The method ensures that appy.css and appy.js come first.'''
|
||||||
|
names = self.getPhysicalRoot().ui.objectIds('File')
|
||||||
|
names.remove('appy.js'); names.insert(0, 'appy.js')
|
||||||
|
names.remove('appy.css'); names.insert(0, 'appy.css')
|
||||||
|
return names
|
||||||
|
|
||||||
def consumeMessages(self):
|
def consumeMessages(self):
|
||||||
'''Returns the list of messages to show to a web page and clean it in
|
'''Returns the list of messages to show to a web page and clean it in
|
||||||
the session.'''
|
the session.'''
|
||||||
|
@ -825,6 +843,11 @@ class ToolMixin(BaseMixin):
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Authentication-related methods
|
# Authentication-related methods
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
|
def _updateCookie(self, login, password):
|
||||||
|
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
|
||||||
|
cookieValue = urllib.quote(cookieValue)
|
||||||
|
self.REQUEST.RESPONSE.setCookie('__ac', cookieValue, path='/')
|
||||||
|
|
||||||
def performLogin(self):
|
def performLogin(self):
|
||||||
'''Logs the user in.'''
|
'''Logs the user in.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
|
@ -837,10 +860,7 @@ class ToolMixin(BaseMixin):
|
||||||
return self.goto(urlBack, msg)
|
return self.goto(urlBack, msg)
|
||||||
# Perform the Zope-level authentication
|
# Perform the Zope-level authentication
|
||||||
login = rq.get('__ac_name', '')
|
login = rq.get('__ac_name', '')
|
||||||
password = rq.get('__ac_password', '')
|
self._updateCookie(login, rq.get('__ac_password', ''))
|
||||||
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
|
|
||||||
cookieValue = urllib.quote(cookieValue)
|
|
||||||
rq.RESPONSE.setCookie('__ac', cookieValue, path='/')
|
|
||||||
user = self.acl_users.validate(rq)
|
user = self.acl_users.validate(rq)
|
||||||
if self.userIsAnon():
|
if self.userIsAnon():
|
||||||
rq.RESPONSE.expireCookie('__ac', path='/')
|
rq.RESPONSE.expireCookie('__ac', path='/')
|
||||||
|
|
15
gen/model.py
15
gen/model.py
|
@ -85,7 +85,10 @@ class ModelClass:
|
||||||
elif isinstance(value, gen.Page):
|
elif isinstance(value, gen.Page):
|
||||||
value = 'pages["%s"]' % value.name
|
value = 'pages["%s"]' % value.name
|
||||||
elif callable(value):
|
elif callable(value):
|
||||||
value = '%s.%s' % (wrapperName, value.__name__)
|
className = wrapperName
|
||||||
|
if (appyType.type == 'Ref') and appyType.isBack:
|
||||||
|
className = appyType.back.klass.__name__
|
||||||
|
value = '%s.%s' % (className, value.__name__)
|
||||||
typeArgs += '%s=%s,' % (name, value)
|
typeArgs += '%s=%s,' % (name, value)
|
||||||
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
|
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
|
||||||
|
|
||||||
|
@ -144,10 +147,12 @@ class User(ModelClass):
|
||||||
password1 = gen.String(format=gen.String.PASSWORD, show=showPassword,
|
password1 = gen.String(format=gen.String.PASSWORD, show=showPassword,
|
||||||
validator=validatePassword, **gm)
|
validator=validatePassword, **gm)
|
||||||
password2 = gen.String(format=gen.String.PASSWORD, show=showPassword, **gm)
|
password2 = gen.String(format=gen.String.PASSWORD, show=showPassword, **gm)
|
||||||
email = gen.String(group='main', width=25)
|
def showEmail(self): pass
|
||||||
|
email = gen.String(show=showEmail, group='main', width=25)
|
||||||
gm['multiplicity'] = (0, None)
|
gm['multiplicity'] = (0, None)
|
||||||
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
|
def showRoles(self): pass
|
||||||
indexed=True, **gm)
|
roles = gen.String(show=showRoles, indexed=True,
|
||||||
|
validator=gen.Selection('getGrantableRoles'), **gm)
|
||||||
|
|
||||||
# The Group class --------------------------------------------------------------
|
# The Group class --------------------------------------------------------------
|
||||||
class Group(ModelClass):
|
class Group(ModelClass):
|
||||||
|
@ -163,7 +168,7 @@ class Group(ModelClass):
|
||||||
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
|
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
|
||||||
multiplicity=(0,None), **m)
|
multiplicity=(0,None), **m)
|
||||||
users = gen.Ref(User, multiplicity=(0,None), add=False, link=True,
|
users = gen.Ref(User, multiplicity=(0,None), add=False, link=True,
|
||||||
back=gen.Ref(attribute='groups', show=True),
|
back=gen.Ref(attribute='groups', show=User.showRoles),
|
||||||
showHeaders=True, shownInfo=('title', 'login'))
|
showHeaders=True, shownInfo=('title', 'login'))
|
||||||
|
|
||||||
# The Translation class --------------------------------------------------------
|
# The Translation class --------------------------------------------------------
|
||||||
|
|
|
@ -40,7 +40,7 @@ img {border: 0}
|
||||||
border-style: solid; border-width: 1px; border-color: grey; }
|
border-style: solid; border-width: 1px; border-color: grey; }
|
||||||
.top { height: 75px; margin-left: 3em; vertical-align: top;}
|
.top { height: 75px; margin-left: 3em; vertical-align: top;}
|
||||||
.lang { margin-right: 3px; }
|
.lang { margin-right: 3px; }
|
||||||
.userStrip { background-color: #89A6B1; height: 30px;
|
.userStrip { background-color: #89A6B1; height: 40px;
|
||||||
border-top: 3px solid #405A64; border-bottom: 2px solid #5F7983; }
|
border-top: 3px solid #405A64; border-bottom: 2px solid #5F7983; }
|
||||||
.login { margin-top: 2px; margin-bottom: 2px; color: white;}
|
.login { margin-top: 2px; margin-bottom: 2px; color: white;}
|
||||||
.buttons { margin-left: 4px;}
|
.buttons { margin-left: 4px;}
|
||||||
|
|
BIN
gen/ui/banner.jpg
Normal file
BIN
gen/ui/banner.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
7
gen/ui/home.pt
Normal file
7
gen/ui/home.pt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<tal:main define="tool python: context.config">
|
||||||
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
|
<div metal:fill-slot="content">
|
||||||
|
<span tal:replace="structure python: tool.translate('front_page_text')"/>
|
||||||
|
</div>
|
||||||
|
</html>
|
||||||
|
</tal:main>
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title tal:content="tool/getAppName"></title>
|
<title tal:content="tool/getAppName"></title>
|
||||||
<tal:link repeat="name python: app.ui.objectIds('File')">
|
<tal:link repeat="name tool/getCssJs">
|
||||||
<link tal:condition="python: name.endswith('.css')"
|
<link tal:condition="python: name.endswith('.css')"
|
||||||
rel="stylesheet" type="text/css" tal:attributes="href string:$appUrl/ui/$name"/>
|
rel="stylesheet" type="text/css" tal:attributes="href string:$appUrl/ui/$name"/>
|
||||||
<script tal:condition="python: name.endswith('.js')"
|
<script tal:condition="python: name.endswith('.js')"
|
||||||
|
@ -31,12 +31,10 @@
|
||||||
<tr valign="top">
|
<tr valign="top">
|
||||||
<td></td>
|
<td></td>
|
||||||
<tal:comment replace="nothing">Language selector (links or listbox)</tal:comment>
|
<tal:comment replace="nothing">Language selector (links or listbox)</tal:comment>
|
||||||
<td align="right"
|
<td align="right" tal:condition="tool/showLanguageSelector">
|
||||||
tal:define="languages tool/getLanguages;
|
<tal:lgs define="languages tool/getLanguages;
|
||||||
defaultLanguage python: languages[0];
|
defaultLanguage python: languages[0];
|
||||||
suffix python: req.get('ACTUAL_URL').split('/')[-1];
|
asLinks python: len(languages) <= 5">
|
||||||
asLinks python: len(languages) <= 5"
|
|
||||||
tal:condition="python: len(languages) >= 2 and (suffix not in ('edit', 'query', 'search'))">
|
|
||||||
<table tal:condition="asLinks">
|
<table tal:condition="asLinks">
|
||||||
<tr>
|
<tr>
|
||||||
<td tal:repeat="lang languages">
|
<td tal:repeat="lang languages">
|
||||||
|
@ -54,6 +52,7 @@
|
||||||
tal:attributes="selected python:defaultLanguage == lang; value lang">
|
tal:attributes="selected python:defaultLanguage == lang; value lang">
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
</tal:lgs>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -9,6 +9,10 @@ class GroupWrapper(AbstractWrapper):
|
||||||
if self.o.isTemporary(): return 'edit'
|
if self.o.isTemporary(): return 'edit'
|
||||||
return 'view'
|
return 'view'
|
||||||
|
|
||||||
|
def showGroups(self):
|
||||||
|
'''Only the admin can view or edit roles.'''
|
||||||
|
return self.user.has_role('Manager')
|
||||||
|
|
||||||
def validateLogin(self, login):
|
def validateLogin(self, login):
|
||||||
'''Is this p_login valid?'''
|
'''Is this p_login valid?'''
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -9,6 +9,16 @@ class UserWrapper(AbstractWrapper):
|
||||||
if self.o.isTemporary(): return 'edit'
|
if self.o.isTemporary(): return 'edit'
|
||||||
return 'view'
|
return 'view'
|
||||||
|
|
||||||
|
def showEmail(self):
|
||||||
|
'''In most cases, email is the login. Show the field only if it is not
|
||||||
|
the case.'''
|
||||||
|
email = self.email
|
||||||
|
return email and (email != self.login)
|
||||||
|
|
||||||
|
def showRoles(self):
|
||||||
|
'''Only the admin can view or edit roles.'''
|
||||||
|
return self.user.has_role('Manager')
|
||||||
|
|
||||||
def validateLogin(self, login):
|
def validateLogin(self, login):
|
||||||
'''Is this p_login valid?'''
|
'''Is this p_login valid?'''
|
||||||
# The login can't be the id of the whole site or "admin"
|
# The login can't be the id of the whole site or "admin"
|
||||||
|
@ -28,8 +38,11 @@ class UserWrapper(AbstractWrapper):
|
||||||
|
|
||||||
def showPassword(self):
|
def showPassword(self):
|
||||||
'''When must we show the 2 fields for entering a password ?'''
|
'''When must we show the 2 fields for entering a password ?'''
|
||||||
|
# When someone creates the user
|
||||||
if self.o.isTemporary(): return 'edit'
|
if self.o.isTemporary(): return 'edit'
|
||||||
return False
|
# When the user itself (which is Owner of the object representing him)
|
||||||
|
# wants to edit information about himself.
|
||||||
|
if self.user.has_role('Owner', self): return 'edit'
|
||||||
|
|
||||||
def getGrantableRoles(self):
|
def getGrantableRoles(self):
|
||||||
'''Returns the list of roles that the admin can grant to a user.'''
|
'''Returns the list of roles that the admin can grant to a user.'''
|
||||||
|
@ -64,9 +77,16 @@ class UserWrapper(AbstractWrapper):
|
||||||
# granted to it.
|
# granted to it.
|
||||||
zopeUser.groups = PersistentMapping()
|
zopeUser.groups = PersistentMapping()
|
||||||
else:
|
else:
|
||||||
# Updates roles at the Zope level.
|
# Update roles at the Zope level.
|
||||||
zopeUser = aclUsers.getUserById(login)
|
zopeUser = aclUsers.getUserById(login)
|
||||||
zopeUser.roles = self.roles
|
zopeUser.roles = self.roles
|
||||||
|
# Update the password if the user has entered new ones.
|
||||||
|
rq = self.request
|
||||||
|
if rq.has_key('password1'):
|
||||||
|
zopeUser.__ = aclUsers._encryptPassword(rq['password1'])
|
||||||
|
# Update the cookie value
|
||||||
|
self.tool.o._updateCookie(login, rq['password1'])
|
||||||
|
self.password1 = self.password2 = ''
|
||||||
# "self" must be owned by its Zope user.
|
# "self" must be owned by its Zope user.
|
||||||
if 'Owner' not in self.o.get_local_roles_for_userid(login):
|
if 'Owner' not in self.o.get_local_roles_for_userid(login):
|
||||||
self.o.manage_addLocalRoles(login, ('Owner',))
|
self.o.manage_addLocalRoles(login, ('Owner',))
|
||||||
|
|
|
@ -108,7 +108,7 @@ class Debianizer:
|
||||||
def __init__(self, app, out, appVersion='0.1.0',
|
def __init__(self, app, out, appVersion='0.1.0',
|
||||||
pythonVersions=('2.6',), zopePort=8080,
|
pythonVersions=('2.6',), zopePort=8080,
|
||||||
depends=('zope2.12', 'openoffice.org', 'imagemagick'),
|
depends=('zope2.12', 'openoffice.org', 'imagemagick'),
|
||||||
sign=True):
|
sign=False):
|
||||||
# app is the path to the Python package to Debianize.
|
# app is the path to the Python package to Debianize.
|
||||||
self.app = app
|
self.app = app
|
||||||
self.appName = os.path.basename(app)
|
self.appName = os.path.basename(app)
|
||||||
|
@ -372,7 +372,15 @@ definitionJsonConf = '''{
|
||||||
|
|
||||||
class Cortexer:
|
class Cortexer:
|
||||||
'''This class allows to produce a Cortex application definition for
|
'''This class allows to produce a Cortex application definition for
|
||||||
a Debianized Python/Appy application.'''
|
a Debianized Python/Appy application.
|
||||||
|
|
||||||
|
Once the "cortex.admin" folder and its content has been generated, in
|
||||||
|
order to push the app definition into Cortex, go in the folder where
|
||||||
|
"cortex.admin" lies and type (command-line tool "cortex-client" must
|
||||||
|
be installed):
|
||||||
|
|
||||||
|
cortex-client sync push --api http://<cortex-host-ip>/api
|
||||||
|
'''
|
||||||
def __init__(self, app, pythonVersions=('2.6',)):
|
def __init__(self, app, pythonVersions=('2.6',)):
|
||||||
self.appName = os.path.basename(app)
|
self.appName = os.path.basename(app)
|
||||||
self.pythonVersions = pythonVersions
|
self.pythonVersions = pythonVersions
|
||||||
|
|
Loading…
Reference in a new issue