[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:
parent
21ffa7b46d
commit
699cc8346b
|
@ -786,6 +786,10 @@ class Type:
|
|||
def getIndexType(self):
|
||||
'''Returns the name of the technical, Zope-level index type for this
|
||||
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'
|
||||
|
||||
def getIndexValue(self, obj, forSearch=False):
|
||||
|
@ -2153,6 +2157,8 @@ class Action(Type):
|
|||
except Exception, e:
|
||||
res = (False, 'An error occurred. %s' % str(e))
|
||||
obj.log(Traceback.get(), type='error')
|
||||
#import transaction
|
||||
#transaction.abort()
|
||||
return res
|
||||
|
||||
def isShowable(self, obj, layoutType):
|
||||
|
|
|
@ -495,6 +495,14 @@ class ZopeGenerator(Generator):
|
|||
msg('new_password', '', msg.NEW_PASSWORD),
|
||||
msg('new_password_body', '', msg.NEW_PASSWORD_BODY),
|
||||
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
|
||||
for role in self.getAllUsedRoles():
|
||||
|
|
|
@ -1020,6 +1020,30 @@ class ToolMixin(BaseMixin):
|
|||
url = appyUser.o.getUrl(mode='edit', page='main', nav='')
|
||||
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):
|
||||
'''Generates a UID for an instance of p_className.'''
|
||||
name = className.split('_')[-1]
|
||||
|
|
23
gen/model.py
23
gen/model.py
|
@ -214,7 +214,8 @@ toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
|
|||
'searchFields', 'optionalFields', 'showWorkflow',
|
||||
'showAllStatesInPhase')
|
||||
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
|
||||
'appyVersion', 'users', 'groups', 'translations',
|
||||
'appyVersion', 'dateFormat', 'hourFormat', 'users',
|
||||
'connectedUsers', 'groups', 'translations',
|
||||
'loadTranslationsAtStartup', 'pages', 'unoEnabledPython',
|
||||
'openOfficePort', 'numberOfResultsPerPage')
|
||||
|
||||
|
@ -226,18 +227,24 @@ class Tool(ModelClass):
|
|||
# Tool attributes
|
||||
def isManager(self): pass
|
||||
def isManagerEdit(self): pass
|
||||
title = gen.String(show=False, page=gen.Page('main', show=False))
|
||||
mailHost = gen.String(default='localhost:25')
|
||||
mailEnabled = gen.Boolean(default=False)
|
||||
mailFrom = gen.String(default='info@appyframework.org')
|
||||
appyVersion = gen.String(layouts='f')
|
||||
lf = {'layouts':'f'}
|
||||
title = gen.String(show=False, page=gen.Page('main', show=False), **lf)
|
||||
mailHost = gen.String(default='localhost:25', **lf)
|
||||
mailEnabled = gen.Boolean(default=False, **lf)
|
||||
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).
|
||||
userPage = gen.Page('users', show=isManager)
|
||||
users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
|
||||
back=gen.Ref(attribute='toTool', show=False),
|
||||
page=gen.Page('users', show=isManager),
|
||||
back=gen.Ref(attribute='toTool', show=False), page=userPage,
|
||||
queryable=True, queryFields=('title', 'login'),
|
||||
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,
|
||||
back=gen.Ref(attribute='toTool2', show=False),
|
||||
page=gen.Page('groups', show=isManager),
|
||||
|
|
|
@ -151,6 +151,14 @@ class PoMessage:
|
|||
'requested for website ${siteUrl} is ${password}<br/>' \
|
||||
'<br/>Best regards.'
|
||||
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=[],
|
||||
niceDefault=False):
|
||||
|
|
|
@ -72,10 +72,10 @@
|
|||
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
|
||||
<table width="100%" class="history">
|
||||
<tr>
|
||||
<th tal:attributes="align dleft">Action</th>
|
||||
<th tal:attributes="align dleft">By</th>
|
||||
<th tal:attributes="align dleft">Date</th>
|
||||
<th tal:attributes="align dleft">Comment</th>
|
||||
<th tal:attributes="align dleft" tal:content="python: _('object_action')"></th>
|
||||
<th tal:attributes="align dleft" tal:content="python: _('object_author')"></th>
|
||||
<th tal:attributes="align dleft" tal:content="python: _('action_date')"></th>
|
||||
<th tal:attributes="align dleft" tal:content="python: _('action_comment')"></th>
|
||||
</tr>
|
||||
<tal:event repeat="event objs">
|
||||
<tr tal:define="odd repeat/event/odd;
|
||||
|
@ -86,7 +86,8 @@
|
|||
<td tal:condition="isDataChange" tal:content="python: _('data_change')"></td>
|
||||
<td tal:condition="not: isDataChange"
|
||||
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:condition="not: isDataChange">
|
||||
<tal:c condition="rhComments"
|
||||
|
@ -202,17 +203,17 @@
|
|||
tal:attributes="src python:test(historyExpanded, 'ui/collapse.gif', 'ui/expand.gif');
|
||||
align dleft"
|
||||
id="appyHistory_img"/>
|
||||
<span>History</span> ||
|
||||
<span tal:replace="python: _('object_history')"></span> ||
|
||||
</tal:accessHistory>
|
||||
|
||||
<tal:comment replace="nothing">Show document creator</tal:comment>
|
||||
<span class="by" tal:condition="creator">
|
||||
<span>by <span tal:replace="creator"/>
|
||||
—
|
||||
</span>
|
||||
<span class="by" tal:condition="python: creator != 'Anonymous User'">
|
||||
<tal:by replace="python: _('object_created_by')"/>
|
||||
<tal:creator replace="python: tool.getUserName(creator)"/> —
|
||||
</span>
|
||||
<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>
|
||||
</tr>
|
||||
<tal:comment replace="nothing">Object history</tal:comment>
|
||||
|
|
|
@ -34,4 +34,9 @@
|
|||
</metal:cell>
|
||||
|
||||
<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>
|
||||
<input type="text" tal:define="maxChars python: test(widget['maxChars'], widget['maxChars'], '')"
|
||||
tal:attributes="name python: '%s*string' % widgetName;
|
||||
maxlength maxChars"/>
|
||||
</metal:search>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os.path
|
||||
import os.path, time
|
||||
import appy
|
||||
from appy.shared.utils import executeCommand
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
from appy.gen.installer import loggedUsers
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
_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.'''
|
||||
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')
|
||||
def getPodOutputFormats(self):
|
||||
'''Gets the available output formats for POD documents.'''
|
||||
|
|
Loading…
Reference in a new issue