[gen] Field.indexed, instead of being a Boolean, can be a str, to represent the name of a Zope Index. This way, it is possible to bypass the standard Appy choice for index types, ie for Computed fields whose content may produce any type of value; added missing translation labels in the macro displaying object's history; added default fields Tool.dateFormat and Tool.hourFormat that give application-wide default formats for dates with/without hour; added a table in Config->Users that shows the connected users and the date/time of their last access to the app; added the missing search macro for a Computed field.

This commit is contained in:
Gaetan Delannay 2012-07-18 21:58:11 +02:00
parent 21ffa7b46d
commit 699cc8346b
8 changed files with 95 additions and 21 deletions

View file

@ -786,6 +786,10 @@ class Type:
def getIndexType(self): def getIndexType(self):
'''Returns the name of the technical, Zope-level index type for this '''Returns the name of the technical, Zope-level index type for this
field.''' field.'''
# Normally, self.indexed contains a Boolean. If a string value is given,
# we consider it to be an index type. It allows to bypass the standard
# way to decide what index type must be used.
if isinstance(self.indexed, str): return self.indexed
return 'FieldIndex' return 'FieldIndex'
def getIndexValue(self, obj, forSearch=False): def getIndexValue(self, obj, forSearch=False):
@ -2153,6 +2157,8 @@ class Action(Type):
except Exception, e: except Exception, e:
res = (False, 'An error occurred. %s' % str(e)) res = (False, 'An error occurred. %s' % str(e))
obj.log(Traceback.get(), type='error') obj.log(Traceback.get(), type='error')
#import transaction
#transaction.abort()
return res return res
def isShowable(self, obj, layoutType): def isShowable(self, obj, layoutType):

View file

@ -495,6 +495,14 @@ class ZopeGenerator(Generator):
msg('new_password', '', msg.NEW_PASSWORD), msg('new_password', '', msg.NEW_PASSWORD),
msg('new_password_body', '', msg.NEW_PASSWORD_BODY), msg('new_password_body', '', msg.NEW_PASSWORD_BODY),
msg('new_password_sent', '', msg.NEW_PASSWORD_SENT), msg('new_password_sent', '', msg.NEW_PASSWORD_SENT),
msg('last_user_access', '', msg.LAST_USER_ACCESS),
msg('object_history', '', msg.OBJECT_HISTORY),
msg('object_created_by', '', msg.OBJECT_CREATED_BY),
msg('object_created_on', '', msg.OBJECT_CREATED_ON),
msg('object_action', '', msg.OBJECT_ACTION),
msg('object_author', '', msg.OBJECT_AUTHOR),
msg('action_date', '', msg.ACTION_DATE),
msg('action_comment', '', msg.ACTION_COMMENT),
] ]
# Create a label for every role added by this application # Create a label for every role added by this application
for role in self.getAllUsedRoles(): for role in self.getAllUsedRoles():

View file

@ -1020,6 +1020,30 @@ class ToolMixin(BaseMixin):
url = appyUser.o.getUrl(mode='edit', page='main', nav='') url = appyUser.o.getUrl(mode='edit', page='main', nav='')
return (' | '.join(info), url) return (' | '.join(info), url)
def getUserName(self, login):
'''Gets the user name corresponding to p_login, or the p_login itself
if the user does not exist anymore.'''
user = self.appy().search1('User', noSecurity=True, login=login)
if not user: return login
firstName = user.firstName
name = user.name
res = ''
if firstName: res += firstName
if name:
if res: res += ' ' + name
else: res = name
if not res: res = login
return res
def formatDate(self, aDate, withHour=True):
'''Returns aDate formatted as specified by tool.dateFormat.
If p_withHour is True, hour is appended, with a format specified
in tool.hourFormat.'''
tool = self.appy()
res = aDate.strftime(tool.dateFormat)
if withHour: res += ' (%s)' % aDate.strftime(tool.hourFormat)
return res
def generateUid(self, className): def generateUid(self, className):
'''Generates a UID for an instance of p_className.''' '''Generates a UID for an instance of p_className.'''
name = className.split('_')[-1] name = className.split('_')[-1]

View file

@ -214,7 +214,8 @@ toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
'searchFields', 'optionalFields', 'showWorkflow', 'searchFields', 'optionalFields', 'showWorkflow',
'showAllStatesInPhase') 'showAllStatesInPhase')
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom', defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
'appyVersion', 'users', 'groups', 'translations', 'appyVersion', 'dateFormat', 'hourFormat', 'users',
'connectedUsers', 'groups', 'translations',
'loadTranslationsAtStartup', 'pages', 'unoEnabledPython', 'loadTranslationsAtStartup', 'pages', 'unoEnabledPython',
'openOfficePort', 'numberOfResultsPerPage') 'openOfficePort', 'numberOfResultsPerPage')
@ -226,18 +227,24 @@ class Tool(ModelClass):
# Tool attributes # Tool attributes
def isManager(self): pass def isManager(self): pass
def isManagerEdit(self): pass def isManagerEdit(self): pass
title = gen.String(show=False, page=gen.Page('main', show=False)) lf = {'layouts':'f'}
mailHost = gen.String(default='localhost:25') title = gen.String(show=False, page=gen.Page('main', show=False), **lf)
mailEnabled = gen.Boolean(default=False) mailHost = gen.String(default='localhost:25', **lf)
mailFrom = gen.String(default='info@appyframework.org') mailEnabled = gen.Boolean(default=False, **lf)
appyVersion = gen.String(layouts='f') mailFrom = gen.String(default='info@appyframework.org', **lf)
appyVersion = gen.String(**lf)
dateFormat = gen.String(default='%d/%m/%Y', **lf)
hourFormat = gen.String(default='%H:%M', **lf)
# Ref(User) will maybe be transformed into Ref(CustomUserClass). # Ref(User) will maybe be transformed into Ref(CustomUserClass).
userPage = gen.Page('users', show=isManager)
users = gen.Ref(User, multiplicity=(0,None), add=True, link=False, users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
back=gen.Ref(attribute='toTool', show=False), back=gen.Ref(attribute='toTool', show=False), page=userPage,
page=gen.Page('users', show=isManager),
queryable=True, queryFields=('title', 'login'), queryable=True, queryFields=('title', 'login'),
showHeaders=True, shownInfo=('title', 'login', 'roles')) showHeaders=True, shownInfo=('title', 'login', 'roles'))
def computeConnectedUsers(self): pass
connectedUsers = gen.Computed(method=computeConnectedUsers, page=userPage,
plainText=False)
groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False, groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False,
back=gen.Ref(attribute='toTool2', show=False), back=gen.Ref(attribute='toTool2', show=False),
page=gen.Page('groups', show=isManager), page=gen.Page('groups', show=isManager),

View file

@ -151,6 +151,14 @@ class PoMessage:
'requested for website ${siteUrl} is ${password}<br/>' \ 'requested for website ${siteUrl} is ${password}<br/>' \
'<br/>Best regards.' '<br/>Best regards.'
NEW_PASSWORD_SENT = 'Your new password has been sent to you by email.' NEW_PASSWORD_SENT = 'Your new password has been sent to you by email.'
LAST_USER_ACCESS = 'Last access'
OBJECT_HISTORY = 'History'
OBJECT_CREATED_BY = 'By'
OBJECT_CREATED_ON = 'On'
OBJECT_ACTION = 'Action'
OBJECT_AUTHOR = 'Author'
ACTION_DATE = 'Date'
ACTION_COMMENT = 'Comment'
def __init__(self, id, msg, default, fuzzy=False, comments=[], def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False): niceDefault=False):

View file

@ -72,10 +72,10 @@
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/> <metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
<table width="100%" class="history"> <table width="100%" class="history">
<tr> <tr>
<th tal:attributes="align dleft">Action</th> <th tal:attributes="align dleft" tal:content="python: _('object_action')"></th>
<th tal:attributes="align dleft">By</th> <th tal:attributes="align dleft" tal:content="python: _('object_author')"></th>
<th tal:attributes="align dleft">Date</th> <th tal:attributes="align dleft" tal:content="python: _('action_date')"></th>
<th tal:attributes="align dleft">Comment</th> <th tal:attributes="align dleft" tal:content="python: _('action_comment')"></th>
</tr> </tr>
<tal:event repeat="event objs"> <tal:event repeat="event objs">
<tr tal:define="odd repeat/event/odd; <tr tal:define="odd repeat/event/odd;
@ -86,7 +86,8 @@
<td tal:condition="isDataChange" tal:content="python: _('data_change')"></td> <td tal:condition="isDataChange" tal:content="python: _('data_change')"></td>
<td tal:condition="not: isDataChange" <td tal:condition="not: isDataChange"
tal:content="python: _(contextObj.getWorkflowLabel(event['action']))"/> tal:content="python: _(contextObj.getWorkflowLabel(event['action']))"/>
<td tal:define="actorid python:event.get('actor');" tal:content="actorid"/> <td tal:define="actorid python:event.get('actor')"
tal:content="python: tool.getUserName(actorid)"/>
<td tal:content="event/time"/> <td tal:content="event/time"/>
<td tal:condition="not: isDataChange"> <td tal:condition="not: isDataChange">
<tal:c condition="rhComments" <tal:c condition="rhComments"
@ -202,17 +203,17 @@
tal:attributes="src python:test(historyExpanded, 'ui/collapse.gif', 'ui/expand.gif'); tal:attributes="src python:test(historyExpanded, 'ui/collapse.gif', 'ui/expand.gif');
align dleft" align dleft"
id="appyHistory_img"/>&nbsp; id="appyHistory_img"/>&nbsp;
<span>History</span> ||&nbsp; <span tal:replace="python: _('object_history')"></span> ||&nbsp;
</tal:accessHistory> </tal:accessHistory>
<tal:comment replace="nothing">Show document creator</tal:comment> <tal:comment replace="nothing">Show document creator</tal:comment>
<span class="by" tal:condition="creator"> <span class="by" tal:condition="python: creator != 'Anonymous User'">
<span>by <span tal:replace="creator"/> <tal:by replace="python: _('object_created_by')"/>
&mdash; <tal:creator replace="python: tool.getUserName(creator)"/> &mdash;
</span>
</span> </span>
<tal:comment replace="nothing">Show creation date</tal:comment> <tal:comment replace="nothing">Show creation date</tal:comment>
<span tal:replace="python:contextObj.created"></span> <tal:by replace="python: _('object_created_on')"/>
<span tal:replace="python: tool.formatDate(contextObj.created, withHour=True)"></span>
</td> </td>
</tr> </tr>
<tal:comment replace="nothing">Object history</tal:comment> <tal:comment replace="nothing">Object history</tal:comment>

View file

@ -34,4 +34,9 @@
</metal:cell> </metal:cell>
<tal:comment replace="nothing">Search macro for a Computed.</tal:comment> <tal:comment replace="nothing">Search macro for a Computed.</tal:comment>
<metal:search define-macro="search"></metal:search> <metal:search define-macro="search">
<label tal:attributes="for widgetName" tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp;
<input type="text" tal:define="maxChars python: test(widget['maxChars'], widget['maxChars'], '')"
tal:attributes="name python: '%s*string' % widgetName;
maxlength maxChars"/>
</metal:search>

View file

@ -1,8 +1,9 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os.path import os.path, time
import appy import appy
from appy.shared.utils import executeCommand from appy.shared.utils import executeCommand
from appy.gen.wrappers import AbstractWrapper from appy.gen.wrappers import AbstractWrapper
from appy.gen.installer import loggedUsers
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
_PY = 'Please specify a file corresponding to a Python interpreter ' \ _PY = 'Please specify a file corresponding to a Python interpreter ' \
@ -38,6 +39,20 @@ class ToolWrapper(AbstractWrapper):
'''Some pages on the tool can only be accessed by God, also in edit.''' '''Some pages on the tool can only be accessed by God, also in edit.'''
if self.user.has_role('Manager'): return True if self.user.has_role('Manager'): return True
def computeConnectedUsers(self):
'''Computes a table showing users that are currently connected.'''
res = '<table cellpadding="0" cellspacing="0" class="list"><tr>' \
'<th></th><th>%s</th></tr>' % self.translate('last_user_access')
rows = []
for userId, lastAccess in loggedUsers.items():
user = self.search1('User', noSecurity=True, login=userId)
if not user: continue # Could have been deleted in the meanwhile
fmt = '%s (%s)' % (self.dateFormat, self.hourFormat)
access = time.strftime(fmt, time.localtime(lastAccess))
rows.append('<tr><td><a href="%s">%s</a></td><td>%s</td></tr>' % \
(user.o.absolute_url(), user.title,access))
return res + '\n'.join(rows) + '</table>'
podOutputFormats = ('odt', 'pdf', 'doc', 'rtf') podOutputFormats = ('odt', 'pdf', 'doc', 'rtf')
def getPodOutputFormats(self): def getPodOutputFormats(self):
'''Gets the available output formats for POD documents.''' '''Gets the available output formats for POD documents.'''