appy.gen: improvements in user management.
This commit is contained in:
parent
9394490d33
commit
9c5f92337b
|
@ -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.'''
|
||||
|
|
|
@ -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='/')
|
||||
|
|
15
gen/model.py
15
gen/model.py
|
@ -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 --------------------------------------------------------
|
||||
|
|
|
@ -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
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>
|
||||
<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;
|
||||
<td align="right" tal:condition="tool/showLanguageSelector">
|
||||
<tal:lgs define="languages tool/getLanguages;
|
||||
defaultLanguage python: languages[0];
|
||||
suffix python: req.get('ACTUAL_URL').split('/')[-1];
|
||||
asLinks python: len(languages) <= 5"
|
||||
tal:condition="python: len(languages) >= 2 and (suffix not in ('edit', 'query', 'search'))">
|
||||
asLinks python: len(languages) <= 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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',))
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue