[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):
|
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):
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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]
|
||||||
|
|
23
gen/model.py
23
gen/model.py
|
@ -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),
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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"/>
|
id="appyHistory_img"/>
|
||||||
<span>History</span> ||
|
<span tal:replace="python: _('object_history')"></span> ||
|
||||||
</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')"/>
|
||||||
—
|
<tal:creator replace="python: tool.getUserName(creator)"/> —
|
||||||
</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>
|
||||||
|
|
|
@ -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>
|
||||||
|
<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
|
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.'''
|
||||||
|
|
Loading…
Reference in a new issue