[gen] Added an index 'Modified' on every object (it represents the date of the last modification); removed obsolete workflow-related code; removed attributes Tool.showAllStatesInPhaseFor...; changed the way to display the object's current state in the UI.
This commit is contained in:
parent
f31cbc4d12
commit
21585df6a1
|
@ -1547,9 +1547,15 @@ class Date(Type):
|
||||||
|
|
||||||
def getFormattedValue(self, obj, value):
|
def getFormattedValue(self, obj, value):
|
||||||
if self.isEmptyValue(value): return ''
|
if self.isEmptyValue(value): return ''
|
||||||
res = value.strftime('%d/%m/') + str(value.year())
|
tool = obj.getTool().appy()
|
||||||
|
# A problem may occur with some extreme year values. Replace the "year"
|
||||||
|
# part "by hand".
|
||||||
|
dateFormat = tool.dateFormat
|
||||||
|
if '%Y' in dateFormat:
|
||||||
|
dateFormat = dateFormat.replace('%Y', str(value.year()))
|
||||||
|
res = value.strftime(dateFormat)
|
||||||
if self.format == Date.WITH_HOUR:
|
if self.format == Date.WITH_HOUR:
|
||||||
res += ' %s' % value.strftime('%H:%M')
|
res += ' %s' % value.strftime(tool.hourFormat)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getRequestValue(self, request, requestName=None):
|
def getRequestValue(self, request, requestName=None):
|
||||||
|
|
|
@ -539,18 +539,6 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
fieldType = gen.Boolean(default=True, page='userInterface',
|
fieldType = gen.Boolean(default=True, page='userInterface',
|
||||||
group=groupName)
|
group=groupName)
|
||||||
self.addField(fieldName, fieldType)
|
self.addField(fieldName, fieldType)
|
||||||
# Adds the boolean field for showing all states in current state or not.
|
|
||||||
# If this boolean is True but the current phase counts only one state,
|
|
||||||
# we will not show the state at all: the fact of knowing in what phase
|
|
||||||
# we are is sufficient. If this boolean is False, we simply show the
|
|
||||||
# current state.
|
|
||||||
defaultValue = False
|
|
||||||
if len(classDescr.getPhases()) > 1:
|
|
||||||
defaultValue = True
|
|
||||||
fieldName = 'showAllStatesInPhaseFor%s' % className
|
|
||||||
fieldType = gen.Boolean(default=defaultValue, page='userInterface',
|
|
||||||
group=groupName)
|
|
||||||
self.addField(fieldName, fieldType)
|
|
||||||
|
|
||||||
class UserClassDescriptor(ClassDescriptor):
|
class UserClassDescriptor(ClassDescriptor):
|
||||||
'''Appy-specific class for representing a user.'''
|
'''Appy-specific class for representing a user.'''
|
||||||
|
|
|
@ -502,6 +502,7 @@ class ZopeGenerator(Generator):
|
||||||
msg('object_history', '', msg.OBJECT_HISTORY),
|
msg('object_history', '', msg.OBJECT_HISTORY),
|
||||||
msg('object_created_by', '', msg.OBJECT_CREATED_BY),
|
msg('object_created_by', '', msg.OBJECT_CREATED_BY),
|
||||||
msg('object_created_on', '', msg.OBJECT_CREATED_ON),
|
msg('object_created_on', '', msg.OBJECT_CREATED_ON),
|
||||||
|
msg('object_modified_on', '', msg.OBJECT_MODIFIED_ON),
|
||||||
msg('object_action', '', msg.OBJECT_ACTION),
|
msg('object_action', '', msg.OBJECT_ACTION),
|
||||||
msg('object_author', '', msg.OBJECT_AUTHOR),
|
msg('object_author', '', msg.OBJECT_AUTHOR),
|
||||||
msg('action_date', '', msg.ACTION_DATE),
|
msg('action_date', '', msg.ACTION_DATE),
|
||||||
|
|
|
@ -9,8 +9,8 @@ from appy.shared.utils import normalizeText
|
||||||
defaultIndexes = {
|
defaultIndexes = {
|
||||||
'State': 'ListIndex', 'UID': 'FieldIndex', 'Title': 'TextIndex',
|
'State': 'ListIndex', 'UID': 'FieldIndex', 'Title': 'TextIndex',
|
||||||
'SortableTitle': 'FieldIndex', 'SearchableText': 'TextIndex',
|
'SortableTitle': 'FieldIndex', 'SearchableText': 'TextIndex',
|
||||||
'Creator': 'FieldIndex', 'Created': 'DateIndex', 'ClassName': 'FieldIndex',
|
'Creator': 'FieldIndex', 'Created': 'DateIndex', 'Modified': 'DateIndex',
|
||||||
'Allowed': 'KeywordIndex'}
|
'ClassName': 'FieldIndex', 'Allowed': 'KeywordIndex'}
|
||||||
|
|
||||||
# Stuff for creating or updating the indexes -----------------------------------
|
# Stuff for creating or updating the indexes -----------------------------------
|
||||||
class TextIndexInfo:
|
class TextIndexInfo:
|
||||||
|
|
|
@ -87,6 +87,10 @@ class BaseMixin:
|
||||||
appyObject = obj.appy()
|
appyObject = obj.appy()
|
||||||
if hasattr(appyObject, 'onEdit'):
|
if hasattr(appyObject, 'onEdit'):
|
||||||
msg = appyObject.onEdit(created)
|
msg = appyObject.onEdit(created)
|
||||||
|
# Update last modification date
|
||||||
|
if not created:
|
||||||
|
from DateTime import DateTime
|
||||||
|
obj.modified = DateTime()
|
||||||
obj.reindex()
|
obj.reindex()
|
||||||
return obj, msg
|
return obj, msg
|
||||||
|
|
||||||
|
@ -700,29 +704,6 @@ class BaseMixin:
|
||||||
res.append({'field':field, 'width':width, 'align': align})
|
res.append({'field':field, 'width':width, 'align': align})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getAppyStates(self, phase, currentOnly=False):
|
|
||||||
'''Returns information about the states that are related to p_phase.
|
|
||||||
If p_currentOnly is True, we return the current state, even if not
|
|
||||||
related to p_phase.'''
|
|
||||||
currentState = self.State()
|
|
||||||
if currentOnly:
|
|
||||||
return [StateDescr(currentState, 'current').get()]
|
|
||||||
res = []
|
|
||||||
workflow = self.getWorkflow()
|
|
||||||
stateStatus = 'done'
|
|
||||||
for stateName in dir(workflow):
|
|
||||||
if getattr(workflow, stateName).__class__.__name__ != 'State':
|
|
||||||
continue
|
|
||||||
if stateName == currentState:
|
|
||||||
stateStatus = 'current'
|
|
||||||
elif stateStatus != 'done':
|
|
||||||
stateStatus = 'future'
|
|
||||||
state = getattr(workflow, stateName)
|
|
||||||
if (state.phase == phase) and \
|
|
||||||
(self._appy_showState(workflow, state.show)):
|
|
||||||
res.append(StateDescr(stateName, stateStatus).get())
|
|
||||||
return res
|
|
||||||
|
|
||||||
def getAppyTransitions(self, includeFake=True, includeNotShowable=False):
|
def getAppyTransitions(self, includeFake=True, includeNotShowable=False):
|
||||||
'''This method returns info about transitions that one can trigger from
|
'''This method returns info about transitions that one can trigger from
|
||||||
the user interface.
|
the user interface.
|
||||||
|
@ -778,8 +759,7 @@ class BaseMixin:
|
||||||
for appyType in self.getAllAppyTypes():
|
for appyType in self.getAllAppyTypes():
|
||||||
typePhase = appyType.page.phase
|
typePhase = appyType.page.phase
|
||||||
if typePhase not in phases:
|
if typePhase not in phases:
|
||||||
states = self.getAppyStates(typePhase)
|
phase = PhaseDescr(typePhase, self)
|
||||||
phase = PhaseDescr(typePhase, states, self)
|
|
||||||
res.append(phase.__dict__)
|
res.append(phase.__dict__)
|
||||||
phases[typePhase] = phase
|
phases[typePhase] = phase
|
||||||
else:
|
else:
|
||||||
|
@ -1213,7 +1193,7 @@ class BaseMixin:
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Standard methods for computing values of standard Appy indexes
|
# Methods for computing values of standard Appy indexes
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
def UID(self):
|
def UID(self):
|
||||||
'''Returns the unique identifier for this object.'''
|
'''Returns the unique identifier for this object.'''
|
||||||
|
@ -1246,6 +1226,11 @@ class BaseMixin:
|
||||||
'''When was this object created ?'''
|
'''When was this object created ?'''
|
||||||
return self.created
|
return self.created
|
||||||
|
|
||||||
|
def Modified(self):
|
||||||
|
'''When was this object last modified ?'''
|
||||||
|
if hasattr(self.aq_base, 'modified'): return self.modified
|
||||||
|
return self.created
|
||||||
|
|
||||||
def State(self, name=True, initial=False):
|
def State(self, name=True, initial=False):
|
||||||
'''Returns information about the current object state. If p_name is
|
'''Returns information about the current object state. If p_name is
|
||||||
True, the returned info is the state name. Else, it is the State
|
True, the returned info is the state name. Else, it is the State
|
||||||
|
@ -1291,11 +1276,12 @@ class BaseMixin:
|
||||||
res.add('user:%s' % id)
|
res.add('user:%s' % id)
|
||||||
return list(res)
|
return list(res)
|
||||||
|
|
||||||
def _appy_showState(self, workflow, stateShow):
|
def showState(self):
|
||||||
'''Must I show a state whose "show value" is p_stateShow?'''
|
'''Must I show self's current state ?'''
|
||||||
|
stateShow = self.State(name=False).show
|
||||||
if callable(stateShow):
|
if callable(stateShow):
|
||||||
return stateShow(workflow, self.appy())
|
return stateShow(self.getWorkflow(), self.appy())
|
||||||
else: return stateShow
|
return stateShow
|
||||||
|
|
||||||
def _appy_listStates(self):
|
def _appy_listStates(self):
|
||||||
'''Lists the possible states for this object.'''
|
'''Lists the possible states for this object.'''
|
||||||
|
|
|
@ -210,7 +210,7 @@ setattr(Page, Page.pages.back.attribute, Page.pages.back)
|
||||||
# Prefixes of the fields generated on the Tool.
|
# Prefixes of the fields generated on the Tool.
|
||||||
toolFieldPrefixes = ('podTemplate', 'formats', 'resultColumns',
|
toolFieldPrefixes = ('podTemplate', 'formats', 'resultColumns',
|
||||||
'enableAdvancedSearch', 'numberOfSearchColumns',
|
'enableAdvancedSearch', 'numberOfSearchColumns',
|
||||||
'searchFields', 'showWorkflow', 'showAllStatesInPhase')
|
'searchFields', 'showWorkflow')
|
||||||
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
|
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
|
||||||
'appyVersion', 'dateFormat', 'hourFormat', 'users',
|
'appyVersion', 'dateFormat', 'hourFormat', 'users',
|
||||||
'connectedUsers', 'groups', 'translations',
|
'connectedUsers', 'groups', 'translations',
|
||||||
|
|
|
@ -36,7 +36,6 @@ class PoMessage:
|
||||||
MSG_numberOfSearchColumns = "Number of search columns"
|
MSG_numberOfSearchColumns = "Number of search columns"
|
||||||
MSG_searchFields = "Search fields"
|
MSG_searchFields = "Search fields"
|
||||||
MSG_showWorkflow = 'Show workflow-related information'
|
MSG_showWorkflow = 'Show workflow-related information'
|
||||||
MSG_showAllStatesInPhase = 'Show all states in phase'
|
|
||||||
POD_ASKACTION = 'Trigger related action'
|
POD_ASKACTION = 'Trigger related action'
|
||||||
REF_NO = 'No object.'
|
REF_NO = 'No object.'
|
||||||
REF_ADD = 'Add a new one'
|
REF_ADD = 'Add a new one'
|
||||||
|
@ -163,6 +162,7 @@ class PoMessage:
|
||||||
OBJECT_HISTORY = 'History'
|
OBJECT_HISTORY = 'History'
|
||||||
OBJECT_CREATED_BY = 'By'
|
OBJECT_CREATED_BY = 'By'
|
||||||
OBJECT_CREATED_ON = 'On'
|
OBJECT_CREATED_ON = 'On'
|
||||||
|
OBJECT_MODIFIED_ON = 'Last updated on'
|
||||||
OBJECT_ACTION = 'Action'
|
OBJECT_ACTION = 'Action'
|
||||||
OBJECT_AUTHOR = 'Author'
|
OBJECT_AUTHOR = 'Author'
|
||||||
ACTION_DATE = 'Date'
|
ACTION_DATE = 'Date'
|
||||||
|
|
|
@ -115,7 +115,7 @@ img { border: 0; vertical-align: middle}
|
||||||
.summary {margin-bottom: 5px;}
|
.summary {margin-bottom: 5px;}
|
||||||
.objectTitle { font-size: 11pt; border-bottom: 3px solid grey;
|
.objectTitle { font-size: 11pt; border-bottom: 3px solid grey;
|
||||||
font-weight: bold;}
|
font-weight: bold;}
|
||||||
.by { padding-top: 5px;}
|
.by { padding-top: 5px; color: grey; font-size: 97% }
|
||||||
.workflow { text-align: center; border-top: 1px solid grey }
|
.workflow { text-align: center; border-top: 1px solid grey }
|
||||||
.underTitle { background-color: #e9e9e9 }
|
.underTitle { background-color: #e9e9e9 }
|
||||||
.objectNavigate { margin-top: 3px;}
|
.objectNavigate { margin-top: 3px;}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 192 B |
|
@ -137,27 +137,6 @@
|
||||||
</tal:history>
|
</tal:history>
|
||||||
</metal:history>
|
</metal:history>
|
||||||
|
|
||||||
<tal:comment replace="nothing">
|
|
||||||
This macro displays an object's state(s). It is used by macro "header" below.
|
|
||||||
</tal:comment>
|
|
||||||
<metal:states define-macro="states"
|
|
||||||
tal:define="showAllStatesInPhase python: tool.getAttr('showAllStatesInPhaseFor' + contextObj.meta_type);
|
|
||||||
states python: contextObj.getAppyStates(phase, currentOnly=not showAllStatesInPhase)"
|
|
||||||
tal:condition="python: test(showAllStatesInPhase, len(states)>1, True)">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<tal:state repeat="stateInfo states">
|
|
||||||
<td class="state"
|
|
||||||
tal:content="python: _(contextObj.getWorkflowLabel(stateInfo['name']))">
|
|
||||||
</td>
|
|
||||||
<td tal:condition="python: stateInfo['name'] != states[-1]['name']">
|
|
||||||
<img tal:attributes="src string: $appUrl/ui/nextState.png"/>
|
|
||||||
</td>
|
|
||||||
</tal:state>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</metal:states>
|
|
||||||
|
|
||||||
<tal:comment replace="nothing">
|
<tal:comment replace="nothing">
|
||||||
This macro displays an object's transitions(s). It is used by macro "header" below.
|
This macro displays an object's transitions(s). It is used by macro "header" below.
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
|
@ -223,14 +202,27 @@
|
||||||
<span tal:replace="python: _('object_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">Document creator</tal:comment>
|
||||||
<span class="by" tal:condition="python: creator != 'Anonymous User'">
|
<tal:creator condition="python: creator != 'Anonymous User'">
|
||||||
<tal:by replace="python: _('object_created_by')"/>
|
<tal:by replace="python: _('object_created_by')"/>
|
||||||
<tal:creator replace="python: tool.getUserName(creator)"/> —
|
<tal:creator replace="python: tool.getUserName(creator)"/>
|
||||||
</span>
|
</tal:creator>
|
||||||
<tal:comment replace="nothing">Show creation date</tal:comment>
|
<tal:comment replace="nothing">Creation and last modification dates</tal:comment>
|
||||||
<tal:by replace="python: _('object_created_on')"/>
|
<tal:by replace="python: _('object_created_on')"/>
|
||||||
<span tal:replace="python: tool.formatDate(contextObj.created, withHour=True)"></span>
|
<tal:dates define="creationDate contextObj/Created;
|
||||||
|
modificationDate contextObj/Modified">
|
||||||
|
<tal:date replace="python: tool.formatDate(creationDate, withHour=True)"/>
|
||||||
|
<tal:modified condition="python: modificationDate != creationDate">—
|
||||||
|
<tal:by replace="python: _('object_modified_on')"/>
|
||||||
|
<tal:date replace="python: tool.formatDate(modificationDate, withHour=True)"/>
|
||||||
|
</tal:modified>
|
||||||
|
</tal:dates>
|
||||||
|
<tal:comment replace="nothing">State</tal:comment>
|
||||||
|
<tal:state condition="contextObj/showState">
|
||||||
|
—
|
||||||
|
<tal:label replace="python: _('workflow_state')"/>:
|
||||||
|
<b tal:content="python: _(contextObj.getWorkflowLabel())"></b>
|
||||||
|
</tal:state>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tal:comment replace="nothing">Object history</tal:comment>
|
<tal:comment replace="nothing">Object history</tal:comment>
|
||||||
|
@ -246,15 +238,10 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tal:comment replace="nothing">Workflow-related information and actions</tal:comment>
|
<tal:comment replace="nothing">Possible transitions</tal:comment>
|
||||||
<tr tal:condition="python: showWorkflow and contextObj.getWorkflowLabel()" class="workflow">
|
<tr tal:condition="python: showWorkflow and contextObj.getWorkflowLabel()" class="workflow">
|
||||||
<td colspan="2">
|
<td colspan="2" tal:attributes="align dright">
|
||||||
<table width="100%">
|
<metal:states use-macro="here/ui/page/macros/transitions"/>
|
||||||
<tr>
|
|
||||||
<td><metal:states use-macro="here/ui/page/macros/states"/></td>
|
|
||||||
<td tal:attributes="align dright"><metal:states use-macro="here/ui/page/macros/transitions"/></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
22
gen/utils.py
22
gen/utils.py
|
@ -48,6 +48,7 @@ def createObject(folder, id, className, appName, wf=True, noSecurity=False):
|
||||||
obj.creator = userId or 'Anonymous User'
|
obj.creator = userId or 'Anonymous User'
|
||||||
from DateTime import DateTime
|
from DateTime import DateTime
|
||||||
obj.created = DateTime()
|
obj.created = DateTime()
|
||||||
|
obj.modified = obj.created
|
||||||
obj.__ac_local_roles__ = { userId: ['Owner'] } # userId can be None (anon).
|
obj.__ac_local_roles__ = { userId: ['Owner'] } # userId can be None (anon).
|
||||||
if wf: obj.notifyWorkflowCreated()
|
if wf: obj.notifyWorkflowCreated()
|
||||||
return obj
|
return obj
|
||||||
|
@ -115,9 +116,8 @@ class GroupDescr(Descr):
|
||||||
groupDict['widgets'].append(newRow)
|
groupDict['widgets'].append(newRow)
|
||||||
|
|
||||||
class PhaseDescr(Descr):
|
class PhaseDescr(Descr):
|
||||||
def __init__(self, name, states, obj):
|
def __init__(self, name, obj):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.states = states
|
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
self.phaseStatus = None
|
self.phaseStatus = None
|
||||||
# The list of names of pages in this phase
|
# The list of names of pages in this phase
|
||||||
|
@ -163,21 +163,10 @@ class PhaseDescr(Descr):
|
||||||
self.hiddenPages.append(appyType.page.name)
|
self.hiddenPages.append(appyType.page.name)
|
||||||
|
|
||||||
def computeStatus(self, allPhases):
|
def computeStatus(self, allPhases):
|
||||||
'''Compute status of whole phase based on individual status of states
|
'''Compute status of this phase, which depends on the page currently
|
||||||
in this phase. If this phase includes no state, the concept of phase
|
|
||||||
is simply used as a tab, and its status depends on the page currently
|
|
||||||
shown. This method also fills fields "previousPhase" and "nextPhase"
|
shown. This method also fills fields "previousPhase" and "nextPhase"
|
||||||
if relevant, based on list of p_allPhases.'''
|
if relevant, based on list of p_allPhases.'''
|
||||||
res = 'Current'
|
res = 'Current'
|
||||||
if self.states:
|
|
||||||
# Compute status base on states
|
|
||||||
res = self.states[0]['stateStatus']
|
|
||||||
if len(self.states) > 1:
|
|
||||||
for state in self.states[1:]:
|
|
||||||
if res != state['stateStatus']:
|
|
||||||
res = 'Current'
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Compute status based on current page
|
# Compute status based on current page
|
||||||
page = self.obj.REQUEST.get('page', 'main')
|
page = self.obj.REQUEST.get('page', 'main')
|
||||||
if page in self.pages:
|
if page in self.pages:
|
||||||
|
@ -194,11 +183,6 @@ class PhaseDescr(Descr):
|
||||||
self.nextPhase = allPhases[i+1]
|
self.nextPhase = allPhases[i+1]
|
||||||
self.phaseStatus = res
|
self.phaseStatus = res
|
||||||
|
|
||||||
class StateDescr(Descr):
|
|
||||||
def __init__(self, name, stateStatus):
|
|
||||||
self.name = name
|
|
||||||
self.stateStatus = stateStatus.capitalize()
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
upperLetter = re.compile('[A-Z]')
|
upperLetter = re.compile('[A-Z]')
|
||||||
def produceNiceMessage(msg):
|
def produceNiceMessage(msg):
|
||||||
|
|
|
@ -113,12 +113,6 @@ class ToolWrapper(AbstractWrapper):
|
||||||
"showWorkflow"
|
"showWorkflow"
|
||||||
Stores the boolean field indicating if we must show workflow-
|
Stores the boolean field indicating if we must show workflow-
|
||||||
related information for p_klass or not.
|
related information for p_klass or not.
|
||||||
|
|
||||||
"showAllStatesInPhase"
|
|
||||||
Stores the boolean field indicating if we must show all states
|
|
||||||
linked to the current phase or not. If this field is False, we
|
|
||||||
simply show the current state, be it linked to the current phase
|
|
||||||
or not.
|
|
||||||
'''
|
'''
|
||||||
fullClassName = self.o.getPortalType(klass)
|
fullClassName = self.o.getPortalType(klass)
|
||||||
res = '%sFor%s' % (attributeType, fullClassName)
|
res = '%sFor%s' % (attributeType, fullClassName)
|
||||||
|
|
Loading…
Reference in a new issue