[gen] Bugfix while managing languages, ui improvements.

This commit is contained in:
Gaetan Delannay 2012-05-29 20:50:18 +02:00
parent aaaccb0669
commit ede29fb6c1
14 changed files with 140 additions and 96 deletions

View file

@ -261,8 +261,8 @@ class ZopeInstaller:
# may still be in the way for migration purposes. # may still be in the way for migration purposes.
users = ('admin',) # We suppose there is at least a user. users = ('admin',) # We suppose there is at least a user.
if not users: if not users:
appyTool.create('users', login='admin', firstName='admin', appyTool.create('users', login='admin', password1='admin',
name='admin', password1='admin', password2='admin', password2='admin',
email='admin@appyframework.org', roles=['Manager']) email='admin@appyframework.org', roles=['Manager'])
appyTool.log('Admin user "admin" created.') appyTool.log('Admin user "admin" created.')

View file

@ -121,6 +121,13 @@ class ToolMixin(BaseMixin):
p_code.''' p_code.'''
return languages.get(code)[2] return languages.get(code)[2]
def changeLanguage(self):
'''Sets the language cookie with the new desired language code that is
in request["language"].'''
rq = self.REQUEST
rq.RESPONSE.setCookie('_ZopeLg', rq['language'], path='/')
return self.goto(rq['HTTP_REFERER'])
def getGlobalCssJs(self): def getGlobalCssJs(self):
'''Returns the list of CSS and JS files to include in the main template. '''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.''' The method ensures that appy.css and appy.js come first.'''
@ -231,6 +238,12 @@ class ToolMixin(BaseMixin):
res = appyObj.showPortlet() res = appyObj.showPortlet()
except AttributeError: except AttributeError:
res = True res = True
else:
appyObj = self.appy()
try:
res = appyObj.showPortletAt(context)
except AttributeError:
res = True
return res return res
def getObject(self, uid, appy=False, brain=False): def getObject(self, uid, appy=False, brain=False):
@ -979,7 +992,7 @@ class ToolMixin(BaseMixin):
elem is the one-line user info as shown on every page; second line is elem is the one-line user info as shown on every page; second line is
the URL to edit user info.''' the URL to edit user info.'''
appyUser = self.appy().appyUser appyUser = self.appy().appyUser
res = [appyUser.title, appyUser.login] res = [appyUser.title]
rolesToShow = [r for r in appyUser.roles \ rolesToShow = [r for r in appyUser.roles \
if r not in ('Authenticated', 'Member')] if r not in ('Authenticated', 'Member')]
if rolesToShow: if rolesToShow:

View file

@ -1345,10 +1345,16 @@ class BaseMixin:
def getUserLanguage(self): def getUserLanguage(self):
'''Gets the language (code) of the current user.''' '''Gets the language (code) of the current user.'''
if not hasattr(self, 'REQUEST'): return 'en' if not hasattr(self, 'REQUEST'): return 'en'
# Try first the "LANGUAGE" key from the request # Try the value which comes from the cookie. Indeed, if such a cookie is
# present, it means that the user has explicitly chosen this language
# via the language selector.
rq = self.REQUEST
if '_ZopeLg' in rq.cookies: return rq.cookies['_ZopeLg']
# Try the LANGUAGE key from the request: it corresponds to the language
# as configured in the user's browser.
res = self.REQUEST.get('LANGUAGE', None) res = self.REQUEST.get('LANGUAGE', None)
if res: return res if res: return res
# Try then the HTTP_ACCEPT_LANGUAGE key from the request, which stores # Try the HTTP_ACCEPT_LANGUAGE key from the request, which stores
# language preferences as defined in the user's browser. Several # language preferences as defined in the user's browser. Several
# languages can be listed, from most to less wanted. # languages can be listed, from most to less wanted.
res = self.REQUEST.get('HTTP_ACCEPT_LANGUAGE', None) res = self.REQUEST.get('HTTP_ACCEPT_LANGUAGE', None)

View file

@ -93,7 +93,7 @@ class ModelClass:
elif callable(value): elif callable(value):
className = wrapperName className = wrapperName
if (appyType.type == 'Ref') and appyType.isBack: if (appyType.type == 'Ref') and appyType.isBack:
className = appyType.back.klass.__name__ className = value.im_class.__name__
value = '%s.%s' % (className, value.__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)
@ -143,10 +143,13 @@ class User(ModelClass):
'password2', 'email', 'roles'] 'password2', 'email', 'roles']
# All methods defined below are fake. Real versions are in the wrapper. # All methods defined below are fake. Real versions are in the wrapper.
title = gen.String(show=False, indexed=True) title = gen.String(show=False, indexed=True)
gm = {'group': 'main', 'multiplicity': (1,1), 'width': 25} gm = {'group': 'main', 'width': 25}
def showName(self): pass def showName(self): pass
name = gen.String(show=showName, **gm) name = gen.String(show=showName, **gm)
firstName = gen.String(show=showName, **gm) firstName = gen.String(show=showName, **gm)
def showEmail(self): pass
email = gen.String(show=showEmail)
gm['multiplicity'] = (1,1)
def showLogin(self): pass def showLogin(self): pass
def validateLogin(self): pass def validateLogin(self): pass
login = gen.String(show=showLogin, validator=validateLogin, login = gen.String(show=showLogin, validator=validateLogin,
@ -156,8 +159,6 @@ 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)
def showEmail(self): pass
email = gen.String(show=showEmail, group='main', width=25)
gm['multiplicity'] = (0, None) gm['multiplicity'] = (0, None)
def showRoles(self): pass def showRoles(self): pass
roles = gen.String(show=showRoles, indexed=True, roles = gen.String(show=showRoles, indexed=True,

View file

@ -1,16 +1,16 @@
body { font: 75% Helvetica,Arial,sans-serif; background-color: #EAEAEA; body { font: 75% Helvetica,Arial,sans-serif; background-color: #EAEAEA;
margin-top: 18px} margin-top: 18px}
pre { font: 100% Helvetica,Arial,sans-serif; margin: 0} pre { font: 100% Helvetica,Arial,sans-serif; margin: 0}
h1 { font-size: 14pt; margin:0;} h1 { font-size: 14pt; margin-bottom:4px;}
h2 { font-size: 13pt; margin:0; font-style: italic; font-weight: normal; h2 { font-size: 13pt; margin-bottom:4px; font-style: italic;
background-color: #d7dee4} font-weight: normal; background-color: #d7dee4}
h3 { font-size: 12pt; margin:0; font-weight: bold;} h3 { font-size: 12pt; margin-bottom:4px; font-weight: bold;}
h4 { font-size: 11pt; margin:0;} h4 { font-size: 11pt; margin-bottom:4px;}
h5 { font-size: 10pt; margin:0; font-style: italic; font-weight: normal; h5 { font-size: 10pt; margin:0; font-style: italic; font-weight: normal;
background-color: #d7dee4} background-color: #d7dee4}
h6 { font-size: 9pt; margin:0; font-weight: bold;} h6 { font-size: 9pt; margin:0; font-weight: bold;}
a { text-decoration: none; color: #503737;} a { text-decoration: none; color: #436976;}
a:visited { color: #503737;} a:visited { color: #436976;}
table { font-size: 100%; border-spacing: 0px; border-collapse:collapse;} table { font-size: 100%; border-spacing: 0px; border-collapse:collapse;}
form { margin: 0; padding: 0;} form { margin: 0; padding: 0;}
p { margin: 0;} p { margin: 0;}
@ -40,7 +40,7 @@ ul { line-height: 1.2em; margin: 0 0 0.2em 0.6em; padding: 0;
list-style: none outside none;} list-style: none outside none;}
ul li { margin: 0; background-image: url("ui/li.gif"); padding-left: 10px; ul li { margin: 0; background-image: url("ui/li.gif"); padding-left: 10px;
background-repeat: no-repeat; background-position: 0 4px;} background-repeat: no-repeat; background-position: 0 4px;}
img {border: 0} img { border: 0; vertical-align: middle}
/* Styles that apply when viewing content of XHTML fields, that mimic styles /* Styles that apply when viewing content of XHTML fields, that mimic styles
that ckeditor uses for displaying XHTML content in the edit view. */ that ckeditor uses for displaying XHTML content in the edit view. */
@ -51,9 +51,10 @@ img {border: 0}
.main { width: 900px; background-color: white; box-shadow: 3px 3px 3px #A9A9A9; .main { width: 900px; background-color: white; box-shadow: 3px 3px 3px #A9A9A9;
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: 6px; }
.userStrip { background-color: #d7dee4; height: 35px; .userStrip { background-color: #6282B3; height: 35px;
border-top: 1px solid #5F7983; border-bottom: 1px solid #5F7983; } border-top: 3px solid #034984; border-bottom: 2px solid #034984; }
.userStripText { font-size: 110%; padding: 0 0.3em 0 0.3em; color: white }
.login { margin-top: 2px; margin-bottom: 2px; color: black;} .login { margin-top: 2px; margin-bottom: 2px; color: black;}
.buttons { margin-left: 4px;} .buttons { margin-left: 4px;}
.fakeButton { border: 1px solid #D7DEE4; background-color: #fde8e0; .fakeButton { border: 1px solid #D7DEE4; background-color: #fde8e0;
@ -117,3 +118,6 @@ img {border: 0}
.topSpace { margin-top: 15px;} .topSpace { margin-top: 15px;}
.discreet { color: grey} .discreet { color: grey}
.pageLink { padding-left: 6px; font-style: italic} .pageLink { padding-left: 6px; font-style: italic}
.footer { font-size: 95% }
.footer td { background-color: #CBCBC9; border-top: 1px solid grey;
padding: 0.4em 1em 0.5em }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 B

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 476 B

4
gen/ui/footer.pt Normal file
View file

@ -0,0 +1,4 @@
<table metal:define-macro="footer"
cellpadding="0" cellspacing="0" width="100%" class="footer">
<tr><td align="right">Made with <a href="http://appyframework.org" target="_blank">Appy</a></td></tr>
</table>

View file

@ -289,7 +289,7 @@
</tal:edit> </tal:edit>
<tal:refresh condition="contextObj/isDebug"> <tal:refresh condition="contextObj/isDebug">
<img title="Refresh" style="cursor:pointer" <img title="Refresh" style="cursor:pointer; vertical-align:top"
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode=layoutType, page=page, refresh='yes'); tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode=layoutType, page=page, refresh='yes');
src string: $appUrl/ui/refresh.png"/> src string: $appUrl/ui/refresh.png"/>
</tal:refresh> </tal:refresh>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

After

Width:  |  Height:  |  Size: 191 B

View file

@ -39,12 +39,12 @@
<td align="right" tal:condition="tool/showLanguageSelector"> <td align="right" tal:condition="tool/showLanguageSelector">
<tal:lgs define="languages tool/getLanguages; <tal:lgs define="languages tool/getLanguages;
defaultLanguage python: languages[0]; defaultLanguage python: languages[0];
asLinks python: len(languages) &lt;= 5"> asLinks python: len(languages) &lt;= 8">
<table tal:condition="asLinks"> <table tal:condition="asLinks">
<tr> <tr>
<td tal:repeat="lang languages"> <td tal:repeat="lang languages">
<a class="lang" <a class="lang"
tal:attributes="href python: req.get('ACTUAL_URL')+'/switchLanguage?set_language=%s' % lang; tal:attributes="href string: $appUrl/config/changeLanguage?language=$lang;
title python: tool.getLanguageName(lang)" title python: tool.getLanguageName(lang)"
tal:content="python: lang"></a> tal:content="python: lang"></a>
</td> </td>
@ -94,7 +94,7 @@
</div> </div>
</td> </td>
</tr> </tr>
<tal:comment replace="nothing">The user data strip</tal:comment> <tal:comment replace="nothing">The user strip</tal:comment>
<tr> <tr>
<td> <td>
<table class="userStrip" width="100%"> <table class="userStrip" width="100%">
@ -103,7 +103,7 @@
<tal:comment replace="nothing">The user login form for anonymous users</tal:comment> <tal:comment replace="nothing">The user login form for anonymous users</tal:comment>
<table align="center" tal:condition="python: isAnon and ('/temp_folder/' not in req['ACTUAL_URL'])" <table align="center" tal:condition="python: isAnon and ('/temp_folder/' not in req['ACTUAL_URL'])"
class="login"> class="login">
<tr><td> <tr><td class="userStripText">
<form name="loginform" method="post" <form name="loginform" method="post"
tal:attributes="action python: tool.absolute_url() + '/performLogin'"> tal:attributes="action python: tool.absolute_url() + '/performLogin'">
@ -141,7 +141,7 @@
<img tal:attributes="src string: $appUrl/ui/logout.gif"/> <img tal:attributes="src string: $appUrl/ui/logout.gif"/>
</a> </a>
</td> </td>
<td align="right" tal:define="userInfo tool/getUserLine"> <td align="right" class="userStripText" tal:define="userInfo tool/getUserLine">
<span tal:content="python: userInfo[0]"></span> <span tal:content="python: userInfo[0]"></span>
<a tal:attributes="href python: userInfo[1]"> <a tal:attributes="href python: userInfo[1]">
<img tal:attributes="src string: $appUrl/ui/edit.gif"/> <img tal:attributes="src string: $appUrl/ui/edit.gif"/>
@ -168,6 +168,9 @@
</table> </table>
</td> </td>
</tr> </tr>
<tr><tal:comment replace="nothing">Footer</tal:comment>
<td><metal:call use-macro="app/ui/footer/macros/footer"/></td>
</tr>
</table> </table>
</body> </body>
</html> </html>

View file

@ -156,17 +156,16 @@
</tr></table> </tr></table>
</tal:atMostOneReference> </tal:atMostOneReference>
<tal:comment replace="nothing">Display a fieldset in all other cases.</tal:comment> <tal:comment replace="nothing">Display a table in all other cases.</tal:comment>
<tal:anyNumberOfReferences condition="not: atMostOneRef"> <tal:anyNumberOfReferences condition="not: atMostOneRef">
<fieldset tal:attributes="class python:test(innerRef, 'innerAppyFieldset', '')"> <div tal:condition="python: not innerRef or showPlusIcon">
<legend tal:condition="python: not innerRef or showPlusIcon">
(<span tal:replace="totalNumber"/>) (<span tal:replace="totalNumber"/>)
<metal:plusIcon use-macro="app/ui/widgets/ref/macros/plusIcon"/> <metal:plusIcon use-macro="app/ui/widgets/ref/macros/plusIcon"/>
<tal:comment replace="nothing">The search icon if field is queryable</tal:comment> <tal:comment replace="nothing">The search icon if field is queryable</tal:comment>
<a tal:condition="python: objs and appyType['queryable']" <a tal:condition="python: objs and appyType['queryable']"
tal:attributes="href python: '%s/ui/search?className=%s&ref=%s:%s' % (tool.absolute_url(), linkedPortalType, contextObj.UID(), appyType['name'])"> tal:attributes="href python: '%s/ui/search?className=%s&ref=%s:%s' % (tool.absolute_url(), linkedPortalType, contextObj.UID(), appyType['name'])">
<img src="search.gif" tal:attributes="title python: _('search_objects')"/></a> <img src="search.gif" tal:attributes="title python: _('search_objects')"/></a>
</legend> </div>
<tal:comment replace="nothing">Appy (top) navigation</tal:comment> <tal:comment replace="nothing">Appy (top) navigation</tal:comment>
<metal:nav use-macro="here/ui/navigate/macros/appyNavigate"/> <metal:nav use-macro="here/ui/navigate/macros/appyNavigate"/>
@ -223,9 +222,6 @@
<tal:comment replace="nothing">Appy (bottom) navigation</tal:comment> <tal:comment replace="nothing">Appy (bottom) navigation</tal:comment>
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/> <metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
</fieldset>
<tal:comment replace="nothing">A carriage return needed in some cases.</tal:comment>
</tal:anyNumberOfReferences> </tal:anyNumberOfReferences>
</div> </div>

View file

@ -51,6 +51,27 @@ class UserWrapper(AbstractWrapper):
# wants to edit information about himself. # wants to edit information about himself.
if self.user.has_role('Owner', self): return 'edit' if self.user.has_role('Owner', self): return 'edit'
def setPassword(self, newPassword=None):
'''Sets a p_newPassword for self. If p_newPassword is not given, we
generate one. This method returns the generated password (or simply
p_newPassword if no generation occurred).'''
if newPassword:
msgPart = 'changed'
else:
newPassword = self.getField('password1').generatePassword()
msgPart = 'generated'
login = self.login
zopeUser = self.o.acl_users.getUserById(login)
tool = self.tool.o
zopeUser.__ = tool._encryptPassword(newPassword)
if self.user.getId() == login:
# The user for which we change the password is the currently logged
# user. So update the authentication cookie, too.
tool._updateCookie(login, newPassword)
self.log('Password %s by "%s" for "%s".' % \
(msgPart, self.user.getId(), login))
return newPassword
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.'''
res = [] res = []
@ -70,7 +91,7 @@ class UserWrapper(AbstractWrapper):
return self._callCustom('validate', new, errors) return self._callCustom('validate', new, errors)
def onEdit(self, created): def onEdit(self, created):
self.title = self.firstName + ' ' + self.name self.title = self.login
aclUsers = self.o.acl_users aclUsers = self.o.acl_users
login = self.login login = self.login
if created: if created:
@ -89,11 +110,7 @@ class UserWrapper(AbstractWrapper):
zopeUser.roles = self.roles zopeUser.roles = self.roles
# Update the password if the user has entered new ones. # Update the password if the user has entered new ones.
rq = self.request rq = self.request
if rq.has_key('password1'): if rq.has_key('password1'): self.setPassword(rq['password1'])
tool = self.tool.o
zopeUser.__ = tool._encryptPassword(rq['password1'])
# Update the cookie value
tool._updateCookie(login, rq['password1'])
self.password1 = self.password2 = '' 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):