From 21585df6a1ef14188d870eac51f986dc80d3203f Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Mon, 5 Nov 2012 10:21:27 +0100 Subject: [PATCH] [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. --- gen/__init__.py | 10 ++++-- gen/descriptors.py | 12 -------- gen/generator.py | 1 + gen/indexer.py | 4 +-- gen/mixins/__init__.py | 46 ++++++++++------------------ gen/model.py | 2 +- gen/po.py | 2 +- gen/ui/appy.css | 2 +- gen/ui/nextState.png | Bin 192 -> 0 bytes gen/ui/page.pt | 59 ++++++++++++++---------------------- gen/utils.py | 48 ++++++++++------------------- gen/wrappers/ToolWrapper.py | 6 ---- 12 files changed, 69 insertions(+), 123 deletions(-) delete mode 100644 gen/ui/nextState.png diff --git a/gen/__init__.py b/gen/__init__.py index 0d9776b..4c90018 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -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): diff --git a/gen/descriptors.py b/gen/descriptors.py index 155edef..adbf5ad 100644 --- a/gen/descriptors.py +++ b/gen/descriptors.py @@ -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.''' diff --git a/gen/generator.py b/gen/generator.py index 86e2346..f62b263 100644 --- a/gen/generator.py +++ b/gen/generator.py @@ -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), diff --git a/gen/indexer.py b/gen/indexer.py index 79d069b..21c5432 100644 --- a/gen/indexer.py +++ b/gen/indexer.py @@ -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: diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index 5c33533..d7d2d9f 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -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.''' diff --git a/gen/model.py b/gen/model.py index b1d1ddc..4d24717 100644 --- a/gen/model.py +++ b/gen/model.py @@ -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', diff --git a/gen/po.py b/gen/po.py index 4073fbe..74eccbf 100644 --- a/gen/po.py +++ b/gen/po.py @@ -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' diff --git a/gen/ui/appy.css b/gen/ui/appy.css index 35462a5..31104e0 100644 --- a/gen/ui/appy.css +++ b/gen/ui/appy.css @@ -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;} diff --git a/gen/ui/nextState.png b/gen/ui/nextState.png deleted file mode 100644 index bb8faf4b18da9171536b60b0eb5a4a9933a8a325..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4?3?zm2ODlt@0G|-o|2I~7s4)EB5}L!n;K9J~ z|3;{l>p2UcEMrNKUoeBivm0q3PLj8~3quF1EKmspXMsm#F#`j)FbFd;%$g$s6l5>) z^mS#w$t=$;t-qu9a1BsM-P6S}gyVX0LPB8z1CyAj!owGfSe{sDYOZYXRA4_;#@OwA crQs?wLt8S-v%VuzRX{Bap00i_>zopr0B{yDqyPW_ diff --git a/gen/ui/page.pt b/gen/ui/page.pt index 475277b..4e88163 100644 --- a/gen/ui/page.pt +++ b/gen/ui/page.pt @@ -137,27 +137,6 @@ - - This macro displays an object's state(s). It is used by macro "header" below. - - - - - - - - - -
- - -
-
- This macro displays an object's transitions(s). It is used by macro "header" below. @@ -223,15 +202,28 @@ ||  - Show document creator - + Document creator + - — - - Show creation date + + + Creation and last modification dates - - + + + — + + + + + State + + — + : + + + Object history @@ -246,15 +238,10 @@ - Workflow-related information and actions + Possible transitions - - - - - - -
+ + diff --git a/gen/utils.py b/gen/utils.py index 54d87d8..4bd7b46 100644 --- a/gen/utils.py +++ b/gen/utils.py @@ -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): diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py index 5de6021..91cfb26 100644 --- a/gen/wrappers/ToolWrapper.py +++ b/gen/wrappers/ToolWrapper.py @@ -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)