[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:
Gaetan Delannay 2012-11-05 10:21:27 +01:00
parent f31cbc4d12
commit 21585df6a1
12 changed files with 69 additions and 123 deletions

View file

@ -1547,9 +1547,15 @@ class Date(Type):
def getFormattedValue(self, obj, value):
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:
res += ' %s' % value.strftime('%H:%M')
res += ' %s' % value.strftime(tool.hourFormat)
return res
def getRequestValue(self, request, requestName=None):

View file

@ -539,18 +539,6 @@ class ToolClassDescriptor(ClassDescriptor):
fieldType = gen.Boolean(default=True, page='userInterface',
group=groupName)
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):
'''Appy-specific class for representing a user.'''

View file

@ -502,6 +502,7 @@ class ZopeGenerator(Generator):
msg('object_history', '', msg.OBJECT_HISTORY),
msg('object_created_by', '', msg.OBJECT_CREATED_BY),
msg('object_created_on', '', msg.OBJECT_CREATED_ON),
msg('object_modified_on', '', msg.OBJECT_MODIFIED_ON),
msg('object_action', '', msg.OBJECT_ACTION),
msg('object_author', '', msg.OBJECT_AUTHOR),
msg('action_date', '', msg.ACTION_DATE),

View file

@ -9,8 +9,8 @@ from appy.shared.utils import normalizeText
defaultIndexes = {
'State': 'ListIndex', 'UID': 'FieldIndex', 'Title': 'TextIndex',
'SortableTitle': 'FieldIndex', 'SearchableText': 'TextIndex',
'Creator': 'FieldIndex', 'Created': 'DateIndex', 'ClassName': 'FieldIndex',
'Allowed': 'KeywordIndex'}
'Creator': 'FieldIndex', 'Created': 'DateIndex', 'Modified': 'DateIndex',
'ClassName': 'FieldIndex', 'Allowed': 'KeywordIndex'}
# Stuff for creating or updating the indexes -----------------------------------
class TextIndexInfo:

View file

@ -87,6 +87,10 @@ class BaseMixin:
appyObject = obj.appy()
if hasattr(appyObject, 'onEdit'):
msg = appyObject.onEdit(created)
# Update last modification date
if not created:
from DateTime import DateTime
obj.modified = DateTime()
obj.reindex()
return obj, msg
@ -700,29 +704,6 @@ class BaseMixin:
res.append({'field':field, 'width':width, 'align': align})
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):
'''This method returns info about transitions that one can trigger from
the user interface.
@ -778,8 +759,7 @@ class BaseMixin:
for appyType in self.getAllAppyTypes():
typePhase = appyType.page.phase
if typePhase not in phases:
states = self.getAppyStates(typePhase)
phase = PhaseDescr(typePhase, states, self)
phase = PhaseDescr(typePhase, self)
res.append(phase.__dict__)
phases[typePhase] = phase
else:
@ -1213,7 +1193,7 @@ class BaseMixin:
return wrapper
# --------------------------------------------------------------------------
# Standard methods for computing values of standard Appy indexes
# Methods for computing values of standard Appy indexes
# --------------------------------------------------------------------------
def UID(self):
'''Returns the unique identifier for this object.'''
@ -1246,6 +1226,11 @@ class BaseMixin:
'''When was this object 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):
'''Returns information about the current object state. If p_name is
True, the returned info is the state name. Else, it is the State
@ -1291,11 +1276,12 @@ class BaseMixin:
res.add('user:%s' % id)
return list(res)
def _appy_showState(self, workflow, stateShow):
'''Must I show a state whose "show value" is p_stateShow?'''
def showState(self):
'''Must I show self's current state ?'''
stateShow = self.State(name=False).show
if callable(stateShow):
return stateShow(workflow, self.appy())
else: return stateShow
return stateShow(self.getWorkflow(), self.appy())
return stateShow
def _appy_listStates(self):
'''Lists the possible states for this object.'''

View file

@ -210,7 +210,7 @@ setattr(Page, Page.pages.back.attribute, Page.pages.back)
# Prefixes of the fields generated on the Tool.
toolFieldPrefixes = ('podTemplate', 'formats', 'resultColumns',
'enableAdvancedSearch', 'numberOfSearchColumns',
'searchFields', 'showWorkflow', 'showAllStatesInPhase')
'searchFields', 'showWorkflow')
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
'appyVersion', 'dateFormat', 'hourFormat', 'users',
'connectedUsers', 'groups', 'translations',

View file

@ -36,7 +36,6 @@ class PoMessage:
MSG_numberOfSearchColumns = "Number of search columns"
MSG_searchFields = "Search fields"
MSG_showWorkflow = 'Show workflow-related information'
MSG_showAllStatesInPhase = 'Show all states in phase'
POD_ASKACTION = 'Trigger related action'
REF_NO = 'No object.'
REF_ADD = 'Add a new one'
@ -163,6 +162,7 @@ class PoMessage:
OBJECT_HISTORY = 'History'
OBJECT_CREATED_BY = 'By'
OBJECT_CREATED_ON = 'On'
OBJECT_MODIFIED_ON = 'Last updated on'
OBJECT_ACTION = 'Action'
OBJECT_AUTHOR = 'Author'
ACTION_DATE = 'Date'

View file

@ -115,7 +115,7 @@ img { border: 0; vertical-align: middle}
.summary {margin-bottom: 5px;}
.objectTitle { font-size: 11pt; border-bottom: 3px solid grey;
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 }
.underTitle { background-color: #e9e9e9 }
.objectNavigate { margin-top: 3px;}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

View file

@ -137,27 +137,6 @@
</tal: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)&gt;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">
This macro displays an object's transitions(s). It is used by macro "header" below.
</tal:comment>
@ -223,15 +202,28 @@
<span tal:replace="python: _('object_history')"></span> ||&nbsp;
</tal:accessHistory>
<tal:comment replace="nothing">Show document creator</tal:comment>
<span class="by" tal:condition="python: creator != 'Anonymous User'">
<tal:comment replace="nothing">Document creator</tal:comment>
<tal:creator condition="python: creator != 'Anonymous User'">
<tal:by replace="python: _('object_created_by')"/>
<tal:creator replace="python: tool.getUserName(creator)"/> &mdash;
</span>
<tal:comment replace="nothing">Show creation date</tal:comment>
<tal:creator replace="python: tool.getUserName(creator)"/>
</tal:creator>
<tal:comment replace="nothing">Creation and last modification dates</tal:comment>
<tal:by replace="python: _('object_created_on')"/>
<span tal:replace="python: tool.formatDate(contextObj.created, withHour=True)"></span>
</td>
<tal:dates define="creationDate contextObj/Created;
modificationDate contextObj/Modified">
<tal:date replace="python: tool.formatDate(creationDate, withHour=True)"/>
<tal:modified condition="python: modificationDate != creationDate">&mdash;
<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">
&mdash;
<tal:label replace="python: _('workflow_state')"/>:
<b tal:content="python: _(contextObj.getWorkflowLabel())"></b>
</tal:state>
</td>
</tr>
<tal:comment replace="nothing">Object history</tal:comment>
<tr tal:condition="hasHistory" class="underTitle">
@ -246,15 +238,10 @@
</span>
</td>
</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">
<td colspan="2">
<table width="100%">
<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 colspan="2" tal:attributes="align dright">
<metal:states use-macro="here/ui/page/macros/transitions"/>
</td>
</tr>
</table>

View file

@ -48,6 +48,7 @@ def createObject(folder, id, className, appName, wf=True, noSecurity=False):
obj.creator = userId or 'Anonymous User'
from DateTime import DateTime
obj.created = DateTime()
obj.modified = obj.created
obj.__ac_local_roles__ = { userId: ['Owner'] } # userId can be None (anon).
if wf: obj.notifyWorkflowCreated()
return obj
@ -115,9 +116,8 @@ class GroupDescr(Descr):
groupDict['widgets'].append(newRow)
class PhaseDescr(Descr):
def __init__(self, name, states, obj):
def __init__(self, name, obj):
self.name = name
self.states = states
self.obj = obj
self.phaseStatus = None
# The list of names of pages in this phase
@ -163,42 +163,26 @@ class PhaseDescr(Descr):
self.hiddenPages.append(appyType.page.name)
def computeStatus(self, allPhases):
'''Compute status of whole phase based on individual status of states
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
'''Compute status of this phase, which depends on the page currently
shown. This method also fills fields "previousPhase" and "nextPhase"
if relevant, based on list of p_allPhases.'''
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
# Compute status based on current page
page = self.obj.REQUEST.get('page', 'main')
if page in self.pages:
res = 'Current'
else:
# Compute status based on current page
page = self.obj.REQUEST.get('page', 'main')
if page in self.pages:
res = 'Current'
else:
res = 'Deselected'
# Identify previous and next phases
for phaseInfo in allPhases:
if phaseInfo['name'] == self.name:
i = allPhases.index(phaseInfo)
if i > 0:
self.previousPhase = allPhases[i-1]
if i < (len(allPhases)-1):
self.nextPhase = allPhases[i+1]
res = 'Deselected'
# Identify previous and next phases
for phaseInfo in allPhases:
if phaseInfo['name'] == self.name:
i = allPhases.index(phaseInfo)
if i > 0:
self.previousPhase = allPhases[i-1]
if i < (len(allPhases)-1):
self.nextPhase = allPhases[i+1]
self.phaseStatus = res
class StateDescr(Descr):
def __init__(self, name, stateStatus):
self.name = name
self.stateStatus = stateStatus.capitalize()
# ------------------------------------------------------------------------------
upperLetter = re.compile('[A-Z]')
def produceNiceMessage(msg):

View file

@ -113,12 +113,6 @@ class ToolWrapper(AbstractWrapper):
"showWorkflow"
Stores the boolean field indicating if we must show workflow-
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)
res = '%sFor%s' % (attributeType, fullClassName)