appy.gen: improvements in user management.

This commit is contained in:
Gaetan Delannay 2012-02-21 12:09:42 +01:00
parent 9394490d33
commit 9c5f92337b
10 changed files with 85 additions and 23 deletions

View file

@ -335,8 +335,7 @@ class ZopeInstaller:
(translation.id, poName))
# Execute custom installation code if any
if hasattr(appyTool, 'install'):
tool.executeAppyAction('install', reindex=False)
if hasattr(appyTool, 'onInstall'): appyTool.onInstall()
def configureSessions(self):
'''Configure the session machinery.'''

View file

@ -102,6 +102,16 @@ class ToolMixin(BaseMixin):
for elem in path.split('/'): res = res._getOb(elem)
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):
'''Returns the supported languages. First one is the default.'''
return self.getProductConfig().languages
@ -111,6 +121,14 @@ class ToolMixin(BaseMixin):
p_code.'''
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):
'''Returns the list of messages to show to a web page and clean it in
the session.'''
@ -825,6 +843,11 @@ class ToolMixin(BaseMixin):
# --------------------------------------------------------------------------
# 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):
'''Logs the user in.'''
rq = self.REQUEST
@ -837,10 +860,7 @@ class ToolMixin(BaseMixin):
return self.goto(urlBack, msg)
# Perform the Zope-level authentication
login = rq.get('__ac_name', '')
password = rq.get('__ac_password', '')
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
cookieValue = urllib.quote(cookieValue)
rq.RESPONSE.setCookie('__ac', cookieValue, path='/')
self._updateCookie(login, rq.get('__ac_password', ''))
user = self.acl_users.validate(rq)
if self.userIsAnon():
rq.RESPONSE.expireCookie('__ac', path='/')

View file

@ -85,7 +85,10 @@ class ModelClass:
elif isinstance(value, gen.Page):
value = 'pages["%s"]' % value.name
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)
return '%s(%s)' % (appyType.__class__.__name__, typeArgs)
@ -144,10 +147,12 @@ class User(ModelClass):
password1 = gen.String(format=gen.String.PASSWORD, show=showPassword,
validator=validatePassword, **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)
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
indexed=True, **gm)
def showRoles(self): pass
roles = gen.String(show=showRoles, indexed=True,
validator=gen.Selection('getGrantableRoles'), **gm)
# The Group class --------------------------------------------------------------
class Group(ModelClass):
@ -163,7 +168,7 @@ class Group(ModelClass):
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
multiplicity=(0,None), **m)
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'))
# The Translation class --------------------------------------------------------

View file

@ -40,7 +40,7 @@ img {border: 0}
border-style: solid; border-width: 1px; border-color: grey; }
.top { height: 75px; margin-left: 3em; vertical-align: top;}
.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; }
.login { margin-top: 2px; margin-bottom: 2px; color: white;}
.buttons { margin-left: 4px;}

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

View file

@ -14,7 +14,7 @@
<head>
<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')"
rel="stylesheet" type="text/css" tal:attributes="href string:$appUrl/ui/$name"/>
<script tal:condition="python: name.endswith('.js')"
@ -31,12 +31,10 @@
<tr valign="top">
<td></td>
<tal:comment replace="nothing">Language selector (links or listbox)</tal:comment>
<td align="right"
tal:define="languages tool/getLanguages;
defaultLanguage python: languages[0];
suffix python: req.get('ACTUAL_URL').split('/')[-1];
asLinks python: len(languages) &lt;= 5"
tal:condition="python: len(languages) &gt;= 2 and (suffix not in ('edit', 'query', 'search'))">
<td align="right" tal:condition="tool/showLanguageSelector">
<tal:lgs define="languages tool/getLanguages;
defaultLanguage python: languages[0];
asLinks python: len(languages) &lt;= 5">
<table tal:condition="asLinks">
<tr>
<td tal:repeat="lang languages">
@ -54,6 +52,7 @@
tal:attributes="selected python:defaultLanguage == lang; value lang">
</option>
</select>
</tal:lgs>
</td>
</tr>
</table>

View file

@ -9,6 +9,10 @@ class GroupWrapper(AbstractWrapper):
if self.o.isTemporary(): return 'edit'
return 'view'
def showGroups(self):
'''Only the admin can view or edit roles.'''
return self.user.has_role('Manager')
def validateLogin(self, login):
'''Is this p_login valid?'''
return True

View file

@ -9,6 +9,16 @@ class UserWrapper(AbstractWrapper):
if self.o.isTemporary(): return 'edit'
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):
'''Is this p_login valid?'''
# The login can't be the id of the whole site or "admin"
@ -28,8 +38,11 @@ class UserWrapper(AbstractWrapper):
def showPassword(self):
'''When must we show the 2 fields for entering a password ?'''
# When someone creates the user
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):
'''Returns the list of roles that the admin can grant to a user.'''
@ -64,9 +77,16 @@ class UserWrapper(AbstractWrapper):
# granted to it.
zopeUser.groups = PersistentMapping()
else:
# Updates roles at the Zope level.
# Update roles at the Zope level.
zopeUser = aclUsers.getUserById(login)
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.
if 'Owner' not in self.o.get_local_roles_for_userid(login):
self.o.manage_addLocalRoles(login, ('Owner',))

View file

@ -108,7 +108,7 @@ class Debianizer:
def __init__(self, app, out, appVersion='0.1.0',
pythonVersions=('2.6',), zopePort=8080,
depends=('zope2.12', 'openoffice.org', 'imagemagick'),
sign=True):
sign=False):
# app is the path to the Python package to Debianize.
self.app = app
self.appName = os.path.basename(app)
@ -372,7 +372,15 @@ definitionJsonConf = '''{
class Cortexer:
'''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',)):
self.appName = os.path.basename(app)
self.pythonVersions = pythonVersions