[gen] Modified Ajax system to be able to ajax-refresh a single row within query results or ref tied object (ongoing work).
This commit is contained in:
		
							parent
							
								
									f047242d81
								
							
						
					
					
						commit
						1bd6cf29a3
					
				
					 11 changed files with 417 additions and 302 deletions
				
			
		| 
						 | 
				
			
			@ -97,6 +97,64 @@ class Field:
 | 
			
		|||
            context[k] = ctx[k]
 | 
			
		||||
        return self.pxRender(context).encode('utf-8')
 | 
			
		||||
 | 
			
		||||
    # Show the field content for some object on a list of results
 | 
			
		||||
    pxRenderAsResult = Px('''
 | 
			
		||||
     <!-- Title -->
 | 
			
		||||
     <x if="field.name == 'title'"
 | 
			
		||||
        var2="navInfo='search.%s.%s.%d.%d' % (className, searchName, \
 | 
			
		||||
                                      startNumber+currentNumber, totalNumber)">
 | 
			
		||||
      <x if="mayView"
 | 
			
		||||
         var2="titleMode=inPopup and 'select' or 'link';
 | 
			
		||||
               pageName=zobj.getDefaultViewPage();
 | 
			
		||||
               selectJs=inPopup and 'onSelectObject(%s,%s,%s)' % (q(cbId), \
 | 
			
		||||
                          q(rootHookId), q(uiSearch.initiator.url))">
 | 
			
		||||
       <x var="sup=zobj.getSupTitle(navInfo)" if="sup">::sup</x>
 | 
			
		||||
       <x>::zobj.getListTitle(mode=titleMode, nav=navInfo, target=target, \
 | 
			
		||||
          page=pageName, inPopup=inPopup, selectJs=selectJs, highlight=True)</x>
 | 
			
		||||
       <span style=":showSubTitles and 'display:inline' or 'display:none'"
 | 
			
		||||
             name="subTitle" var="sub=zobj.getSubTitle()"
 | 
			
		||||
             if="sub">::zobj.highlight(sub)</span>
 | 
			
		||||
 | 
			
		||||
       <!-- Actions -->
 | 
			
		||||
       <div if="not inPopup and uiSearch.showActions and zobj.mayAct()"
 | 
			
		||||
            class="objectActions" style=":'display:%s' % uiSearch.showActions"
 | 
			
		||||
            var2="layoutType='buttons'" >
 | 
			
		||||
        <!-- Edit -->
 | 
			
		||||
        <a if="zobj.mayEdit()"
 | 
			
		||||
           var2="linkInPopup=inPopup or (target.target != '_self')"
 | 
			
		||||
           target=":target.target" onclick=":target.openPopup"
 | 
			
		||||
           href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \
 | 
			
		||||
                              nav=navInfo, inPopup=linkInPopup)">
 | 
			
		||||
         <img src=":url('edit')" title=":_('object_edit')"/>
 | 
			
		||||
        </a>
 | 
			
		||||
        <!-- Delete -->
 | 
			
		||||
        <img if="zobj.mayDelete()" class="clickable" src=":url('delete')"
 | 
			
		||||
             title=":_('object_delete')"
 | 
			
		||||
             onClick=":'onDeleteObject(%s)' % q(zobj.id)"/>
 | 
			
		||||
        <!-- Workflow transitions -->
 | 
			
		||||
        <x if="zobj.showTransitions('result')"
 | 
			
		||||
           var2="targetObj=zobj">:targetObj.appy().pxTransitions</x>
 | 
			
		||||
        <!-- Fields (actions) defined with layout "buttons" -->
 | 
			
		||||
        <x if="not inPopup"
 | 
			
		||||
           var2="fields=zobj.getAppyTypes('buttons', 'main');
 | 
			
		||||
                 layoutType='cell'">
 | 
			
		||||
         <!-- Call pxCell and not pxRender to avoid having a table -->
 | 
			
		||||
         <x for="field in fields"
 | 
			
		||||
            var2="name=field.name; smallButtons=True">:field.pxCell</x>
 | 
			
		||||
        </x>
 | 
			
		||||
       </div>
 | 
			
		||||
      </x>
 | 
			
		||||
      <x if="not mayView">
 | 
			
		||||
       <img src=":url('fake')" style="margin-right: 5px"/>
 | 
			
		||||
       <x>:_('unauthorized')</x>
 | 
			
		||||
      </x>
 | 
			
		||||
     </x>
 | 
			
		||||
     <!-- Any other field -->
 | 
			
		||||
     <x if="(field.name != 'title') and mayView">
 | 
			
		||||
      <x var="layoutType='cell'; innerRef=True"
 | 
			
		||||
         if="field.isShowable(zobj, 'result')">:field.pxRender</x>
 | 
			
		||||
     </x>''')
 | 
			
		||||
 | 
			
		||||
    # Displays a field label
 | 
			
		||||
    pxLabel = Px('''<label if="field.hasLabel and field.renderLabel"
 | 
			
		||||
     lfor=":field.name">::_('label', field=field)</label>''')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,7 @@ class Ref(Field):
 | 
			
		|||
    # (edit, delete, etc).
 | 
			
		||||
    pxObjectActions = Px('''
 | 
			
		||||
     <div if="field.showActions" class="objectActions"
 | 
			
		||||
          style=":'display:%s' % field.showActions">
 | 
			
		||||
          style=":'display:%s' % field.showActions" var2="layoutType='buttons'">
 | 
			
		||||
      <!-- Arrows for moving objects up or down -->
 | 
			
		||||
      <x if="(totalNumber >1) and changeOrder and not inPickList \
 | 
			
		||||
            and not inMenu"
 | 
			
		||||
| 
						 | 
				
			
			@ -132,7 +132,7 @@ class Ref(Field):
 | 
			
		|||
                      q(field.name), q(tiedUid))"/>
 | 
			
		||||
      <!-- Workflow transitions -->
 | 
			
		||||
      <x if="tied.o.showTransitions('result')"
 | 
			
		||||
         var2="targetObj=tied.o; buttonsMode='small'">:tied.pxTransitions</x>
 | 
			
		||||
         var2="targetObj=tied.o">:tied.pxTransitions</x>
 | 
			
		||||
      <!-- Fields (actions) defined with layout "buttons" -->
 | 
			
		||||
      <x if="not inPopup"
 | 
			
		||||
         var2="fields=tied.o.getAppyTypes('buttons', 'main');
 | 
			
		||||
| 
						 | 
				
			
			@ -279,7 +279,8 @@ class Ref(Field):
 | 
			
		|||
          class=":loop.tied.odd and 'even' or 'odd'"
 | 
			
		||||
          var2="tiedUid=tied.o.id;
 | 
			
		||||
                objectIndex=field.getIndexOf(zobj, tiedUid)|None;
 | 
			
		||||
                mayView=tied.o.mayView()">
 | 
			
		||||
                mayView=tied.o.mayView()"
 | 
			
		||||
          id=":'%s_%s' % (ajaxHookId, tiedUid)">
 | 
			
		||||
       <td if="not inPickList and numbered">:field.pxNumber</td>
 | 
			
		||||
       <td if="checkboxes" class="cbCell">
 | 
			
		||||
        <input if="mayView" type="checkbox" name=":ajaxHookId" checked="checked"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										203
									
								
								fields/search.py
									
										
									
									
									
								
							
							
						
						
									
										203
									
								
								fields/search.py
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -146,14 +146,6 @@ class Search:
 | 
			
		|||
            return gutils.callMethod(tool, self.show, klass=klass)
 | 
			
		||||
        return self.show
 | 
			
		||||
 | 
			
		||||
    def getCbJsInit(self, hookId):
 | 
			
		||||
        '''Returns the code that creates JS data structures for storing the
 | 
			
		||||
           status of checkboxes for every result of this search.'''
 | 
			
		||||
        default = self.checkboxesDefault and 'unchecked' or 'checked'
 | 
			
		||||
        return '''var node=document.getElementById('%s');
 | 
			
		||||
                  node['_appy_objs_cbs'] = {};
 | 
			
		||||
                  node['_appy_objs_sem'] = '%s';''' % (hookId, default)
 | 
			
		||||
 | 
			
		||||
    def getSessionKey(self, className, full=True):
 | 
			
		||||
        '''Returns the name of the key, in the session, where results for this
 | 
			
		||||
           search are stored when relevant. If p_full is False, only the suffix
 | 
			
		||||
| 
						 | 
				
			
			@ -166,7 +158,7 @@ class Search:
 | 
			
		|||
class UiSearch:
 | 
			
		||||
    '''Instances of this class are generated on-the-fly for manipulating a
 | 
			
		||||
       Search from the User Interface.'''
 | 
			
		||||
    # PX for rendering a search.
 | 
			
		||||
    # Rendering a search
 | 
			
		||||
    pxView = Px('''
 | 
			
		||||
     <div class="portletSearch">
 | 
			
		||||
      <a href=":'%s?className=%s&search=%s' % \
 | 
			
		||||
| 
						 | 
				
			
			@ -175,11 +167,169 @@ class UiSearch:
 | 
			
		|||
         title=":search.translatedDescr">:search.translated</a>
 | 
			
		||||
     </div>''')
 | 
			
		||||
 | 
			
		||||
    # Search results, as a list (used by pxResult below)
 | 
			
		||||
    pxResultList = Px('''
 | 
			
		||||
     <x var="showHeaders=showHeaders|True;
 | 
			
		||||
             checkboxes=uiSearch.search.checkboxes;
 | 
			
		||||
             checkboxesId=rootHookId + '_objs';
 | 
			
		||||
             cbShown=uiSearch.showCheckboxes();
 | 
			
		||||
             cbDisplay=cbShown and 'table-cell' or 'none'">
 | 
			
		||||
      <script>:uiSearch.getAjaxData(ajaxHookId, ztool, popup=inPopup, \
 | 
			
		||||
               checkboxes=checkboxes, checkboxesId=checkboxesId, \
 | 
			
		||||
               cbDisplay=cbDisplay, startNumber=startNumber, \
 | 
			
		||||
               totalNumber=totalNumber)</script>
 | 
			
		||||
      <table class="list" width="100%">
 | 
			
		||||
       <!-- Headers, with filters and sort arrows -->
 | 
			
		||||
       <tr if="showHeaders">
 | 
			
		||||
        <th if="checkboxes" class="cbCell" style=":'display:%s' % cbDisplay">
 | 
			
		||||
         <img src=":url('checkall')" class="clickable"
 | 
			
		||||
              title=":_('check_uncheck')"
 | 
			
		||||
              onclick=":'toggleAllCbs(%s)' % q(checkboxesId)"/>
 | 
			
		||||
        </th>
 | 
			
		||||
        <th for="column in columns"
 | 
			
		||||
            var2="field=column.field;
 | 
			
		||||
                  sortable=field.isSortable(usage='search');
 | 
			
		||||
                  filterable=field.filterable"
 | 
			
		||||
            width=":column.width" align=":column.align">
 | 
			
		||||
         <x>::ztool.truncateText(_(field.labelId))</x>
 | 
			
		||||
         <x if="(totalNumber > 1) or filterValue">:tool.pxSortAndFilter</x>
 | 
			
		||||
         <x>:tool.pxShowDetails</x>
 | 
			
		||||
        </th>
 | 
			
		||||
       </tr>
 | 
			
		||||
 | 
			
		||||
       <!-- Results -->
 | 
			
		||||
       <tr if="not zobjects">
 | 
			
		||||
        <td colspan=":len(columns)+1">:_('query_no_result')</td>
 | 
			
		||||
       </tr>
 | 
			
		||||
       <x for="zobj in zobjects"
 | 
			
		||||
          var2="@currentNumber=currentNumber + 1;
 | 
			
		||||
               rowCss=loop.zobj.odd and 'even' or 'odd'">:obj.pxViewAsResult</x>
 | 
			
		||||
      </table>
 | 
			
		||||
      <!-- The button for selecting objects and closing the popup -->
 | 
			
		||||
      <div if="inPopup and cbShown" align=":dleft">
 | 
			
		||||
       <input type="button"
 | 
			
		||||
              var="label=_('object_link_many'); css=ztool.getButtonCss(label)"
 | 
			
		||||
              value=":label" class=":css" style=":url('linkMany', bg=True)"
 | 
			
		||||
              onclick=":'onSelectObjects(%s,%s,%s,%s,%s,%s,%s)' % \
 | 
			
		||||
               (q(rootHookId), q(uiSearch.initiator.url), \
 | 
			
		||||
                q(uiSearch.initiatorMode), q(sortKey), q(sortOrder), \
 | 
			
		||||
                q(filterKey), q(filterValue))"/>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- Init checkboxes if present -->
 | 
			
		||||
      <script if="checkboxes">:'initCbs(%s)' % q(checkboxesId)</script>
 | 
			
		||||
      <script>:'initFocus(%s)' % q(ajaxHookId)</script></x>''')
 | 
			
		||||
 | 
			
		||||
    # Search results, as a grid (used by pxResult below)
 | 
			
		||||
    pxResultGrid = Px('''
 | 
			
		||||
     <table width="100%"
 | 
			
		||||
            var="modeElems=resultMode.split('_');
 | 
			
		||||
                 cols=(len(modeElems)==2) and int(modeElems[1]) or 4;
 | 
			
		||||
                 rows=ztool.splitList(zobjects, cols)">
 | 
			
		||||
      <tr for="row in rows" valign="middle">
 | 
			
		||||
       <td for="zobj in row" width=":'%d%%' % (100/cols)" align="center"
 | 
			
		||||
           style="padding-top: 25px"
 | 
			
		||||
           var2="obj=zobj.appy(); mayView=zobj.mayView()">
 | 
			
		||||
        <x var="@currentNumber=currentNumber + 1"
 | 
			
		||||
           for="column in columns"
 | 
			
		||||
           var2="field=column.field">:field.pxRenderAsResult</x>
 | 
			
		||||
       </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
     </table>''')
 | 
			
		||||
 | 
			
		||||
    # Render search results
 | 
			
		||||
    pxResult = Px('''
 | 
			
		||||
     <div var="ajaxHookId='queryResult';
 | 
			
		||||
               className=req['className'];
 | 
			
		||||
               searchName=req.get('search', '');
 | 
			
		||||
               uiSearch=uiSearch|ztool.getSearch(className,searchName,ui=True);
 | 
			
		||||
               rootHookId=uiSearch.getRootHookId();
 | 
			
		||||
               refInfo=ztool.getRefInfo();
 | 
			
		||||
               refObject=refInfo[0];
 | 
			
		||||
               refField=refInfo[1];
 | 
			
		||||
               refUrlPart=refObject and ('&ref=%s:%s' % (refObject.id, \
 | 
			
		||||
                                                             refField)) or '';
 | 
			
		||||
               startNumber=req.get('startNumber', '0');
 | 
			
		||||
               startNumber=int(startNumber);
 | 
			
		||||
               sortKey=req.get('sortKey', '');
 | 
			
		||||
               sortOrder=req.get('sortOrder', 'asc');
 | 
			
		||||
               filterKey=req.get('filterKey', '');
 | 
			
		||||
               filterValue=req.get('filterValue', '');
 | 
			
		||||
               queryResult=ztool.executeQuery(className, \
 | 
			
		||||
                   search=uiSearch.search, startNumber=startNumber, \
 | 
			
		||||
                   remember=True, sortBy=sortKey, sortOrder=sortOrder, \
 | 
			
		||||
                   filterKey=filterKey, filterValue=filterValue, \
 | 
			
		||||
                   refObject=refObject, refField=refField);
 | 
			
		||||
               zobjects=queryResult.objects;
 | 
			
		||||
               totalNumber=queryResult.totalNumber;
 | 
			
		||||
               batchSize=queryResult.batchSize;
 | 
			
		||||
               batchNumber=len(zobjects);
 | 
			
		||||
               navBaseCall='askQueryResult(%s,%s,%s,%s,%s,**v**)' % \
 | 
			
		||||
                 (q(ajaxHookId), q(ztool.absolute_url()), q(className), \
 | 
			
		||||
                  q(searchName),int(inPopup));
 | 
			
		||||
               showNewSearch=showNewSearch|True;
 | 
			
		||||
               newSearchUrl='%s/search?className=%s%s' % \
 | 
			
		||||
                   (ztool.absolute_url(), className, refUrlPart);
 | 
			
		||||
               showSubTitles=req.get('showSubTitles', 'true') == 'true';
 | 
			
		||||
               klass=ztool.getAppyClass(className);
 | 
			
		||||
               resultMode=uiSearch.getResultMode(klass);
 | 
			
		||||
               target=ztool.getLinksTargetInfo(klass)"
 | 
			
		||||
          id=":ajaxHookId">
 | 
			
		||||
 | 
			
		||||
      <x if="zobjects or filterValue">
 | 
			
		||||
       <!-- Display here POD templates if required -->
 | 
			
		||||
       <table var="fields=ztool.getResultPodFields(className);
 | 
			
		||||
                   layoutType='view'"
 | 
			
		||||
              if="not inPopup and zobjects and fields" align=":dright">
 | 
			
		||||
        <tr>
 | 
			
		||||
         <td var="zobj=zobjects[0]; obj=zobj.appy()"
 | 
			
		||||
             for="field in fields"
 | 
			
		||||
             class=":not loop.field.last and 'pod' or ''">:field.pxRender</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
       </table>
 | 
			
		||||
 | 
			
		||||
       <!-- The title of the search -->
 | 
			
		||||
       <p if="not inPopup">
 | 
			
		||||
       <x>::uiSearch.translated</x> (<span class="discreet">:totalNumber</span>)
 | 
			
		||||
        <x if="showNewSearch and (searchName == 'customSearch')"> —
 | 
			
		||||
          <i><a href=":newSearchUrl">:_('search_new')</a></i>
 | 
			
		||||
        </x>
 | 
			
		||||
       </p>
 | 
			
		||||
       <table width="100%">
 | 
			
		||||
        <tr valign="top">
 | 
			
		||||
         <!-- Search description -->
 | 
			
		||||
         <td if="uiSearch.translatedDescr">
 | 
			
		||||
          <span class="discreet">:uiSearch.translatedDescr</span><br/>
 | 
			
		||||
         </td>
 | 
			
		||||
         <!-- (Top) navigation -->
 | 
			
		||||
         <td align=":dright" width="150px">:tool.pxNavigate</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
       </table>
 | 
			
		||||
 | 
			
		||||
       <!-- Results, as a list or grid -->
 | 
			
		||||
       <x var="columnLayouts=ztool.getResultColumnsLayouts(className, refInfo);
 | 
			
		||||
               columns=ztool.getColumnsSpecifiers(className,columnLayouts,dir);
 | 
			
		||||
               currentNumber=0">
 | 
			
		||||
        <x if="resultMode == 'list'">:uiSearch.pxResultList</x>
 | 
			
		||||
        <x if="resultMode != 'list'">:uiSearch.pxResultGrid</x>
 | 
			
		||||
       </x>
 | 
			
		||||
 | 
			
		||||
       <!-- (Bottom) navigation -->
 | 
			
		||||
       <x>:tool.pxNavigate</x>
 | 
			
		||||
      </x>
 | 
			
		||||
 | 
			
		||||
      <x if="not zobjects and not filterValue">
 | 
			
		||||
       <x>:_('query_no_result')</x>
 | 
			
		||||
       <x if="showNewSearch and (searchName == 'customSearch')"><br/>
 | 
			
		||||
        <i class="discreet"><a href=":newSearchUrl">:_('search_new')</a></i></x>
 | 
			
		||||
      </x>
 | 
			
		||||
    </div>''')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, search, className, tool):
 | 
			
		||||
        self.search = search
 | 
			
		||||
        self.name = search.name
 | 
			
		||||
        self.type = 'search'
 | 
			
		||||
        self.colspan = search.colspan
 | 
			
		||||
        self.className = className
 | 
			
		||||
        # Property "display" of the div tag containing actions for every search
 | 
			
		||||
        # result.
 | 
			
		||||
        self.showActions = search.showActions
 | 
			
		||||
| 
						 | 
				
			
			@ -188,7 +338,7 @@ class UiSearch:
 | 
			
		|||
            self.translated = search.translated
 | 
			
		||||
            self.translatedDescr = search.translatedDescr
 | 
			
		||||
        else:
 | 
			
		||||
            # The label may be specific in some special cases.
 | 
			
		||||
            # The label may be specific in some special cases
 | 
			
		||||
            labelDescr = ''
 | 
			
		||||
            if search.name == 'allSearch':
 | 
			
		||||
                label = '%s_plural' % className
 | 
			
		||||
| 
						 | 
				
			
			@ -221,6 +371,11 @@ class UiSearch:
 | 
			
		|||
           Else, simply return the name of the search.'''
 | 
			
		||||
        return getattr(self, 'initiatorHook', self.name)
 | 
			
		||||
 | 
			
		||||
    def getResultMode(self, klass):
 | 
			
		||||
        '''Must we show, on pxResult, instances of p_klass as a list or
 | 
			
		||||
           as a grid?'''
 | 
			
		||||
        return getattr(klass, 'resultMode', 'list')
 | 
			
		||||
 | 
			
		||||
    def showCheckboxes(self):
 | 
			
		||||
        '''If checkboxes are enabled for this search (and if an initiator field
 | 
			
		||||
           is there), they must be visible only if the initiator field is
 | 
			
		||||
| 
						 | 
				
			
			@ -229,4 +384,32 @@ class UiSearch:
 | 
			
		|||
           the DOM because they store object UIDs.'''
 | 
			
		||||
        if not self.search.checkboxes: return
 | 
			
		||||
        return not self.initiator or self.initiatorField.isMultiValued()
 | 
			
		||||
 | 
			
		||||
    def getCbJsInit(self, hookId):
 | 
			
		||||
        '''Returns the code that creates JS data structures for storing the
 | 
			
		||||
           status of checkboxes for every result of this search.'''
 | 
			
		||||
        default = self.search.checkboxesDefault and 'unchecked' or 'checked'
 | 
			
		||||
        return '''var node=document.getElementById('%s');
 | 
			
		||||
                  node['_appy_objs_cbs'] = {};
 | 
			
		||||
                  node['_appy_objs_sem'] = '%s';''' % (hookId, default)
 | 
			
		||||
 | 
			
		||||
    def getAjaxData(self, hook, ztool, **params):
 | 
			
		||||
        '''Initializes an AjaxData object on the DOM node corresponding to
 | 
			
		||||
           p_hook = the whole search result.'''
 | 
			
		||||
        # Complete params with default parameters
 | 
			
		||||
        params['className'] = self.className
 | 
			
		||||
        params['searchName'] = self.name
 | 
			
		||||
        params = sutils.getStringDict(params)
 | 
			
		||||
        return "document.getElementById('%s')['ajax']=new AjaxData('%s', " \
 | 
			
		||||
               "'pxResult', %s, null, '%s')" % \
 | 
			
		||||
               (hook, hook, params, ztool.absolute_url())
 | 
			
		||||
 | 
			
		||||
    def getAjaxDataRow(self, zobj, parentHook, **params):
 | 
			
		||||
        '''Initializes an AjaxData object on the DOM node corresponding to
 | 
			
		||||
           p_hook = a row within the list of results.'''
 | 
			
		||||
        hook = zobj.id
 | 
			
		||||
        return "document.getElementById('%s')['ajax']=new AjaxData('%s', " \
 | 
			
		||||
               "'pxViewAsResultFromAjax',%s,'%s','%s')" % \
 | 
			
		||||
               (hook, hook, sutils.getStringDict(params), parentHook,
 | 
			
		||||
                zobj.absolute_url())
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -361,7 +361,7 @@ class Transition:
 | 
			
		|||
           "onTrigger" on the workflow class by convention. The common action is
 | 
			
		||||
           executed before the transition-specific action (if any).'''
 | 
			
		||||
        obj = obj.appy()
 | 
			
		||||
        wf = wf.__instance__ # We need the prototypical instance here.
 | 
			
		||||
        wf = wf.__instance__ # We need the prototypical instance here
 | 
			
		||||
        wf.onTrigger(obj, name, fromState)
 | 
			
		||||
 | 
			
		||||
    def trigger(self, name, obj, wf, comment, doAction=True, doHistory=True,
 | 
			
		||||
| 
						 | 
				
			
			@ -374,11 +374,11 @@ class Transition:
 | 
			
		|||
           the transition is triggered programmatically, and no message is
 | 
			
		||||
           returned to the user. If p_reindex is False, object reindexing will
 | 
			
		||||
           be performed by the calling method.'''
 | 
			
		||||
        # "Triggerability" and security checks.
 | 
			
		||||
        # "Triggerability" and security checks
 | 
			
		||||
        if (name != '_init_') and \
 | 
			
		||||
           not self.isTriggerable(obj, wf, noSecurity=noSecurity):
 | 
			
		||||
            raise Exception('Transition "%s" can\'t be triggered.' % name)
 | 
			
		||||
        # Create the workflow_history dict if it does not exist.
 | 
			
		||||
        # Create the workflow_history dict if it does not exist
 | 
			
		||||
        if not hasattr(obj.aq_base, 'workflow_history'):
 | 
			
		||||
            from persistent.mapping import PersistentMapping
 | 
			
		||||
            obj.workflow_history = PersistentMapping()
 | 
			
		||||
| 
						 | 
				
			
			@ -403,11 +403,11 @@ class Transition:
 | 
			
		|||
            action = None
 | 
			
		||||
            fromState = None
 | 
			
		||||
        else:
 | 
			
		||||
            fromState = obj.State() # Remember the "from" (=start) state.
 | 
			
		||||
            fromState = obj.State() # Remember the "from" (=start) state
 | 
			
		||||
        if not doHistory: comment = '_invisible_'
 | 
			
		||||
        obj.addHistoryEvent(action, review_state=targetStateName,
 | 
			
		||||
                            comments=comment)
 | 
			
		||||
        # Execute the action that is common to all transitions, if defined.
 | 
			
		||||
        # Execute the action that is common to all transitions, if defined
 | 
			
		||||
        if doAction and hasattr(wf, 'onTrigger'):
 | 
			
		||||
            self.executeCommonAction(obj, name, wf, fromState)
 | 
			
		||||
        # Execute the related action if needed
 | 
			
		||||
| 
						 | 
				
			
			@ -417,19 +417,22 @@ class Transition:
 | 
			
		|||
        # (Allowed, State) need to be updated here.
 | 
			
		||||
        if reindex and not obj.isTemporary(): obj.reindex()
 | 
			
		||||
        # Return a message to the user if needed
 | 
			
		||||
        if not doSay or (name == '_init_'): return
 | 
			
		||||
        if not doSay: return
 | 
			
		||||
        if not msg: msg = obj.translate('object_saved')
 | 
			
		||||
        obj.say(msg)
 | 
			
		||||
        return msg
 | 
			
		||||
 | 
			
		||||
    def onUiRequest(self, obj, wf, name, rq):
 | 
			
		||||
        '''Executed when a user wants to trigger this transition from the UI.'''
 | 
			
		||||
        tool = obj.getTool()
 | 
			
		||||
        # Trigger the transition
 | 
			
		||||
        self.trigger(name, obj, wf, rq.get('comment', ''), reindex=False)
 | 
			
		||||
        # Reindex obj if required.
 | 
			
		||||
        msg = self.trigger(name, obj, wf, rq.get('comment', ''), reindex=False)
 | 
			
		||||
        # Reindex obj if required
 | 
			
		||||
        if not obj.isTemporary(): obj.reindex()
 | 
			
		||||
        # If we are called from an Ajax request, return a message
 | 
			
		||||
        if hasattr(rq, 'pxContext') and rq.pxContext['ajax']: return msg
 | 
			
		||||
        # If we are viewing the object and if the logged user looses the
 | 
			
		||||
        # permission to view it, redirect the user to its home page.
 | 
			
		||||
        if msg: obj.say(msg)
 | 
			
		||||
        if not obj.mayView() and \
 | 
			
		||||
           (obj.absolute_url_path() in rq['HTTP_REFERER']):
 | 
			
		||||
            back = tool.getHomePage()
 | 
			
		||||
| 
						 | 
				
			
			@ -462,22 +465,24 @@ class Transition:
 | 
			
		|||
                    if startOk and endOk: return trName
 | 
			
		||||
 | 
			
		||||
class UiTransition:
 | 
			
		||||
    '''Represents a widget that displays a transition.'''
 | 
			
		||||
    '''Represents a widget that displays a transition'''
 | 
			
		||||
    pxView = Px('''
 | 
			
		||||
     <x var="label=transition.title;
 | 
			
		||||
             css=ztool.getButtonCss(label, buttonsMode == 'small')">
 | 
			
		||||
             inButtons=layoutType == 'buttons';
 | 
			
		||||
             css=ztool.getButtonCss(label, inButtons)">
 | 
			
		||||
      <!-- Real button -->
 | 
			
		||||
      <input if="transition.mayTrigger" type="button" class=":css"
 | 
			
		||||
             var="back=inButtons and q(zobj.id) or 'null'"
 | 
			
		||||
             style=":url(transition.icon, bg=True)" value=":label"
 | 
			
		||||
             onclick=":'triggerTransition(%s,%s,%s)' % (q(formId), \
 | 
			
		||||
                        q(transition.name), q(transition.confirm))"/>
 | 
			
		||||
             onclick=":'triggerTransition(%s,%s,%s,%s)' % (q(formId), \
 | 
			
		||||
                        q(transition.name), q(transition.confirm), back)"/>
 | 
			
		||||
 | 
			
		||||
      <!-- Fake button, explaining why the transition can't be triggered -->
 | 
			
		||||
      <input if="not transition.mayTrigger" type="button"
 | 
			
		||||
             class=":'fake %s' % css" style=":url('fake', bg=True)"
 | 
			
		||||
             value=":label" title=":transition.reason"/></x>''')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name, transition, obj, mayTrigger, ):
 | 
			
		||||
    def __init__(self, name, transition, obj, mayTrigger):
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.transition = transition
 | 
			
		||||
        self.type = 'transition'
 | 
			
		||||
| 
						 | 
				
			
			@ -494,7 +499,7 @@ class UiTransition:
 | 
			
		|||
        if not mayTrigger:
 | 
			
		||||
            self.mayTrigger = False
 | 
			
		||||
            self.reason = mayTrigger.msg
 | 
			
		||||
        # Required by the UiGroup.
 | 
			
		||||
        # Required by the UiGroup
 | 
			
		||||
        self.colspan = 1
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -272,12 +272,6 @@ class ToolMixin(BaseMixin):
 | 
			
		|||
                            for key in self.queryParamNames])
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
    def getResultMode(self, className):
 | 
			
		||||
        '''Must we show, on pxQueryResult, instances of p_className as a list or
 | 
			
		||||
           as a grid?'''
 | 
			
		||||
        klass = self.getAppyClass(className)
 | 
			
		||||
        return getattr(klass, 'resultMode', 'list')
 | 
			
		||||
 | 
			
		||||
    def showPortlet(self, obj, layoutType):
 | 
			
		||||
        '''When must the portlet be shown? p_obj and p_layoutType can be None
 | 
			
		||||
           if we are not browing any objet (ie, we are on the home page).'''
 | 
			
		||||
| 
						 | 
				
			
			@ -1298,4 +1292,18 @@ class ToolMixin(BaseMixin):
 | 
			
		|||
            if field: msg = getattr(field, action)(obj.o)
 | 
			
		||||
            else: msg = getattr(obj.o, action)()
 | 
			
		||||
        return msg
 | 
			
		||||
 | 
			
		||||
    def updatePxContextFromRequest(self):
 | 
			
		||||
        '''Takes any user-defined key from the request and put it as a variable
 | 
			
		||||
           on the current PX context.'''
 | 
			
		||||
        req = self.REQUEST
 | 
			
		||||
        ctx = req.pxContext
 | 
			
		||||
        # Get "form" data (get, post) and cookie values
 | 
			
		||||
        for source in (req.form, req.cookies):
 | 
			
		||||
            for k, v in source.iteritems():
 | 
			
		||||
                # Convert v to some Python data when relevant
 | 
			
		||||
                if v in ('True', 'False', 'true', 'false'):
 | 
			
		||||
                    exec 'v = %s' % v.capitalize()
 | 
			
		||||
                elif v.isdigit(): v = int(v)
 | 
			
		||||
                ctx[k] = v
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1019,9 +1019,9 @@ class BaseMixin:
 | 
			
		|||
        wf = self.getWorkflow()
 | 
			
		||||
        # Get the initial workflow state
 | 
			
		||||
        initialState = self.State(name=False)
 | 
			
		||||
        # Create a Transition instance representing the initial transition.
 | 
			
		||||
        # Create a Transition instance representing the initial transition
 | 
			
		||||
        initialTransition = gen.Transition((initialState, initialState))
 | 
			
		||||
        initialTransition.trigger('_init_', self, wf, '')
 | 
			
		||||
        initialTransition.trigger('_init_', self, wf, '', doSay=False)
 | 
			
		||||
 | 
			
		||||
    def getWorkflow(self, name=False, className=None):
 | 
			
		||||
        '''Returns the workflow applicable for p_self (or for any instance of
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,7 +79,7 @@ function XhrObject() { // Wraps a XmlHttpRequest object
 | 
			
		|||
                      replaced by result of executing the Ajax request. */
 | 
			
		||||
  this.onGet = ''; /* The name of a Javascript function to call once we
 | 
			
		||||
                      receive the result. */
 | 
			
		||||
  this.info = {};  /* An associative array for putting anything else. */
 | 
			
		||||
  this.info = {};  /* An associative array for putting anything else */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* When inserting HTML at some DOM node in a page via Ajax, scripts defined in
 | 
			
		||||
| 
						 | 
				
			
			@ -228,11 +228,61 @@ function askAjaxChunk(hook,mode,url,px,params,beforeSend,onGet) {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Object representing all the data required to perform an Ajax request
 | 
			
		||||
function AjaxData(hook, px, params, parentHook, url, mode, beforeSend, onGet) {
 | 
			
		||||
  this.hook = hook;
 | 
			
		||||
  this.mode = mode;
 | 
			
		||||
  if (!mode) this.mode = 'GET';
 | 
			
		||||
  this.url = url;
 | 
			
		||||
  this.px = px;
 | 
			
		||||
  this.params = params;
 | 
			
		||||
  this.beforeSend = beforeSend;
 | 
			
		||||
  this.onGet = onGet;
 | 
			
		||||
  /* If a parentHook is spefified, this AjaxData must be completed with a parent
 | 
			
		||||
     AjaxData instance. */
 | 
			
		||||
  this.parentHook = parentHook;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function askAjax(hook, form) {
 | 
			
		||||
  /* Call askAjaxChunk by getting an AjaxData instance from p_hook and a
 | 
			
		||||
      potential action from p_form). */
 | 
			
		||||
  var d = document.getElementById(hook)['ajax'];
 | 
			
		||||
  // Complete data with a parent data if present
 | 
			
		||||
  if (d['parentHook']) {
 | 
			
		||||
    var parent = document.getElementById(d['parentHook'])['ajax'];
 | 
			
		||||
    for (var key in parent) {
 | 
			
		||||
      if (key == 'params') continue; // Will get a specific treatment herafter
 | 
			
		||||
      if (!d[key]) d[key] = parent[key]; // Override if no value on child
 | 
			
		||||
    }
 | 
			
		||||
    // Merge parameters
 | 
			
		||||
    if (parent.params) {
 | 
			
		||||
      for (var key in parent.params) {
 | 
			
		||||
        if (key in d.params) continue; // Override if not value on child
 | 
			
		||||
        d.params[key] = parent.params[key];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // If a p_form id is given, integrate the form submission in the ajax request
 | 
			
		||||
  if (form) {
 | 
			
		||||
    var f = document.getElementById(form);
 | 
			
		||||
    var mode = 'POST';
 | 
			
		||||
    // Deduce the action from the form action
 | 
			
		||||
    d.params['action'] = _rsplit(f.action, '/', 2)[1];
 | 
			
		||||
    // Get the other params
 | 
			
		||||
    var elems = f.elements;
 | 
			
		||||
    for (var i=0; i < elems.length; i++) {
 | 
			
		||||
      d.params[elems[i].name] = elems[i].value;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  else var mode = d.mode;
 | 
			
		||||
  askAjaxChunk(d.hook,mode,d.url,d.px,d.params,d.beforeSend,d.onGet) }
 | 
			
		||||
 | 
			
		||||
/* The functions below wrap askAjaxChunk for getting specific content through
 | 
			
		||||
   an Ajax request. */
 | 
			
		||||
function askQueryResult(hookId, objectUrl, className, searchName, popup,
 | 
			
		||||
                        startNumber, sortKey, sortOrder, filterKey) {
 | 
			
		||||
  // Sends an Ajax request for getting the result of a query.
 | 
			
		||||
  // Sends an Ajax request for getting the result of a query
 | 
			
		||||
  var params = {'className': className, 'search': searchName,
 | 
			
		||||
                'startNumber': startNumber, 'popup': popup};
 | 
			
		||||
  if (sortKey) params['sortKey'] = sortKey;
 | 
			
		||||
| 
						 | 
				
			
			@ -244,8 +294,8 @@ function askQueryResult(hookId, objectUrl, className, searchName, popup,
 | 
			
		|||
      params['filterValue'] = encodeURIComponent(filterWidget.value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  askAjaxChunk(hookId, 'GET', objectUrl, 'pxQueryResult', params, null,
 | 
			
		||||
               evalInnerScripts);
 | 
			
		||||
  var px = className + ':' + searchName + ':' + 'pxResult';
 | 
			
		||||
  askAjaxChunk(hookId, 'GET', objectUrl, px, params, null, evalInnerScripts);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function askObjectHistory(hookId, objectUrl, maxPerPage, startNumber) {
 | 
			
		||||
| 
						 | 
				
			
			@ -265,7 +315,7 @@ function askRefField(hookId, objectUrl, innerRef, startNumber, action,
 | 
			
		|||
  params[startKey] = startNumber;
 | 
			
		||||
  if (action) params['action'] = action;
 | 
			
		||||
  if (actionParams) {
 | 
			
		||||
    for (key in actionParams) {
 | 
			
		||||
    for (var key in actionParams) {
 | 
			
		||||
      if ((key == 'move') && (typeof actionParams[key] == 'object')) {
 | 
			
		||||
        // Get the new index from an input field
 | 
			
		||||
        var id = actionParams[key].id;
 | 
			
		||||
| 
						 | 
				
			
			@ -343,7 +393,7 @@ function _rsplit(s, delimiter, limit) {
 | 
			
		|||
  var elems = s.split(delimiter);
 | 
			
		||||
  var exc = elems.length - limit;
 | 
			
		||||
  if (exc <= 0) return elems;
 | 
			
		||||
  // Merge back first elements to get p_limit elements.
 | 
			
		||||
  // Merge back first elements to get p_limit elements
 | 
			
		||||
  var head = '';
 | 
			
		||||
  var res = [];
 | 
			
		||||
  for (var i=0; i < elems.length; i++) {
 | 
			
		||||
| 
						 | 
				
			
			@ -582,12 +632,22 @@ function submitAppyForm(button) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Function used for triggering a workflow transition
 | 
			
		||||
function triggerTransition(formId, transitionId, msg) {
 | 
			
		||||
  var theForm = document.getElementById(formId);
 | 
			
		||||
  theForm.transition.value = transitionId;
 | 
			
		||||
  if (!msg) { theForm.submit(); }
 | 
			
		||||
  else { // Ask the user to confirm.
 | 
			
		||||
   askConfirm('form', formId, msg, true);
 | 
			
		||||
function triggerTransition(formId, transitionId, msg, back) {
 | 
			
		||||
  var f = document.getElementById(formId);
 | 
			
		||||
  f.transition.value = transitionId;
 | 
			
		||||
  if (!msg) {
 | 
			
		||||
    /* We must submit the form and either refresh the entire page (back is null)
 | 
			
		||||
       or ajax-refresh a given part only (p_back corresponds to the id of the
 | 
			
		||||
       DOM node to be refreshed. */
 | 
			
		||||
    if (back) askAjax(back, formId);
 | 
			
		||||
    else f.submit();
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    // Ask a confirmation to the user before proceeding
 | 
			
		||||
    if (back) {
 | 
			
		||||
      var js = "askAjax('"+back+"', '"+formId+"');"
 | 
			
		||||
      askConfirm('script', js, msg, true) }
 | 
			
		||||
    else askConfirm('form', formId, msg, true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -100,11 +100,10 @@ class ToolWrapper(AbstractWrapper):
 | 
			
		|||
     <x if="cssJs">
 | 
			
		||||
      <link for="cssFile in cssJs['css']" rel="stylesheet" type="text/css"
 | 
			
		||||
            href=":url(cssFile)"/>
 | 
			
		||||
      <script for="jsFile in cssJs['js']" type="text/javascript"
 | 
			
		||||
              src=":url(jsFile)"></script></x>
 | 
			
		||||
      <script for="jsFile in cssJs['js']" src=":url(jsFile)"></script></x>
 | 
			
		||||
 | 
			
		||||
     <!-- Javascript messages -->
 | 
			
		||||
     <script type="text/javascript">::ztool.getJavascriptMessages()</script>
 | 
			
		||||
     <script>::ztool.getJavascriptMessages()</script>
 | 
			
		||||
 | 
			
		||||
     <!-- Global form for deleting an object -->
 | 
			
		||||
     <form id="deleteForm" method="post" action="do">
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +270,7 @@ class ToolWrapper(AbstractWrapper):
 | 
			
		|||
      </x>
 | 
			
		||||
     </x>''')
 | 
			
		||||
 | 
			
		||||
    # The message that is shown when a user triggers an action.
 | 
			
		||||
    # The message that is shown when a user triggers an action
 | 
			
		||||
    pxMessage = Px('''
 | 
			
		||||
     <div class=":inPopup and 'messagePopup message' or 'message'"
 | 
			
		||||
          style="display:none" id="appyMessage">
 | 
			
		||||
| 
						 | 
				
			
			@ -284,7 +283,7 @@ class ToolWrapper(AbstractWrapper):
 | 
			
		|||
     <script type="text/javascript" var="messages=ztool.consumeMessages()"
 | 
			
		||||
             if="messages">::'showAppyMessage(%s)' % q(messages)</script>''')
 | 
			
		||||
 | 
			
		||||
    # The page footer.
 | 
			
		||||
    # The page footer
 | 
			
		||||
    pxFooter = Px('''
 | 
			
		||||
     <table cellpadding="0" cellspacing="0" width="100%" class="footer">
 | 
			
		||||
      <tr>
 | 
			
		||||
| 
						 | 
				
			
			@ -343,231 +342,6 @@ class ToolWrapper(AbstractWrapper):
 | 
			
		|||
      </tr>
 | 
			
		||||
     </table>''', template=AbstractWrapper.pxTemplate, hook='content')
 | 
			
		||||
 | 
			
		||||
    # Show on query list or grid, the field content for a given object.
 | 
			
		||||
    pxQueryField = Px('''
 | 
			
		||||
     <!-- Title -->
 | 
			
		||||
     <x if="field.name == 'title'">
 | 
			
		||||
      <x if="mayView"
 | 
			
		||||
         var2="navInfo='search.%s.%s.%d.%d' % \
 | 
			
		||||
                (className, searchName, startNumber+currentNumber, totalNumber);
 | 
			
		||||
               titleMode=inPopup and 'select' or 'link';
 | 
			
		||||
               pageName=zobj.getDefaultViewPage();
 | 
			
		||||
               selectJs=inPopup and 'onSelectObject(%s,%s,%s)' % (q(cbId), \
 | 
			
		||||
                          q(rootHookId), q(uiSearch.initiator.url))">
 | 
			
		||||
       <x var="sup=zobj.getSupTitle(navInfo)" if="sup">::sup</x>
 | 
			
		||||
       <x>::zobj.getListTitle(mode=titleMode, nav=navInfo, target=target, \
 | 
			
		||||
          page=pageName, inPopup=inPopup, selectJs=selectJs, highlight=True)</x>
 | 
			
		||||
       <span style=":showSubTitles and 'display:inline' or 'display:none'"
 | 
			
		||||
             name="subTitle" var="sub=zobj.getSubTitle()"
 | 
			
		||||
             if="sub">::zobj.highlight(sub)</span>
 | 
			
		||||
 | 
			
		||||
       <!-- Actions -->
 | 
			
		||||
       <div if="not inPopup and uiSearch.showActions and zobj.mayAct()"
 | 
			
		||||
            class="objectActions" style=":'display:%s' % uiSearch.showActions">
 | 
			
		||||
        <!-- Edit -->
 | 
			
		||||
        <a if="zobj.mayEdit()"
 | 
			
		||||
           var2="navInfo='search.%s.%s.%d.%d' % \
 | 
			
		||||
               (className, searchName, loop.zobj.nb+1+startNumber, totalNumber);
 | 
			
		||||
                 linkInPopup=inPopup or (target.target != '_self')"
 | 
			
		||||
           target=":target.target" onclick=":target.openPopup"
 | 
			
		||||
           href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \
 | 
			
		||||
                              nav=navInfo, inPopup=linkInPopup)">
 | 
			
		||||
         <img src=":url('edit')" title=":_('object_edit')"/>
 | 
			
		||||
        </a>
 | 
			
		||||
        <!-- Delete -->
 | 
			
		||||
        <img if="zobj.mayDelete()" class="clickable" src=":url('delete')"
 | 
			
		||||
             title=":_('object_delete')"
 | 
			
		||||
             onClick=":'onDeleteObject(%s)' % q(zobj.id)"/>
 | 
			
		||||
        <!-- Workflow transitions -->
 | 
			
		||||
        <x if="zobj.showTransitions('result')"
 | 
			
		||||
           var2="targetObj=zobj;
 | 
			
		||||
                 buttonsMode='small'">:targetObj.appy().pxTransitions</x>
 | 
			
		||||
        <!-- Fields (actions) defined with layout "buttons" -->
 | 
			
		||||
        <x if="not inPopup"
 | 
			
		||||
           var2="fields=zobj.getAppyTypes('buttons', 'main');
 | 
			
		||||
                 layoutType='cell'">
 | 
			
		||||
         <!-- Call pxCell and not pxRender to avoid having a table -->
 | 
			
		||||
         <x for="field in fields"
 | 
			
		||||
            var2="name=field.name; smallButtons=True">:field.pxCell</x>
 | 
			
		||||
        </x>
 | 
			
		||||
       </div>
 | 
			
		||||
      </x>
 | 
			
		||||
      <x if="not mayView">
 | 
			
		||||
       <img src=":url('fake')" style="margin-right: 5px"/>
 | 
			
		||||
       <x>:_('unauthorized')</x>
 | 
			
		||||
      </x>
 | 
			
		||||
     </x>
 | 
			
		||||
     <!-- Any other field -->
 | 
			
		||||
     <x if="(field.name != 'title') and mayView">
 | 
			
		||||
      <x var="layoutType='cell'; innerRef=True"
 | 
			
		||||
         if="field.isShowable(zobj, 'result')">:field.pxRender</x>
 | 
			
		||||
     </x>''')
 | 
			
		||||
 | 
			
		||||
    # Show query results as a list.
 | 
			
		||||
    pxQueryResultList = Px('''
 | 
			
		||||
     <x var="showHeaders=showHeaders|True;
 | 
			
		||||
             checkboxes=uiSearch.search.checkboxes;
 | 
			
		||||
             checkboxesId=rootHookId + '_objs';
 | 
			
		||||
             cbShown=uiSearch.showCheckboxes();
 | 
			
		||||
             cbDisplay=cbShown and 'display:table-cell' or 'display:none'">
 | 
			
		||||
      <table class="list" width="100%">
 | 
			
		||||
       <!-- Headers, with filters and sort arrows -->
 | 
			
		||||
       <tr if="showHeaders">
 | 
			
		||||
        <th if="checkboxes" class="cbCell" style=":cbDisplay">
 | 
			
		||||
         <img src=":url('checkall')" class="clickable"
 | 
			
		||||
              title=":_('check_uncheck')"
 | 
			
		||||
              onclick=":'toggleAllCbs(%s)' % q(checkboxesId)"/>
 | 
			
		||||
        </th>
 | 
			
		||||
        <th for="column in columns"
 | 
			
		||||
            var2="field=column.field;
 | 
			
		||||
                  sortable=field.isSortable(usage='search');
 | 
			
		||||
                  filterable=field.filterable"
 | 
			
		||||
            width=":column.width" align=":column.align">
 | 
			
		||||
         <x>::ztool.truncateText(_(field.labelId))</x>
 | 
			
		||||
         <x if="(totalNumber > 1) or filterValue">:tool.pxSortAndFilter</x>
 | 
			
		||||
         <x>:tool.pxShowDetails</x>
 | 
			
		||||
        </th>
 | 
			
		||||
       </tr>
 | 
			
		||||
 | 
			
		||||
       <!-- Results -->
 | 
			
		||||
       <tr if="not zobjects">
 | 
			
		||||
        <td colspan=":len(columns)+1">:_('query_no_result')</td>
 | 
			
		||||
       </tr>
 | 
			
		||||
       <tr for="zobj in zobjects" valign="top"
 | 
			
		||||
           var2="@currentNumber=currentNumber + 1;
 | 
			
		||||
                 obj=zobj.appy(); mayView=zobj.mayView();
 | 
			
		||||
                 cbId='%s_%s' % (checkboxesId, currentNumber)"
 | 
			
		||||
           class=":loop.zobj.odd and 'even' or 'odd'">
 | 
			
		||||
        <!-- A checkbox if required -->
 | 
			
		||||
        <td if="checkboxes" class="cbCell" id=":cbId" style=":cbDisplay">
 | 
			
		||||
         <input type="checkbox" name=":checkboxesId" checked="checked"
 | 
			
		||||
                value=":zobj.id" onclick="toggleCb(this)"/>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td for="column in columns"
 | 
			
		||||
            var2="field=column.field" id=":'field_%s' % field.name"
 | 
			
		||||
            width=":column.width"
 | 
			
		||||
            align=":column.align">:tool.pxQueryField</td>
 | 
			
		||||
       </tr>
 | 
			
		||||
      </table>
 | 
			
		||||
      <!-- The button for selecting objects and closing the popup. -->
 | 
			
		||||
      <div if="inPopup and cbShown" align=":dleft">
 | 
			
		||||
       <input type="button"
 | 
			
		||||
              var="label=_('object_link_many'); css=ztool.getButtonCss(label)"
 | 
			
		||||
              value=":label" class=":css" style=":url('linkMany', bg=True)"
 | 
			
		||||
              onclick=":'onSelectObjects(%s,%s,%s,%s,%s,%s,%s)' % \
 | 
			
		||||
               (q(rootHookId), q(uiSearch.initiator.url), \
 | 
			
		||||
                q(uiSearch.initiatorMode), q(sortKey), q(sortOrder), \
 | 
			
		||||
                q(filterKey), q(filterValue))"/>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- Init checkboxes if present. -->
 | 
			
		||||
      <script if="checkboxes">:'initCbs(%s)' % q(checkboxesId)</script>
 | 
			
		||||
      <script>:'initFocus(%s)' % q(ajaxHookId)</script></x>''')
 | 
			
		||||
 | 
			
		||||
    # Show query results as a grid.
 | 
			
		||||
    pxQueryResultGrid = Px('''
 | 
			
		||||
     <table width="100%"
 | 
			
		||||
            var="modeElems=resultMode.split('_');
 | 
			
		||||
                 cols=(len(modeElems)==2) and int(modeElems[1]) or 4;
 | 
			
		||||
                 rows=ztool.splitList(zobjects, cols)">
 | 
			
		||||
      <tr for="row in rows" valign="middle">
 | 
			
		||||
       <td for="zobj in row" width=":'%d%%' % (100/cols)" align="center"
 | 
			
		||||
           style="padding-top: 25px"
 | 
			
		||||
           var2="obj=zobj.appy(); mayView=zobj.mayView()">
 | 
			
		||||
        <x var="currentNumber=currentNumber + 1"
 | 
			
		||||
           for="column in columns"
 | 
			
		||||
           var2="field=column.field">:tool.pxQueryField</x>
 | 
			
		||||
       </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
     </table>''')
 | 
			
		||||
 | 
			
		||||
    # Show paginated query results as a list or grid.
 | 
			
		||||
    pxQueryResult = Px('''
 | 
			
		||||
     <div var="ajaxHookId='queryResult';
 | 
			
		||||
               _=ztool.translate;
 | 
			
		||||
               className=req['className'];
 | 
			
		||||
               searchName=req.get('search', '');
 | 
			
		||||
               uiSearch=uiSearch|ztool.getSearch(className,searchName,ui=True);
 | 
			
		||||
               rootHookId=uiSearch.getRootHookId();
 | 
			
		||||
               refInfo=ztool.getRefInfo();
 | 
			
		||||
               refObject=refInfo[0];
 | 
			
		||||
               refField=refInfo[1];
 | 
			
		||||
               refUrlPart=refObject and ('&ref=%s:%s' % (refObject.id, \
 | 
			
		||||
                                                             refField)) or '';
 | 
			
		||||
               startNumber=req.get('startNumber', '0');
 | 
			
		||||
               startNumber=int(startNumber);
 | 
			
		||||
               sortKey=req.get('sortKey', '');
 | 
			
		||||
               sortOrder=req.get('sortOrder', 'asc');
 | 
			
		||||
               filterKey=req.get('filterKey', '');
 | 
			
		||||
               filterValue=req.get('filterValue', '');
 | 
			
		||||
               queryResult=ztool.executeQuery(className, \
 | 
			
		||||
                   search=uiSearch.search, startNumber=startNumber, \
 | 
			
		||||
                   remember=True, sortBy=sortKey, sortOrder=sortOrder, \
 | 
			
		||||
                   filterKey=filterKey, filterValue=filterValue, \
 | 
			
		||||
                   refObject=refObject, refField=refField);
 | 
			
		||||
               zobjects=queryResult.objects;
 | 
			
		||||
               totalNumber=queryResult.totalNumber;
 | 
			
		||||
               batchSize=queryResult.batchSize;
 | 
			
		||||
               batchNumber=len(zobjects);
 | 
			
		||||
               navBaseCall='askQueryResult(%s,%s,%s,%s,%s,**v**)' % \
 | 
			
		||||
                 (q(ajaxHookId), q(ztool.absolute_url()), q(className), \
 | 
			
		||||
                  q(searchName),int(inPopup));
 | 
			
		||||
               showNewSearch=showNewSearch|True;
 | 
			
		||||
               newSearchUrl='%s/search?className=%s%s' % \
 | 
			
		||||
                   (ztool.absolute_url(), className, refUrlPart);
 | 
			
		||||
               showSubTitles=req.get('showSubTitles', 'true') == 'true';
 | 
			
		||||
               resultMode=ztool.getResultMode(className);
 | 
			
		||||
               target=ztool.getLinksTargetInfo(ztool.getAppyClass(className))"
 | 
			
		||||
          id=":ajaxHookId">
 | 
			
		||||
 | 
			
		||||
      <x if="zobjects or filterValue">
 | 
			
		||||
       <!-- Display here POD templates if required. -->
 | 
			
		||||
       <table var="fields=ztool.getResultPodFields(className);
 | 
			
		||||
                   layoutType='view'"
 | 
			
		||||
              if="not inPopup and zobjects and fields" align=":dright">
 | 
			
		||||
        <tr>
 | 
			
		||||
         <td var="zobj=zobjects[0]; obj=zobj.appy()"
 | 
			
		||||
             for="field in fields"
 | 
			
		||||
             class=":not loop.field.last and 'pod' or ''">:field.pxRender</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
       </table>
 | 
			
		||||
 | 
			
		||||
       <!-- The title of the search -->
 | 
			
		||||
       <p if="not inPopup">
 | 
			
		||||
        <x>::uiSearch.translated</x> (<span class="discreet">:totalNumber</span>)
 | 
			
		||||
        <x if="showNewSearch and (searchName == 'customSearch')"> —
 | 
			
		||||
          <i><a href=":newSearchUrl">:_('search_new')</a></i>
 | 
			
		||||
        </x>
 | 
			
		||||
       </p>
 | 
			
		||||
       <table width="100%">
 | 
			
		||||
        <tr valign="top">
 | 
			
		||||
         <!-- Search description -->
 | 
			
		||||
         <td if="uiSearch.translatedDescr">
 | 
			
		||||
          <span class="discreet">:uiSearch.translatedDescr</span><br/>
 | 
			
		||||
         </td>
 | 
			
		||||
         <!-- (Top) navigation -->
 | 
			
		||||
         <td align=":dright" width="150px">:tool.pxNavigate</td>
 | 
			
		||||
        </tr>
 | 
			
		||||
       </table>
 | 
			
		||||
 | 
			
		||||
       <!-- Results, as a list or grid -->
 | 
			
		||||
       <x var="columnLayouts=ztool.getResultColumnsLayouts(className, refInfo);
 | 
			
		||||
               columns=ztool.getColumnsSpecifiers(className,columnLayouts, dir);
 | 
			
		||||
               currentNumber=0">
 | 
			
		||||
        <x if="resultMode == 'list'">:tool.pxQueryResultList</x>
 | 
			
		||||
        <x if="resultMode != 'list'">:tool.pxQueryResultGrid</x>
 | 
			
		||||
       </x>
 | 
			
		||||
 | 
			
		||||
       <!-- (Bottom) navigation -->
 | 
			
		||||
       <x>:tool.pxNavigate</x>
 | 
			
		||||
      </x>
 | 
			
		||||
 | 
			
		||||
      <x if="not zobjects and not filterValue">
 | 
			
		||||
       <x>:_('query_no_result')</x>
 | 
			
		||||
       <x if="showNewSearch and (searchName == 'customSearch')"><br/>
 | 
			
		||||
        <i class="discreet"><a href=":newSearchUrl">:_('search_new')</a></i></x>
 | 
			
		||||
      </x>
 | 
			
		||||
    </div>''')
 | 
			
		||||
 | 
			
		||||
    pxQuery = Px('''
 | 
			
		||||
     <div var="className=req['className'];
 | 
			
		||||
               searchName=req.get('search', '');
 | 
			
		||||
| 
						 | 
				
			
			@ -575,9 +349,8 @@ class ToolWrapper(AbstractWrapper):
 | 
			
		|||
               rootHookId=uiSearch.getRootHookId();
 | 
			
		||||
               cssJs=None"
 | 
			
		||||
          id=":rootHookId">
 | 
			
		||||
      <script type="text/javascript">:uiSearch.search.getCbJsInit(rootHookId)
 | 
			
		||||
      </script>
 | 
			
		||||
      <x>:tool.pxPagePrologue</x><x>:tool.pxQueryResult</x>
 | 
			
		||||
      <script>:uiSearch.getCbJsInit(rootHookId)</script>
 | 
			
		||||
      <x>:tool.pxPagePrologue</x><x>:uiSearch.pxResult</x>
 | 
			
		||||
     </div>''', template=AbstractWrapper.pxTemplate, hook='content')
 | 
			
		||||
 | 
			
		||||
    pxSearch = Px('''
 | 
			
		||||
| 
						 | 
				
			
			@ -591,8 +364,7 @@ class ToolWrapper(AbstractWrapper):
 | 
			
		|||
      <!-- Include type-specific CSS and JS -->
 | 
			
		||||
      <link for="cssFile in cssJs['css']" rel="stylesheet" type="text/css"
 | 
			
		||||
            href=":url(cssFile)"/>
 | 
			
		||||
      <script for="jsFile in cssJs['js']" type="text/javascript"
 | 
			
		||||
              src=":url(jsFile)"></script>
 | 
			
		||||
      <script for="jsFile in cssJs['js']" src=":url(jsFile)"></script>
 | 
			
		||||
 | 
			
		||||
      <!-- Search title -->
 | 
			
		||||
      <h1><x>:_('%s_plural'%className)</x> –
 | 
			
		||||
| 
						 | 
				
			
			@ -625,13 +397,8 @@ class ToolWrapper(AbstractWrapper):
 | 
			
		|||
 | 
			
		||||
    pxBack = Px('''
 | 
			
		||||
     <html>
 | 
			
		||||
      <head>
 | 
			
		||||
       <script src=":ztool.getIncludeUrl('appy.js')" type="text/javascript">
 | 
			
		||||
       </script>
 | 
			
		||||
      </head>
 | 
			
		||||
      <body>
 | 
			
		||||
       <script type="text/javascript">backFromPopup()</script>
 | 
			
		||||
      </body>
 | 
			
		||||
      <head><script src=":ztool.getIncludeUrl('appy.js')"></script></head>
 | 
			
		||||
      <body><script>backFromPopup()</script></body>
 | 
			
		||||
     </html>''')
 | 
			
		||||
 | 
			
		||||
    def isManager(self):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,7 +66,7 @@ class AbstractWrapper(object):
 | 
			
		|||
                req=ztool.REQUEST;              resp=req.RESPONSE;
 | 
			
		||||
                inPopup=req.get('popup') == '1';
 | 
			
		||||
                obj=obj or ztool.getHomeObject(inPopup);
 | 
			
		||||
                zobj=obj and obj.o or None;
 | 
			
		||||
                zobj=obj and obj.o or None;     ajax=False;
 | 
			
		||||
                isAnon=user.login=='anon';      app=ztool.getApp();
 | 
			
		||||
                appFolder=app.data;             url = ztool.getIncludeUrl;
 | 
			
		||||
                appName=ztool.getAppName();     _=ztool.translate;
 | 
			
		||||
| 
						 | 
				
			
			@ -88,8 +88,7 @@ class AbstractWrapper(object):
 | 
			
		|||
      <x for="name in ztool.getGlobalCssJs(dir)">
 | 
			
		||||
       <link if="name.endswith('.css')" rel="stylesheet" type="text/css"
 | 
			
		||||
             href=":url(name)"/>
 | 
			
		||||
       <script if="name.endswith('.js')" type="text/javascript"
 | 
			
		||||
               src=":url(name)"></script>
 | 
			
		||||
       <script if="name.endswith('.js')" src=":url(name)"></script>
 | 
			
		||||
      </x>
 | 
			
		||||
     </head>
 | 
			
		||||
     <body style=":(cfg.skin == 'wide') and 'margin:0' or ''">
 | 
			
		||||
| 
						 | 
				
			
			@ -376,9 +375,8 @@ class AbstractWrapper(object):
 | 
			
		|||
    pxTransitions = Px('''
 | 
			
		||||
     <form var="transitions=targetObj.getTransitions()" if="transitions"
 | 
			
		||||
           var2="formId='trigger_%s' % targetObj.id" method="post"
 | 
			
		||||
           id=":formId" action=":targetObj.absolute_url() + '/do'"
 | 
			
		||||
           id=":formId" action=":targetObj.absolute_url() + '/onTrigger'"
 | 
			
		||||
           style="display: inline">
 | 
			
		||||
      <input type="hidden" name="action" value="Trigger"/>
 | 
			
		||||
      <input type="hidden" name="transition"/>
 | 
			
		||||
      <!-- Input field for storing the comment coming from the popup -->
 | 
			
		||||
      <textarea id="comment" name="comment" cols="30" rows="3"
 | 
			
		||||
| 
						 | 
				
			
			@ -388,8 +386,7 @@ class AbstractWrapper(object):
 | 
			
		|||
       <x if="transition.type == 'transition'">:transition.pxView</x>
 | 
			
		||||
       <x if="transition.type == 'group'"
 | 
			
		||||
          var2="uiGroup=transition">:uiGroup.px</x>
 | 
			
		||||
      </x>
 | 
			
		||||
     </form>''')
 | 
			
		||||
      </x></form>''')
 | 
			
		||||
 | 
			
		||||
    # Displays header information about an object: title, workflow-related info,
 | 
			
		||||
    # history...
 | 
			
		||||
| 
						 | 
				
			
			@ -534,7 +531,7 @@ class AbstractWrapper(object):
 | 
			
		|||
                                                   inPopup=inPopup))"/>
 | 
			
		||||
      </x>
 | 
			
		||||
      <!-- Workflow transitions -->
 | 
			
		||||
      <x var="targetObj=zobj; buttonsMode='normal'"
 | 
			
		||||
      <x var="targetObj=zobj"
 | 
			
		||||
         if="mayAct and \
 | 
			
		||||
             targetObj.showTransitions(layoutType)">:obj.pxTransitions</x>
 | 
			
		||||
      <!-- Fields (actions) defined with layout "buttons" -->
 | 
			
		||||
| 
						 | 
				
			
			@ -578,6 +575,39 @@ class AbstractWrapper(object):
 | 
			
		|||
      </table>
 | 
			
		||||
     </form>''')
 | 
			
		||||
 | 
			
		||||
    # The object, as shown in a list of query results
 | 
			
		||||
    pxViewAsResult = Px('''
 | 
			
		||||
     <tr var2="obj=zobj.appy(); mayView=zobj.mayView();
 | 
			
		||||
               cbId='%s_%s' % (checkboxesId, currentNumber)"
 | 
			
		||||
         id=":zobj.id" class=":rowCss" valign="top">
 | 
			
		||||
      <!-- A checkbox if required -->
 | 
			
		||||
      <td if="checkboxes" class="cbCell" id=":cbId"
 | 
			
		||||
          style=":'display:%s' % cbDisplay">
 | 
			
		||||
       <input type="checkbox" name=":checkboxesId" checked="checked"
 | 
			
		||||
              value=":zobj.id" onclick="toggleCb(this)"/>
 | 
			
		||||
      </td>
 | 
			
		||||
      <td for="column in columns"
 | 
			
		||||
          var2="field=column.field" id=":'field_%s' % field.name"
 | 
			
		||||
          width=":column.width"
 | 
			
		||||
          align=":column.align">:field.pxRenderAsResult</td>
 | 
			
		||||
     <!-- Store data in this tr node allowing to ajax-refresh it -->
 | 
			
		||||
     <script>:uiSearch.getAjaxDataRow(zobj, ajaxHookId, \
 | 
			
		||||
               currentNumber=currentNumber, rowCss=rowCss)</script>
 | 
			
		||||
     </tr>''')
 | 
			
		||||
 | 
			
		||||
    # When calling pxViewAsResult from Ajax, this surrounding PX is called to
 | 
			
		||||
    # define the appropriate variables based on request values.
 | 
			
		||||
    pxViewAsResultFromAjax = Px('''
 | 
			
		||||
     <x var="ajaxHookId='queryResult';
 | 
			
		||||
             dummy=ztool.updatePxContextFromRequest();
 | 
			
		||||
             showSubTitles=showSubTitles|True;
 | 
			
		||||
             refInfo=ztool.getRefInfo();
 | 
			
		||||
             columnLayouts=ztool.getResultColumnsLayouts(className, refInfo);
 | 
			
		||||
             columns=ztool.getColumnsSpecifiers(className, columnLayouts, dir);
 | 
			
		||||
             target=ztool.getLinksTargetInfo(ztool.getAppyClass(className));
 | 
			
		||||
             uiSearch=ztool.getSearch(\
 | 
			
		||||
               className, searchName, ui=True)">:obj.pxViewAsResult</x>''')
 | 
			
		||||
    
 | 
			
		||||
    pxView = Px('''
 | 
			
		||||
     <x var="x=zobj.mayView(raiseError=True);
 | 
			
		||||
             errors=req.get('errors', {});
 | 
			
		||||
| 
						 | 
				
			
			@ -639,11 +669,12 @@ class AbstractWrapper(object):
 | 
			
		|||
             req=ztool.REQUEST;              resp=req.RESPONSE;
 | 
			
		||||
             dummy=setattr(req, 'pxContext', _ctx_);
 | 
			
		||||
             lang=ztool.getUserLanguage();   q=ztool.quote;
 | 
			
		||||
             action=req.get('action', '');
 | 
			
		||||
             action=req.get('action', '');   ajax=True;
 | 
			
		||||
             px=req['px'].split(':');
 | 
			
		||||
             inPopup=req.get('popup') == '1';
 | 
			
		||||
             className=(len(px) == 3) and px[0] or None;
 | 
			
		||||
             field=className and zobj.getAppyType(px[1], className) or None;
 | 
			
		||||
             field=className and (zobj.getAppyType(px[1], className) or \
 | 
			
		||||
                                  ztool.getSearch(className, px[1], ui=True));
 | 
			
		||||
             field=(len(px) == 2) and zobj.getAppyType(px[0]) or field;
 | 
			
		||||
             dir=ztool.getLanguageDirection(lang);
 | 
			
		||||
             dleft=(dir == 'ltr') and 'left' or 'right';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,11 +95,11 @@ class Px:
 | 
			
		|||
              as is, without re-applying the template (else, an infinite
 | 
			
		||||
              recursion would occur).
 | 
			
		||||
        '''
 | 
			
		||||
        # Developer, forget the following line forever.
 | 
			
		||||
        # Developer, forget the following line forever
 | 
			
		||||
        if '_ctx_' not in context: context['_ctx_'] = context
 | 
			
		||||
 | 
			
		||||
        if self.hook and applyTemplate:
 | 
			
		||||
            # Call the template PX, filling the hook with the current PX.
 | 
			
		||||
            # Call the template PX, filling the hook with the current PX
 | 
			
		||||
            context[self.hook] = self
 | 
			
		||||
            return self.template(context)
 | 
			
		||||
        else:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -294,6 +294,8 @@ def getStringDict(d):
 | 
			
		|||
    res = []
 | 
			
		||||
    for k, v in d.iteritems():
 | 
			
		||||
        if type(v) not in sequenceTypes:
 | 
			
		||||
            if not isinstance(k, basestring): k = str(k)
 | 
			
		||||
            if not isinstance(v, basestring): v = str(v)
 | 
			
		||||
            value = "'%s':'%s'" % (k, v.replace("'", "\\'"))
 | 
			
		||||
        else:
 | 
			
		||||
            value = "'%s':%s" % (k, v)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue