From 2aedf8c88a936af017b66228828a3100ec09bbaa Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Tue, 19 Oct 2010 10:47:42 +0200 Subject: [PATCH] Added a new level of configurability in navigation: allow to hide/show every button on every page + bugfixes in page/phase navigation. --- gen/__init__.py | 77 ++++++++++++++++++++++----- gen/descriptors.py | 4 +- gen/odt/generator.py | 2 +- gen/plone25/descriptors.py | 13 ++--- gen/plone25/mixins/__init__.py | 85 +++++++++++++++--------------- gen/plone25/model.py | 10 ++-- gen/plone25/skin/edit.pt | 6 +-- gen/plone25/skin/page.pt | 93 +++++++++++++++++---------------- gen/plone25/skin/portlet.pt | 4 +- gen/plone25/skin/view.pt | 22 ++++---- gen/plone25/skin/widgets/ref.pt | 8 +-- gen/utils.py | 54 +++++++++---------- 12 files changed, 213 insertions(+), 165 deletions(-) diff --git a/gen/__init__.py b/gen/__init__.py index fd1a03b..54c3105 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -4,8 +4,8 @@ from appy.shared.utils import Traceback from appy.gen.layout import Table from appy.gen.layout import defaultFieldLayouts from appy.gen.po import PoMessage -from appy.gen.utils import sequenceTypes, PageDescr, GroupDescr, Keywords, \ - FileWrapper, getClassName, SomeObjects +from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \ + getClassName, SomeObjects from appy.shared.data import languages # Default Appy permissions ----------------------------------------------------- @@ -22,10 +22,67 @@ emptyTuple = () # (pages, groups,...) ---------------------------------------------------------- class Page: '''Used for describing a page, its related phase, show condition, etc.''' - def __init__(self, name, phase='main', show=True): + subElements = ('save', 'cancel', 'previous', 'next') + def __init__(self, name, phase='main', show=True, showSave=True, + showCancel=True, showPrevious=True, showNext=True): self.name = name self.phase = phase self.show = show + # When editing the page, must I show the "save" button? + self.showSave = showSave + # When editing the page, must I show the "cancel" button? + self.showCancel = showCancel + # When editing the page, and when a previous page exists, must I show + # the "previous" button? + self.showPrevious = showPrevious + # When editing the page, and when a next page exists, must I show the + # "next" button? + self.showNext = showNext + + @staticmethod + def get(pageData): + '''Produces a Page instance from p_pageData. User-defined p_pageData + can be: + (a) a string containing the name of the page; + (b) a string containing _; + (c) a Page instance. + This method returns always a Page instance.''' + res = pageData + if res and isinstance(res, basestring): + # Page data is given as a string. + pageElems = pageData.rsplit('_', 1) + if len(pageElems) == 1: # We have case (a) + res = Page(pageData) + else: # We have case (b) + res = Page(pageData[0], phase=pageData[1]) + return res + + def isShowable(self, obj, layoutType, elem='page'): + '''Must this page be shown for p_obj? "Show value" can be True, False + or 'view' (page is available only in "view" mode). + + If p_elem is not "page", this method returns the fact that a + sub-element is viewable or not (button "save", "cancel", etc).''' + # Define what attribute to test for "showability". + showAttr = 'show' + if elem != 'page': + showAttr = 'show%s' % elem.capitalize() + # Get the value of the show attribute as identified above. + show = getattr(self, showAttr) + if callable(show): show = show(obj.appy()) + # Show value can be 'view', for example. Thanks to p_layoutType, + # convert show value to a real final boolean value. + res = show + if res == 'view': res = layoutType == 'view' + return res + + def getInfo(self, obj, layoutType): + '''Gets information about this page, for p_obj, as a dict.''' + res = {} + for elem in Page.subElements: + res['show%s' % elem.capitalize()] = self.isShowable(obj, layoutType, + elem=elem) + return res class Group: '''Used for describing a group of widgets within a page.''' @@ -313,9 +370,9 @@ class Type: # Must the field be visible or not? self.show = show # When displaying/editing the whole object, on what page and phase must - # this field value appear? Default is ('main', 'main'). pageShow - # indicates if the page must be shown or not. - self.page, self.phase, self.pageShow = PageDescr.getPageInfo(page) + # this field value appear? + self.page = Page.get(page) + self.pageName = self.page.name # Within self.page, in what group of fields must this field value # appear? self.group = Group.get(group) @@ -465,14 +522,6 @@ class Type: else: res = False return res - def showPage(self, obj): - '''Must the page where this field lies be shown ? "Show value" can be - True, False or 'view' (page is available only in "view" mode).''' - if callable(self.pageShow): - return self.pageShow(obj.appy()) - else: - return self.pageShow - def formatSync(self, sync): '''Creates a dictionary indicating, for every layout type, if the field value must be retrieved synchronously or not.''' diff --git a/gen/descriptors.py b/gen/descriptors.py index b263ca6..4c69dc1 100644 --- a/gen/descriptors.py +++ b/gen/descriptors.py @@ -73,8 +73,8 @@ class ClassDescriptor(Descriptor): '''Gets the phases defined on fields of this class.''' res = [] for fieldName, appyType, klass in self.getOrderedAppyAttributes(): - if appyType.phase not in res: - res.append(appyType.phase) + if appyType.page.phase not in res: + res.append(appyType.page.phase) return res class WorkflowDescriptor(Descriptor): diff --git a/gen/odt/generator.py b/gen/odt/generator.py index 2a57168..b4b4d89 100644 --- a/gen/odt/generator.py +++ b/gen/odt/generator.py @@ -37,7 +37,7 @@ class Generator(AbstractGenerator): if the page where the field must be displayed has a boolean attribute "show" having the boolean value False.''' if (type(field.show) == bool) and not field.show: return True - if (type(field.pageShow) == bool) and not field.pageShow: return True + if (type(field.page.show) == bool) and not field.page.show: return True return False undumpable = ('Ref', 'Action', 'File', 'Computed') diff --git a/gen/plone25/descriptors.py b/gen/plone25/descriptors.py index dd8717d..7d9a1f6 100644 --- a/gen/plone25/descriptors.py +++ b/gen/plone25/descriptors.py @@ -13,8 +13,7 @@ import appy.gen.descriptors from appy.gen.po import PoMessage from appy.gen import Date, String, State, Transition, Type, Search, \ Selection, Import, Role -from appy.gen.utils import GroupDescr, PageDescr, produceNiceMessage, \ - sequenceTypes, getClassName +from appy.gen.utils import produceNiceMessage, getClassName TABS = 4 # Number of blanks in a Python indentation. # ------------------------------------------------------------------------------ @@ -172,12 +171,14 @@ class FieldDescriptor: messages.append(self.produceMessage(helpId, isLabel=False)) # Create i18n messages linked to pages and phases messages = self.generator.labels - pageMsgId = '%s_page_%s' % (self.classDescr.name, self.appyType.page) - phaseMsgId = '%s_phase_%s' % (self.classDescr.name, self.appyType.phase) + pageMsgId = '%s_page_%s' % (self.classDescr.name, + self.appyType.page.name) + phaseMsgId = '%s_phase_%s' % (self.classDescr.name, + self.appyType.page.phase) pagePoMsg = PoMessage(pageMsgId, '', - produceNiceMessage(self.appyType.page)) + produceNiceMessage(self.appyType.page.name)) phasePoMsg = PoMessage(phaseMsgId, '', - produceNiceMessage(self.appyType.phase)) + produceNiceMessage(self.appyType.page.phase)) for poMsg in (pagePoMsg, phasePoMsg): if poMsg not in messages: messages.append(poMsg) diff --git a/gen/plone25/mixins/__init__.py b/gen/plone25/mixins/__init__.py index afbfd01..f865f90 100644 --- a/gen/plone25/mixins/__init__.py +++ b/gen/plone25/mixins/__init__.py @@ -149,7 +149,6 @@ class BaseMixin: errorMessage = self.translate( 'Please correct the indicated errors.', domain='plone') isNew = rq.get('is_new') == 'True' - # Go back to the consult view if the user clicked on 'Cancel' if rq.get('buttonCancel.x', None): if isNew: @@ -204,31 +203,29 @@ class BaseMixin: # previous pages may have changed). Moreover, previous and next # pages may not be available in "edit" mode, so we return the edit # or view pages depending on page.show. - currentPage = rq.get('page') - phaseInfo = self.getAppyPhases(page=currentPage) - previousPage, show = self.getPreviousPage(phaseInfo, currentPage) - if previousPage: + phaseInfo = self.getAppyPhases(currentOnly=True, layoutType='edit') + pageName, pageInfo = self.getPreviousPage(phaseInfo, rq['page']) + if pageName: # Return to the edit or view page? - if show != 'view': - rq.set('page', previousPage) + if pageInfo['showOnEdit']: + rq.set('page', pageName) return obj.skyn.edit(obj) else: - return self.goto(obj.getUrl(page=previousPage)) + return self.goto(obj.getUrl(page=pageName)) else: obj.plone_utils.addPortalMessage(msg) return self.goto(obj.getUrl()) if rq.get('buttonNext.x', None): # Go to the next page for this object - currentPage = rq.get('page') - phaseInfo = self.getAppyPhases(page=currentPage) - nextPage, show = self.getNextPage(phaseInfo, currentPage) - if nextPage: + phaseInfo = self.getAppyPhases(currentOnly=True, layoutType='edit') + pageName, pageInfo = self.getNextPage(phaseInfo, rq['page']) + if pageName: # Return to the edit or view page? - if show != 'view': - rq.set('page', nextPage) + if pageInfo['showOnEdit']: + rq.set('page', pageName) return obj.skyn.edit(obj) else: - return self.goto(obj.getUrl(page=nextPage)) + return self.goto(obj.getUrl(page=pageName)) else: obj.plone_utils.addPortalMessage(msg) return self.goto(obj.getUrl()) @@ -390,13 +387,13 @@ class BaseMixin: className = className or self.__class__.__name__ return self.getProductConfig().attributes[className] - def getGroupedAppyTypes(self, layoutType, page): + def getGroupedAppyTypes(self, layoutType, pageName): '''Returns the fields sorted by group. For every field, the appyType (dict version) is given.''' res = [] groups = {} # The already encountered groups for appyType in self.getAllAppyTypes(): - if appyType.page != page: continue + if appyType.page.name != pageName: continue if not appyType.isShowable(self, layoutType): continue if not appyType.group: res.append(appyType.__dict__) @@ -408,12 +405,12 @@ class BaseMixin: GroupDescr.addWidget(groupDescr, appyType.__dict__) return res - def getAppyTypes(self, layoutType, page): + def getAppyTypes(self, layoutType, pageName): '''Returns the list of appyTypes that belong to a given p_page, for a given p_layoutType.''' res = [] for appyType in self.getAllAppyTypes(): - if appyType.page != page: continue + if appyType.page.name != pageName: continue if not appyType.isShowable(self, layoutType): continue res.append(appyType) return res @@ -478,23 +475,23 @@ class BaseMixin: res.append(transition) return res - def getAppyPhases(self, currentOnly=False, page=None): + def getAppyPhases(self, currentOnly=False, layoutType='view'): '''Gets the list of phases that are defined for this content type. If - p_currentOnly is True, the search is limited to the current phase. - If p_page is not None, the search is limited to the phase - where p_page lies.''' + p_currentOnly is True, the search is limited to the phase where the + current page (as defined in the request) lies.''' # Get the list of phases res = [] # Ordered list of phases phases = {} # Dict of phases for appyType in self.getAllAppyTypes(): - if appyType.phase not in phases: - states = self.getAppyStates(appyType.phase) - phase = PhaseDescr(appyType.phase, states, self) + typePhase = appyType.page.phase + if typePhase not in phases: + states = self.getAppyStates(typePhase) + phase = PhaseDescr(typePhase, states, self) res.append(phase.__dict__) - phases[appyType.phase] = phase + phases[typePhase] = phase else: - phase = phases[appyType.phase] - phase.addPage(appyType, self) + phase = phases[typePhase] + phase.addPage(appyType, self, layoutType) # Remove phases that have no visible page for i in range(len(res)-1, -1, -1): if not res[i]['pages']: @@ -504,15 +501,19 @@ class BaseMixin: for ph in phases.itervalues(): ph.computeStatus(res) ph.totalNbOfPhases = len(res) - # Restrict the result if we must not produce the whole list of phases + # Restrict the result to the current phase if required if currentOnly: - for phaseInfo in res: - if phaseInfo['phaseStatus'] == 'Current': - return phaseInfo - elif page: + rq = self.REQUEST + page = rq.get('page', 'main') for phaseInfo in res: if page in phaseInfo['pages']: return phaseInfo + # If I am here, it means that the page as defined in the request, + # or 'main' by default, is not existing nor visible in any phase. + # In this case I set the page as being the first visible page in + # the first visible phase. + rq.set('page', res[0]['pages'][0]) + return res[0] else: return res @@ -522,15 +523,15 @@ class BaseMixin: if pageIndex > 0: # We stay on the same phase, previous page res = phase['pages'][pageIndex-1] - show = phase['pageShows'][res] - return res, show + resInfo = phase['pagesInfo'][res] + return res, resInfo else: if phase['previousPhase']: # We go to the last page of previous phase previousPhase = phase['previousPhase'] res = previousPhase['pages'][-1] - show = previousPhase['pageShows'][res] - return res, show + resInfo = previousPhase['pagesInfo'][res] + return res, resInfo else: return None, None @@ -540,15 +541,15 @@ class BaseMixin: if pageIndex < len(phase['pages'])-1: # We stay on the same phase, next page res = phase['pages'][pageIndex+1] - show = phase['pageShows'][res] - return res, show + resInfo = phase['pagesInfo'][res] + return res, resInfo else: if phase['nextPhase']: # We go to the first page of next phase nextPhase = phase['nextPhase'] res = nextPhase['pages'][0] - show = nextPhase['pageShows'][res] - return res, show + resInfo = nextPhase['pagesInfo'][res] + return res, resInfo else: return None, None diff --git a/gen/plone25/model.py b/gen/plone25/model.py index ae3c125..77e4a0a 100644 --- a/gen/plone25/model.py +++ b/gen/plone25/model.py @@ -19,10 +19,10 @@ class ModelClass: _appy_attributes = [] # We need to keep track of attributes order. # When creating a new instance of a ModelClass, the following attributes # must not be given in the constructor (they are computed attributes). - _appy_notinit = ('id', 'type', 'pythonType', 'slaves', 'phase', 'pageShow', - 'isSelect', 'hasLabel', 'hasDescr', 'hasHelp', - 'master_css', 'layouts', 'required', 'filterable', - 'validable', 'backd', 'isBack', 'sync') + _appy_notinit = ('id', 'type', 'pythonType', 'slaves', 'isSelect', + 'hasLabel', 'hasDescr', 'hasHelp', 'master_css', + 'layouts', 'required', 'filterable', 'validable', 'backd', + 'isBack', 'sync', 'pageName') @classmethod def _appy_addField(klass, fieldName, fieldType, classDescr): @@ -55,6 +55,8 @@ class ModelClass: attrValue = 'Selection("%s")' % attrValue.methodName elif isinstance(attrValue, Group): attrValue = 'Group("%s")' % attrValue.name + elif isinstance(attrValue, Page): + attrValue = 'Page("%s")' % attrValue.name elif type(attrValue) == types.FunctionType: attrValue = '%sWrapper.%s'% (klass.__name__, attrValue.__name__) typeArgs += '%s=%s,' % (attrName, attrValue) diff --git a/gen/plone25/skin/edit.pt b/gen/plone25/skin/edit.pt index c509f47..b6b6a63 100644 --- a/gen/plone25/skin/edit.pt +++ b/gen/plone25/skin/edit.pt @@ -6,12 +6,12 @@ tool contextObj/getTool; appFolder tool/getAppFolder; appName appFolder/getId; + phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='edit'); + phase phaseInfo/name; page request/page|python:'main'; cssAndJs python: contextObj.getCssAndJs(layoutType, page); css python: cssAndJs[0]; - js python: cssAndJs[1]; - phaseInfo python: contextObj.getAppyPhases(page=page); - phase phaseInfo/name"> + js python: cssAndJs[1]"> -
- - - - - - - - - - - + isEdit python: layoutType == 'edit'; + pageInfo python: phaseInfo['pagesInfo'][page]"> +
+ + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - + + + + + + + + + + + diff --git a/gen/plone25/skin/portlet.pt b/gen/plone25/skin/portlet.pt index 4d60e56..f0099c3 100644 --- a/gen/plone25/skin/portlet.pt +++ b/gen/plone25/skin/portlet.pt @@ -160,7 +160,7 @@ + tal:condition="python: member.has_permission('Modify portal content', contextObj) and phase['pagesInfo'][pageName]['showOnEdit']"/> @@ -180,7 +180,7 @@ + tal:condition="python: member.has_permission('Modify portal content', contextObj) and phase['pagesInfo'][aPage]['showOnEdit']"/> diff --git a/gen/plone25/skin/view.pt b/gen/plone25/skin/view.pt index c42cefc..1812b15 100644 --- a/gen/plone25/skin/view.pt +++ b/gen/plone25/skin/view.pt @@ -15,17 +15,17 @@ Fill main slot of Plone main_template diff --git a/gen/plone25/skin/widgets/ref.pt b/gen/plone25/skin/widgets/ref.pt index 7113514..df96fd1 100644 --- a/gen/plone25/skin/widgets/ref.pt +++ b/gen/plone25/skin/widgets/ref.pt @@ -9,9 +9,9 @@ on a forward reference, the "nav" parameter is added to the URL for allowing to navigate from one object to the next/previous on skyn/view. @@ -41,7 +41,7 @@ Edit the element - @@ -63,7 +63,7 @@ through a reference widget. Indeed, If field was declared as "addable", we must provide an icon for creating a new linked object (at least if multiplicities allow it). _ - - a appy.gen.Page instance for a more detailed page description. - This method returns a normalized tuple containing page-related - information.''' - if isinstance(pageOrName, basestring): - res = pageOrName.rsplit('_', 1) - if len(res) == 1: - res.append('main') - res.append(True) - else: - res = [pageOrName.name, pageOrName.phase, pageOrName.show] - return res - class PhaseDescr(Descr): def __init__(self, name, states, obj): self.name = name self.states = states self.obj = obj self.phaseStatus = None - self.pages = [] # The list of pages in this phase - # Below, a dict of "show" values (True, False or 'view') for every page - # in self.pages. - self.pageShows = {} + # The list of names of pages in this phase + self.pages = [] + # The list of hidden pages in this phase + self.hiddenPages = [] + # The dict below stores infor about every page listed in self.pages. + self.pagesInfo = {} self.totalNbOfPhases = None # The following attributes allows to browse, from a given page, to the # last page of the previous phase and to the first page of the following @@ -93,13 +77,23 @@ class PhaseDescr(Descr): self.previousPhase = None self.nextPhase = None - def addPage(self, appyType, obj): - toAdd = appyType.page - if toAdd not in self.pages: - showValue = appyType.showPage(obj) - if showValue: - self.pages.append(toAdd) - self.pageShows[toAdd] = showValue + def addPage(self, appyType, obj, layoutType): + '''Adds page-related information in the phase.''' + # If the page is already there, we have nothing more to do. + if (appyType.page.name in self.pages) or \ + (appyType.page.name in self.hiddenPages): return + # Add the page only if it must be shown. + if appyType.page.isShowable(obj, layoutType): + # The page must be added. + self.pages.append(appyType.page.name) + # Create the dict about page information and add it in self.pageInfo + pageInfo = {'page': appyType.page, + 'showOnView': appyType.page.isShowable(obj, 'view'), + 'showOnEdit': appyType.page.isShowable(obj, 'edit')} + pageInfo.update(appyType.page.getInfo(obj, layoutType)) + self.pagesInfo[appyType.page.name] = pageInfo + else: + self.hiddenPages.append(appyType.page.name) def computeStatus(self, allPhases): '''Compute status of whole phase based on individual status of states