2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
2012-07-18 14:58:11 -05:00
|
|
|
import os.path, time
|
2011-01-18 08:48:55 -06:00
|
|
|
import appy
|
2013-02-28 16:00:06 -06:00
|
|
|
from appy.gen.mail import sendMail
|
2011-01-18 08:48:55 -06:00
|
|
|
from appy.shared.utils import executeCommand
|
2011-12-05 08:11:29 -06:00
|
|
|
from appy.gen.wrappers import AbstractWrapper
|
2013-06-25 05:04:23 -05:00
|
|
|
from appy.px import Px
|
2010-08-05 11:23:17 -05:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
_PY = 'Please specify a file corresponding to a Python interpreter ' \
|
|
|
|
'(ie "/usr/bin/python").'
|
|
|
|
FILE_NOT_FOUND = 'Path "%s" was not found.'
|
|
|
|
VALUE_NOT_FILE = 'Path "%s" is not a file. ' + _PY
|
|
|
|
NO_PYTHON = "Name '%s' does not starts with 'python'. " + _PY
|
|
|
|
NOT_UNO_ENABLED_PYTHON = '"%s" is not a UNO-enabled Python interpreter. ' \
|
|
|
|
'To check if a Python interpreter is UNO-enabled, ' \
|
|
|
|
'launch it and type "import uno". If you have no ' \
|
|
|
|
'ImportError exception it is ok.'
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class ToolWrapper(AbstractWrapper):
|
2013-06-25 05:04:23 -05:00
|
|
|
|
2013-08-21 05:35:30 -05:00
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
# Navigation-related PXs
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
# Icon for hiding/showing details below the title of an object shown in a
|
|
|
|
# list of objects.
|
|
|
|
pxShowDetails = Px('''
|
|
|
|
<img if="ztool.subTitleIsUsed(className) and (field.name == 'title')"
|
|
|
|
class="clickable" src=":url('toggleDetails')"
|
|
|
|
onclick="toggleSubTitles()"/>''')
|
|
|
|
|
|
|
|
# Displays up/down arrows in a table header column for sorting a given
|
|
|
|
# column. Requires variables "sortable", 'filterable' and 'field'.
|
|
|
|
pxSortAndFilter = Px('''<x>
|
|
|
|
<x if="sortable">
|
|
|
|
<img if="(sortKey != field.name) or (sortOrder == 'desc')"
|
|
|
|
onclick=":navBaseCall.replace('**v**', '0,%s,%s,%s' % \
|
|
|
|
(q(field.name), q('asc'), q(filterKey)))"
|
|
|
|
src=":url('sortDown.gif')" class="clickable"/>
|
|
|
|
<img if="(sortKey != field.name) or (sortOrder == 'asc')"
|
|
|
|
onclick=":navBaseCall.replace('**v**', '0,%s,%s,%s' % \
|
|
|
|
(q(field.name), q('desc'), q(filterKey)))"
|
|
|
|
src=":url('sortUp.gif')" class="clickable"/>
|
|
|
|
</x>
|
|
|
|
<x if="filterable">
|
|
|
|
<input type="text" size="7" id=":'%s_%s' % (ajaxHookId, field.name)"
|
|
|
|
value=":filterKey == field.name and filterValue or ''"/>
|
|
|
|
<img onclick=":navBaseCall.replace('**v**', '0, %s,%s,%s' % \
|
|
|
|
(q(sortKey), q(sortOrder), q(field.name)))"
|
|
|
|
src=":url('funnel')" class="clickable"/>
|
|
|
|
</x></x>''')
|
|
|
|
|
|
|
|
# Buttons for navigating among a list of objects (from a Ref field or a
|
|
|
|
# query): next,back,first,last...
|
|
|
|
pxNavigate = Px('''
|
|
|
|
<div if="totalNumber > batchSize" align=":dright">
|
|
|
|
<table class="listNavigate"
|
|
|
|
var="mustSortAndFilter=ajaxHookId == 'queryResult';
|
|
|
|
sortAndFilter=mustSortAndFilter and \
|
|
|
|
',%s,%s,%s' % (q(sortKey),q(sortOrder),q(filterKey)) or ''">
|
|
|
|
<tr valign="middle">
|
|
|
|
<!-- Go to the first page -->
|
|
|
|
<td if="(startNumber != 0) and (startNumber != batchSize)"><img
|
|
|
|
class="clickable" src=":url('arrowLeftDouble')"
|
|
|
|
title=":_('goto_first')"
|
|
|
|
onClick=":navBaseCall.replace('**v**', '0'+sortAndFilter)"/></td>
|
|
|
|
|
|
|
|
<!-- Go to the previous page -->
|
|
|
|
<td var="sNumber=startNumber - batchSize" if="startNumber != 0"><img
|
|
|
|
class="clickable" src=":url('arrowLeftSimple')"
|
|
|
|
title=":_('goto_previous')"
|
|
|
|
onClick=":navBaseCall.replace('**v**', \
|
|
|
|
str(sNumber)+sortAndFilter)"/></td>
|
|
|
|
|
|
|
|
<!-- Explain which elements are currently shown -->
|
|
|
|
<td class="discreet">
|
|
|
|
<x>:startNumber + 1</x><img src=":url('to')"/>
|
|
|
|
<x>:startNumber + batchNumber</x> <b>//</b>
|
|
|
|
<x>:totalNumber</x> </td>
|
|
|
|
|
|
|
|
<!-- Go to the next page -->
|
|
|
|
<td var="sNumber=startNumber + batchSize"
|
|
|
|
if="sNumber < totalNumber"><img class="clickable"
|
|
|
|
src=":url('arrowRightSimple')" title=":_('goto_next')"
|
|
|
|
onClick=":navBaseCall.replace('**v**', \
|
|
|
|
str(sNumber)+sortAndFilter)"/></td>
|
|
|
|
|
|
|
|
<!-- Go to the last page -->
|
|
|
|
<td var="lastPageIsIncomplete=totalNumber % batchSize;
|
|
|
|
nbOfCompletePages=totalNumber/batchSize;
|
|
|
|
nbOfCountedPages=lastPageIsIncomplete and \
|
|
|
|
nbOfCompletePages or nbOfCompletePages-1;
|
|
|
|
sNumber= nbOfCountedPages * batchSize"
|
|
|
|
if="(startNumber != sNumber) and \
|
|
|
|
(startNumber != sNumber-batchSize)"><img class="clickable"
|
|
|
|
src=":url('arrowRightDouble')" title=":_('goto_last')"
|
|
|
|
onClick=":navBaseCall.replace('**v**', \
|
|
|
|
str(sNumber)+sortAndFilter)"/></td>
|
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
</div>''')
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
# PXs for graphical elements shown on every page
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
# Global elements included in every page.
|
|
|
|
pxPagePrologue = Px('''<x>
|
|
|
|
<!-- Include type-specific CSS and JS. -->
|
|
|
|
<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>
|
|
|
|
|
|
|
|
<!-- Javascript messages -->
|
|
|
|
<script type="text/javascript">::ztool.getJavascriptMessages()</script>
|
|
|
|
|
|
|
|
<!-- Global form for deleting an object -->
|
|
|
|
<form id="deleteForm" method="post" action="do">
|
|
|
|
<input type="hidden" name="action" value="Delete"/>
|
|
|
|
<input type="hidden" name="objectUid"/>
|
|
|
|
</form>
|
|
|
|
<!-- Global form for deleting an event from an object's history -->
|
|
|
|
<form id="deleteEventForm" method="post" action="do">
|
|
|
|
<input type="hidden" name="action" value="DeleteEvent"/>
|
|
|
|
<input type="hidden" name="objectUid"/>
|
|
|
|
<input type="hidden" name="eventTime"/>
|
|
|
|
</form>
|
|
|
|
<!-- Global form for unlinking an object -->
|
|
|
|
<form id="unlinkForm" method="post" action="do">
|
|
|
|
<input type="hidden" name="action" value="Unlink"/>
|
|
|
|
<input type="hidden" name="sourceUid"/>
|
|
|
|
<input type="hidden" name="fieldName"/>
|
|
|
|
<input type="hidden" name="targetUid"/>
|
|
|
|
</form>
|
|
|
|
<!-- Global form for unlocking a page -->
|
|
|
|
<form id="unlockForm" method="post" action="do">
|
|
|
|
<input type="hidden" name="action" value="Unlock"/>
|
|
|
|
<input type="hidden" name="objectUid"/>
|
|
|
|
<input type="hidden" name="pageName"/>
|
|
|
|
</form>
|
|
|
|
<!-- Global form for generating a document from a pod template -->
|
|
|
|
<form id="podTemplateForm" name="podTemplateForm" method="post"
|
|
|
|
action=":ztool.absolute_url() + '/generateDocument'">
|
|
|
|
<input type="hidden" name="objectUid"/>
|
|
|
|
<input type="hidden" name="fieldName"/>
|
|
|
|
<input type="hidden" name="podFormat"/>
|
|
|
|
<input type="hidden" name="askAction"/>
|
|
|
|
<input type="hidden" name="queryData"/>
|
|
|
|
<input type="hidden" name="customParams"/>
|
|
|
|
</form>
|
|
|
|
</x>''')
|
|
|
|
|
|
|
|
pxPageBottom = Px('''
|
|
|
|
<script type="text/javascript">initSlaves();</script>''')
|
|
|
|
|
|
|
|
pxPortlet = Px('''
|
|
|
|
<x var="toolUrl=tool.url;
|
|
|
|
queryUrl='%s/query' % toolUrl;
|
|
|
|
currentSearch=req.get('search', None);
|
|
|
|
currentClass=req.get('className', None);
|
|
|
|
currentPage=req['PATH_INFO'].rsplit('/',1)[-1];
|
|
|
|
rootClasses=ztool.getRootClasses();
|
|
|
|
phases=zobj and zobj.getAppyPhases() or None">
|
|
|
|
|
|
|
|
<table class="portletContent"
|
|
|
|
if="zobj and phases and zobj.mayNavigate()"
|
|
|
|
var2="singlePhase=phases and (len(phases) == 1);
|
|
|
|
page=req.get('page', '');
|
|
|
|
mayEdit=zobj.mayEdit()">
|
|
|
|
<x for="phase in phases">:phase.pxView</x>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<!-- One section for every searchable root class -->
|
2013-08-23 11:57:27 -05:00
|
|
|
<x for="rootClass in rootClasses" if="ztool.userMaySearch(rootClass)"
|
|
|
|
var2="className=ztool.getPortalType(rootClass)">
|
2013-08-21 05:35:30 -05:00
|
|
|
|
|
|
|
<!-- A separator if required -->
|
|
|
|
<div class="portletSep" var="nb=loop.rootClass.nb"
|
|
|
|
if="(nb != 0) or ((nb == 0) and phases)"></div>
|
|
|
|
|
|
|
|
<!-- Section title (link triggers the default search) -->
|
|
|
|
<div class="portletContent"
|
|
|
|
var="searchInfo=ztool.getGroupedSearches(rootClass)">
|
|
|
|
<div class="portletTitle">
|
2013-08-21 15:25:27 -05:00
|
|
|
<a var="queryParam=searchInfo.default and \
|
|
|
|
searchInfo.default.name or ''"
|
2013-08-21 05:35:30 -05:00
|
|
|
href=":'%s?className=%s&search=%s' % \
|
2013-08-23 11:57:27 -05:00
|
|
|
(queryUrl, className, queryParam)"
|
|
|
|
class=":(not currentSearch and (currentClass==className) and \
|
2013-08-21 05:35:30 -05:00
|
|
|
(currentPage=='query')) and \
|
2013-08-23 11:57:27 -05:00
|
|
|
'portletCurrent' or ''">::_(className + '_plural')</a>
|
2013-08-21 05:35:30 -05:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Actions -->
|
2013-08-23 11:57:27 -05:00
|
|
|
<x var="mayCreate=ztool.userMayCreate(rootClass);
|
2013-08-21 05:35:30 -05:00
|
|
|
createMeans=ztool.getCreateMeans(rootClass)">
|
|
|
|
|
2013-08-23 11:57:27 -05:00
|
|
|
<!-- Create a new object from a web form. -->
|
2013-08-21 05:35:30 -05:00
|
|
|
<input type="button" class="button"
|
2013-08-23 11:57:27 -05:00
|
|
|
if="mayCreate and ('form' in createMeans)"
|
2013-08-21 05:35:30 -05:00
|
|
|
style=":url('buttonAdd', bg=True)" value=":_('query_create')"
|
|
|
|
onclick=":'goto(%s)' % \
|
|
|
|
q('%s/do?action=Create&className=%s' % \
|
2013-08-23 11:57:27 -05:00
|
|
|
(toolUrl, className))"/>
|
2013-08-21 05:35:30 -05:00
|
|
|
|
|
|
|
<!-- Create object(s) by importing data -->
|
|
|
|
<input type="button" class="button"
|
2013-08-23 11:57:27 -05:00
|
|
|
if="mayCreate and ('import' in createMeans)"
|
2013-08-21 05:35:30 -05:00
|
|
|
style=":url('buttonImport', bg=True)" value=":_('query_import')"
|
|
|
|
onclick=":'goto(%s)' % \
|
2013-08-23 11:57:27 -05:00
|
|
|
q('%s/import?className=%s' % (toolUrl, className))"/>
|
2013-08-21 05:35:30 -05:00
|
|
|
</x>
|
|
|
|
|
|
|
|
<!-- Searches -->
|
|
|
|
<x if="ztool.advancedSearchEnabledFor(rootClass)">
|
|
|
|
|
|
|
|
<!-- Live search -->
|
|
|
|
<form action=":'%s/do' % toolUrl">
|
|
|
|
<input type="hidden" name="action" value="SearchObjects"/>
|
2013-08-23 11:57:27 -05:00
|
|
|
<input type="hidden" name="className" value=":className"/>
|
2013-08-21 05:35:30 -05:00
|
|
|
<table cellpadding="0" cellspacing="0">
|
|
|
|
<tr valign="bottom">
|
|
|
|
<td><input type="text" size="14" name="w_SearchableText"
|
|
|
|
class="inputSearch"/></td>
|
|
|
|
<td>
|
|
|
|
<input type="image" class="clickable" src=":url('search.gif')"
|
|
|
|
title=":_('search_button')"/></td>
|
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
</form>
|
|
|
|
|
|
|
|
<!-- Advanced search -->
|
2013-08-23 11:57:27 -05:00
|
|
|
<div var="highlighted=(currentClass == className) and \
|
2013-08-21 05:35:30 -05:00
|
|
|
(currentPage == 'search')"
|
|
|
|
class=":highlighted and 'portletSearch portletCurrent' or \
|
|
|
|
'portletSearch'"
|
|
|
|
align=":dright">
|
|
|
|
<a var="text=_('search_title')" style="font-size: 88%"
|
2013-08-23 11:57:27 -05:00
|
|
|
href=":'%s/search?className=%s' % (toolUrl, className)"
|
2013-08-21 05:35:30 -05:00
|
|
|
title=":text"><x>:text</x>...</a>
|
|
|
|
</div>
|
|
|
|
</x>
|
|
|
|
|
|
|
|
<!-- Predefined searches -->
|
2013-08-21 15:25:27 -05:00
|
|
|
<x for="search in searchInfo.searches">
|
|
|
|
<x if="search.type == 'group'">:search.px</x>
|
|
|
|
<x if="search.type != 'group'">:search.pxView</x>
|
2013-08-21 05:35:30 -05:00
|
|
|
</x>
|
|
|
|
</div>
|
|
|
|
</x>
|
|
|
|
</x>''')
|
|
|
|
|
|
|
|
# The message that is shown when a user triggers an action.
|
|
|
|
pxMessage = Px('''
|
|
|
|
<div var="messages=ztool.consumeMessages()" if="messages" class="message">
|
|
|
|
<!-- The icon for closing the message -->
|
|
|
|
<img src=":url('close')" align=":dright" class="clickable"
|
|
|
|
onclick="this.parentNode.style.display='none'"/>
|
|
|
|
<!-- The message content -->
|
|
|
|
<x>::messages</x>
|
|
|
|
</div>''')
|
|
|
|
|
|
|
|
# The page footer.
|
|
|
|
pxFooter = Px('''
|
|
|
|
<table cellpadding="0" cellspacing="0" width="100%" class="footer">
|
|
|
|
<tr>
|
|
|
|
<td align=":dright">Made with
|
|
|
|
<a href="http://appyframework.org" target="_blank">Appy</a></td></tr>
|
|
|
|
</table>''')
|
|
|
|
|
|
|
|
# Hook for defining a PX that proposes additional links, after the links
|
|
|
|
# corresponding to top-level pages.
|
|
|
|
pxLinks = ''
|
|
|
|
|
|
|
|
# Hook for defining a PX that proposes additional icons after standard
|
|
|
|
# icons in the user strip.
|
|
|
|
pxIcons = ''
|
|
|
|
|
|
|
|
# Displays the content of a layouted object (a page or a field). If the
|
|
|
|
# layouted object is a page, the "layout target" (where to look for PXs)
|
|
|
|
# will be the object whose page is shown; if the layouted object is a field,
|
|
|
|
# the layout target will be this field.
|
|
|
|
pxLayoutedObject = Px('''
|
2013-08-21 15:25:27 -05:00
|
|
|
<table var="layoutCss=layout.css_class;
|
2013-08-21 05:35:30 -05:00
|
|
|
isCell=layoutType == 'cell'"
|
2013-08-21 15:25:27 -05:00
|
|
|
cellpadding=":layout.cellpadding"
|
|
|
|
cellspacing=":layout.cellspacing"
|
|
|
|
width=":not isCell and layout.width or ''"
|
2013-08-21 05:35:30 -05:00
|
|
|
align=":not isCell and \
|
2013-08-21 15:25:27 -05:00
|
|
|
ztool.flipLanguageDirection(layout.align, dir) or ''"
|
2013-08-21 05:35:30 -05:00
|
|
|
class=":tagCss and ('%s %s' % (tagCss, layoutCss)).strip() or \
|
|
|
|
layoutCss"
|
2013-08-21 15:25:27 -05:00
|
|
|
style=":layout.style" id=":tagId" name=":tagName">
|
2013-08-21 05:35:30 -05:00
|
|
|
|
|
|
|
<!-- The table header row -->
|
2013-08-21 15:25:27 -05:00
|
|
|
<tr if="layout.headerRow" valign=":layout.headerRow.valign">
|
|
|
|
<th for="cell in layout.headerRow.cells" width=":cell.width"
|
|
|
|
align=":ztool.flipLanguageDirection(cell.align, dir)">
|
2013-08-21 05:35:30 -05:00
|
|
|
</th>
|
|
|
|
</tr>
|
|
|
|
<!-- The table content -->
|
2013-08-21 15:25:27 -05:00
|
|
|
<tr for="row in layout.rows" valign=":row.valign">
|
|
|
|
<td for="cell in row.cells" colspan=":cell.colspan"
|
|
|
|
align=":ztool.flipLanguageDirection(cell.align, dir)"
|
2013-08-21 05:35:30 -05:00
|
|
|
class=":not loop.cell.last and 'cellGap' or ''">
|
2013-08-21 15:25:27 -05:00
|
|
|
<x for="pxName in cell.content">
|
2013-08-21 05:35:30 -05:00
|
|
|
<x var="px=(pxName == '?') and 'px%s' % layoutType.capitalize() \
|
|
|
|
or pxName">:getattr(layoutTarget, px)</x>
|
|
|
|
<img if="not loop.pxName.last" src=":url('space.gif')"/>
|
|
|
|
</x>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</table>''')
|
|
|
|
|
2013-06-25 16:22:33 -05:00
|
|
|
pxHome = Px('''
|
|
|
|
<table width="300px" height="240px" align="center">
|
|
|
|
<tr valign="middle">
|
2013-06-26 06:44:31 -05:00
|
|
|
<td align="center">::_('front_page_text')</td>
|
2013-06-25 16:22:33 -05:00
|
|
|
</tr>
|
2013-06-27 04:57:39 -05:00
|
|
|
</table>''', template=AbstractWrapper.pxTemplate, hook='content')
|
|
|
|
|
|
|
|
# Show on query list or grid, the field content for a given object.
|
2013-07-15 04:23:29 -05:00
|
|
|
pxQueryField = Px('''<x>
|
|
|
|
<!-- Title -->
|
|
|
|
<x if="field.name == 'title'"
|
|
|
|
var2="navInfo='search.%s.%s.%d.%d' % \
|
|
|
|
(className, searchName, startNumber+currentNumber, totalNumber);
|
2013-07-23 03:29:39 -05:00
|
|
|
cssClass=zobj.getCssFor('title')">
|
|
|
|
<x>::zobj.getSupTitle(navInfo)</x>
|
|
|
|
<a href=":zobj.getUrl(nav=navInfo, page=zobj.getDefaultViewPage())"
|
|
|
|
if="enableLinks" class=":cssClass">:zobj.Title()</a><span
|
|
|
|
if="not enableLinks" class=":cssClass">:zobj.Title()</span><span
|
2013-07-15 04:23:29 -05:00
|
|
|
style=":showSubTitles and 'display:inline' or 'display:none'"
|
2013-07-23 03:29:39 -05:00
|
|
|
name="subTitle">::zobj.getSubTitle()</span>
|
2013-06-27 04:57:39 -05:00
|
|
|
|
2013-07-15 04:23:29 -05:00
|
|
|
<!-- Actions: edit, delete -->
|
2013-07-23 03:29:39 -05:00
|
|
|
<div if="zobj.mayAct()">
|
|
|
|
<a if="zobj.mayEdit()"
|
2013-07-15 04:23:29 -05:00
|
|
|
var2="navInfo='search.%s.%s.%d.%d' % \
|
2013-07-23 03:29:39 -05:00
|
|
|
(className, searchName, loop.zobj.nb+1+startNumber, totalNumber)"
|
|
|
|
href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \
|
|
|
|
nav=navInfo)">
|
2013-07-20 12:56:17 -05:00
|
|
|
<img src=":url('edit')" title=":_('object_edit')"/></a>
|
2013-07-23 03:29:39 -05:00
|
|
|
<img if="zobj.mayDelete()" class="clickable" src=":url('delete')"
|
2013-07-15 04:23:29 -05:00
|
|
|
title=":_('object_delete')"
|
2013-07-23 03:29:39 -05:00
|
|
|
onClick=":'onDeleteObject(%s)' % q(zobj.UID())"/>
|
2013-07-15 04:23:29 -05:00
|
|
|
</div>
|
|
|
|
</x>
|
|
|
|
<!-- Any other field -->
|
|
|
|
<x if="field.name != 'title'">
|
2013-07-23 03:29:39 -05:00
|
|
|
<x var="layoutType='cell'; innerRef=True"
|
2013-08-21 05:35:30 -05:00
|
|
|
if="zobj.showField(field.name, 'result')">:field.pxRender</x>
|
2013-07-15 04:23:29 -05:00
|
|
|
</x>
|
|
|
|
</x>''')
|
2013-06-27 04:57:39 -05:00
|
|
|
|
|
|
|
# Show query results as a list.
|
|
|
|
pxQueryResultList = Px('''
|
2013-08-21 05:35:30 -05:00
|
|
|
<table class="list" width="100%" var="showHeaders=showHeaders|True">
|
2013-06-27 04:57:39 -05:00
|
|
|
<!-- Headers, with filters and sort arrows -->
|
|
|
|
<tr if="showHeaders">
|
2013-07-15 04:23:29 -05:00
|
|
|
<th for="column in columns"
|
2013-08-21 05:35:30 -05:00
|
|
|
var2="field=column.field;
|
2013-07-15 04:23:29 -05:00
|
|
|
sortable=ztool.isSortable(field.name, className, 'search');
|
2013-08-21 05:35:30 -05:00
|
|
|
filterable=field.filterable"
|
|
|
|
width=":column.width" align=":column.align">
|
2013-07-15 04:23:29 -05:00
|
|
|
<x>::ztool.truncateText(_(field.labelId))</x>
|
2013-08-21 05:35:30 -05:00
|
|
|
<x>:tool.pxSortAndFilter</x><x>:tool.pxShowDetails</x>
|
2013-07-15 04:23:29 -05:00
|
|
|
</th>
|
2013-06-27 04:57:39 -05:00
|
|
|
</tr>
|
|
|
|
|
|
|
|
<!-- Results -->
|
2013-07-23 03:29:39 -05:00
|
|
|
<tr for="zobj in zobjects" id="query_row" valign="top"
|
|
|
|
var2="currentNumber=currentNumber + 1;
|
|
|
|
obj=zobj.appy()"
|
|
|
|
class=":loop.zobj.odd and 'even' or 'odd'">
|
2013-07-15 04:23:29 -05:00
|
|
|
<td for="column in columns"
|
2013-08-21 05:35:30 -05:00
|
|
|
var2="field=column.field" id=":'field_%s' % field.name"
|
|
|
|
width=":column.width"
|
|
|
|
align=":column.align">:tool.pxQueryField</td>
|
2013-07-15 04:23:29 -05:00
|
|
|
</tr>
|
2013-06-27 04:57:39 -05:00
|
|
|
</table>''')
|
|
|
|
|
|
|
|
# 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;
|
2013-07-23 03:29:39 -05:00
|
|
|
rows=ztool.splitList(zobjects, cols)">
|
2013-06-27 04:57:39 -05:00
|
|
|
<tr for="row in rows" valign="middle">
|
2013-07-23 03:29:39 -05:00
|
|
|
<td for="zobj in row" width=":'%d%%' % (100/cols)" align="center"
|
|
|
|
style="padding-top: 25px" var2="obj=zobj.appy()">
|
2013-06-27 04:57:39 -05:00
|
|
|
<x var="currentNumber=currentNumber + 1"
|
2013-07-15 04:23:29 -05:00
|
|
|
for="column in columns"
|
2013-08-21 05:35:30 -05:00
|
|
|
var2="field=column.field">:tool.pxQueryField</x>
|
2013-06-27 04:57:39 -05:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</table>''')
|
|
|
|
|
|
|
|
# Show paginated query results as a list or grid.
|
|
|
|
pxQueryResult = Px('''
|
|
|
|
<div id="queryResult"
|
|
|
|
var="_=ztool.translate;
|
|
|
|
className=req['className'];
|
|
|
|
refInfo=ztool.getRefInfo();
|
|
|
|
refObject=refInfo[0];
|
|
|
|
refField=refInfo[1];
|
|
|
|
refUrlPart=refObject and ('&ref=%s:%s' % (refObject.UID(), \
|
|
|
|
refField)) or '';
|
|
|
|
startNumber=req.get('startNumber', '0');
|
|
|
|
startNumber=int(startNumber);
|
|
|
|
searchName=req.get('search', '');
|
2013-08-21 05:35:30 -05:00
|
|
|
uiSearch=ztool.getSearch(className, searchName, ui=True);
|
2013-06-27 04:57:39 -05:00
|
|
|
sortKey=req.get('sortKey', '');
|
|
|
|
sortOrder=req.get('sortOrder', 'asc');
|
|
|
|
filterKey=req.get('filterKey', '');
|
|
|
|
filterValue=req.get('filterValue', '');
|
|
|
|
queryResult=ztool.executeQuery(className, \
|
2013-08-21 05:35:30 -05:00
|
|
|
search=uiSearch.search, startNumber=startNumber, \
|
2013-06-27 04:57:39 -05:00
|
|
|
remember=True, sortBy=sortKey, sortOrder=sortOrder, \
|
|
|
|
filterKey=filterKey, filterValue=filterValue, \
|
|
|
|
refObject=refObject, refField=refField);
|
2013-08-21 15:25:27 -05:00
|
|
|
zobjects=queryResult.objects;
|
|
|
|
totalNumber=queryResult.totalNumber;
|
|
|
|
batchSize=queryResult.batchSize;
|
2013-07-23 03:29:39 -05:00
|
|
|
batchNumber=len(zobjects);
|
2013-06-27 04:57:39 -05:00
|
|
|
ajaxHookId='queryResult';
|
2013-07-15 04:23:29 -05:00
|
|
|
navBaseCall='askQueryResult(%s,%s,%s,%s,**v**)' % \
|
|
|
|
(q(ajaxHookId), q(ztool.absolute_url()), q(className), \
|
|
|
|
q(searchName));
|
2013-08-21 05:35:30 -05:00
|
|
|
showNewSearch=showNewSearch|True;
|
|
|
|
enableLinks=enableLinks|True;
|
|
|
|
newSearchUrl='%s/search?className=%s%s' % \
|
2013-06-27 04:57:39 -05:00
|
|
|
(ztool.absolute_url(), className, refUrlPart);
|
|
|
|
showSubTitles=req.get('showSubTitles', 'true') == 'true';
|
|
|
|
resultMode=ztool.getResultMode(className)">
|
|
|
|
|
2013-07-23 03:29:39 -05:00
|
|
|
<x if="zobjects">
|
2013-06-27 04:57:39 -05:00
|
|
|
<!-- Display here POD templates if required. -->
|
2013-08-21 05:35:30 -05:00
|
|
|
<table var="fields=ztool.getResultPodFields(className);
|
2013-06-27 04:57:39 -05:00
|
|
|
layoutType='view'"
|
2013-08-21 05:35:30 -05:00
|
|
|
if="zobjects and fields" align=":dright">
|
2013-06-27 04:57:39 -05:00
|
|
|
<tr>
|
2013-07-23 03:29:39 -05:00
|
|
|
<td var="zobj=zobjects[0]; obj=zobj.appy()"
|
2013-08-21 05:35:30 -05:00
|
|
|
for="field in fields">:field.pxView</td>
|
2013-06-27 04:57:39 -05:00
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<!-- The title of the search -->
|
|
|
|
<p>
|
2013-08-21 05:35:30 -05:00
|
|
|
<x>:uiSearch.translated</x> (<x>:totalNumber</x>)
|
2013-06-27 04:57:39 -05:00
|
|
|
<x if="showNewSearch and (searchName == 'customSearch')"> —
|
|
|
|
<i><a href=":newSearchUrl">:_('search_new')</a></i>
|
|
|
|
</x>
|
|
|
|
</p>
|
|
|
|
<table width="100%">
|
|
|
|
<tr>
|
|
|
|
<!-- Search description -->
|
2013-08-21 05:35:30 -05:00
|
|
|
<td if="uiSearch.translatedDescr">
|
|
|
|
<span class="discreet">:uiSearch.translatedDescr</span><br/>
|
2013-06-27 04:57:39 -05:00
|
|
|
</td>
|
2013-08-21 05:35:30 -05:00
|
|
|
<!-- (Top) navigation -->
|
|
|
|
<td align=":dright" width="25%"><x>:tool.pxNavigate</x></td>
|
2013-06-27 04:57:39 -05:00
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<!-- Results, as a list or grid -->
|
|
|
|
<x var="columnLayouts=ztool.getResultColumnsLayouts(className, refInfo);
|
2013-08-21 05:35:30 -05:00
|
|
|
columns=ztool.getColumnsSpecifiers(className,columnLayouts, dir);
|
2013-06-27 04:57:39 -05:00
|
|
|
currentNumber=0">
|
2013-08-21 05:35:30 -05:00
|
|
|
<x if="resultMode == 'list'">:tool.pxQueryResultList</x>
|
|
|
|
<x if="resultMode != 'list'">:tool.pxQueryResultGrid</x>
|
2013-06-27 04:57:39 -05:00
|
|
|
</x>
|
|
|
|
|
2013-08-21 05:35:30 -05:00
|
|
|
<!-- (Bottom) navigation -->
|
|
|
|
<x>:tool.pxNavigate</x>
|
2013-06-27 04:57:39 -05:00
|
|
|
</x>
|
|
|
|
|
2013-07-23 03:29:39 -05:00
|
|
|
<x if="not zobjects">
|
2013-08-21 05:35:30 -05:00
|
|
|
<x>:_('query_no_result')</x>
|
2013-06-27 04:57:39 -05:00
|
|
|
<x if="showNewSearch and (searchName == 'customSearch')"><br/>
|
|
|
|
<i class="discreet"><a href=":newSearchUrl">:_('search_new')</a></i></x>
|
|
|
|
</x>
|
|
|
|
</div>''')
|
|
|
|
|
|
|
|
pxQuery = Px('''
|
2013-08-21 05:35:30 -05:00
|
|
|
<x var="className=req['className']; searchName=req.get('search', '');
|
|
|
|
cssJs=None">
|
|
|
|
<x>:tool.pxPagePrologue</x><x>:tool.pxQueryResult</x>
|
2013-06-27 04:57:39 -05:00
|
|
|
</x>''', template=AbstractWrapper.pxTemplate, hook='content')
|
2013-06-25 05:04:23 -05:00
|
|
|
|
2013-06-28 08:00:02 -05:00
|
|
|
pxSearch = Px('''
|
|
|
|
<x var="className=req['className'];
|
|
|
|
refInfo=req.get('ref', None);
|
|
|
|
searchInfo=ztool.getSearchInfo(className, refInfo);
|
|
|
|
cssJs={};
|
2013-08-21 05:35:30 -05:00
|
|
|
x=ztool.getCssJs(searchInfo.fields, 'edit', cssJs)">
|
2013-06-28 08:00:02 -05:00
|
|
|
|
|
|
|
<!-- Include type-specific CSS and JS. -->
|
|
|
|
<link for="cssFile in cssJs['css']" rel="stylesheet" type="text/css"
|
2013-07-20 12:56:17 -05:00
|
|
|
href=":url(cssFile)"/>
|
2013-06-28 08:00:02 -05:00
|
|
|
<script for="jsFile in cssJs['js']" type="text/javascript"
|
2013-07-20 12:56:17 -05:00
|
|
|
src=":url(jsFile)"></script>
|
2013-06-28 08:00:02 -05:00
|
|
|
|
|
|
|
<!-- Search title -->
|
|
|
|
<h1><x>:_('%s_plural'%className)</x> –
|
|
|
|
<x>:_('search_title')</x></h1>
|
|
|
|
<br/>
|
|
|
|
<!-- Form for searching objects of request/className. -->
|
|
|
|
<form name="search" action=":ztool.absolute_url()+'/do'" method="post">
|
|
|
|
<input type="hidden" name="action" value="SearchObjects"/>
|
|
|
|
<input type="hidden" name="className" value=":className"/>
|
|
|
|
<input if="refInfo" type="hidden" name="ref" value=":refInfo"/>
|
|
|
|
|
|
|
|
<table width="100%">
|
|
|
|
<tr for="searchRow in ztool.getGroupedSearchFields(searchInfo)"
|
|
|
|
valign="top">
|
2013-07-15 04:23:29 -05:00
|
|
|
<td for="field in searchRow"
|
|
|
|
var2="scolspan=field and field.scolspan or 1"
|
|
|
|
colspan=":scolspan"
|
2013-08-21 05:35:30 -05:00
|
|
|
width=":'%d%%' % ((100/searchInfo.nbOfColumns)*scolspan)">
|
2013-07-15 04:23:29 -05:00
|
|
|
<x if="field"
|
|
|
|
var2="name=field.name;
|
2013-08-21 05:35:30 -05:00
|
|
|
widgetName='w_%s' % name">:field.pxSearch</x>
|
2013-07-15 04:23:29 -05:00
|
|
|
<br class="discreet"/>
|
|
|
|
</td>
|
2013-06-28 08:00:02 -05:00
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<!-- Submit button -->
|
|
|
|
<p align=":dright"><br/>
|
|
|
|
<input type="submit" class="button" value=":_('search_button')"
|
2013-07-20 12:56:17 -05:00
|
|
|
style=":url('buttonSearch', bg=True)"/>
|
2013-06-28 08:00:02 -05:00
|
|
|
</p>
|
|
|
|
</form>
|
2013-07-15 04:23:29 -05:00
|
|
|
</x>''', template=AbstractWrapper.pxTemplate, hook='content')
|
2013-06-28 08:00:02 -05:00
|
|
|
|
|
|
|
pxImport = Px('''
|
|
|
|
<x var="className=req['className'];
|
|
|
|
importElems=ztool.getImportElements(className);
|
|
|
|
allAreImported=True">
|
2013-08-21 05:35:30 -05:00
|
|
|
<x>:tool.pxPagePrologue</x>
|
2013-06-28 08:00:02 -05:00
|
|
|
<script type="text/javascript"><![CDATA[
|
|
|
|
var importedElemsShown = false;
|
|
|
|
function toggleViewableElements() {
|
|
|
|
var rows = document.getElementsByName('importedElem');
|
|
|
|
var newDisplay = 'table-row';
|
|
|
|
if (isIe) newDisplay = 'block';
|
|
|
|
if (importedElemsShown) newDisplay = 'none';
|
|
|
|
for (var i=0; i<rows.length; i++) {
|
|
|
|
rows[i].style.display = newDisplay;
|
|
|
|
}
|
|
|
|
importedElemsShown = !importedElemsShown;
|
|
|
|
}
|
|
|
|
var checkBoxesChecked = true;
|
|
|
|
function toggleCheckboxes() {
|
|
|
|
var checkBoxes = document.getElementsByName('cbElem');
|
|
|
|
var newCheckValue = true;
|
|
|
|
if (checkBoxesChecked) newCheckValue = false;
|
|
|
|
for (var i=0; i<checkBoxes.length; i++) {
|
|
|
|
checkBoxes[i].checked = newCheckValue;
|
|
|
|
}
|
|
|
|
checkBoxesChecked = newCheckValue;
|
|
|
|
}
|
|
|
|
function importSingleElement(importPath) {
|
|
|
|
var f = document.forms['importElements'];
|
|
|
|
f.importPath.value = importPath;
|
|
|
|
f.submit();
|
|
|
|
}
|
|
|
|
function importManyElements() {
|
|
|
|
var f = document.forms['importElements'];
|
|
|
|
var importPaths = '';
|
|
|
|
// Get the values of the checkboxes
|
|
|
|
var checkBoxes = document.getElementsByName('cbElem');
|
|
|
|
for (var i=0; i<checkBoxes.length; i++) {
|
|
|
|
if (checkBoxes[i].checked) {
|
|
|
|
importPaths += checkBoxes[i].value + '|';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (! importPaths) alert(no_elem_selected);
|
|
|
|
else {
|
|
|
|
f.importPath.value = importPaths;
|
|
|
|
f.submit();
|
|
|
|
}
|
|
|
|
}]]>
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<!-- Form for importing several elements at once. -->
|
|
|
|
<form name="importElements"
|
|
|
|
action=":ztool.absolute_url()+'/do'" method="post">
|
|
|
|
<input type="hidden" name="action" value="ImportObjects"/>
|
|
|
|
<input type="hidden" name="className" value=":className"/>
|
|
|
|
<input type="hidden" name="importPath" value=""/>
|
|
|
|
</form>
|
|
|
|
|
2013-08-21 05:35:30 -05:00
|
|
|
<h1>:_('import_title')</h1><br/>
|
2013-06-28 08:00:02 -05:00
|
|
|
<table class="list" width="100%">
|
|
|
|
<tr>
|
|
|
|
<th for="columnHeader in importElems[0]">
|
2013-07-20 12:56:17 -05:00
|
|
|
<img if="loop.columnHeader.nb == 0" src=":url('eye')"
|
2013-07-15 06:39:05 -05:00
|
|
|
title="_('import_show_hide')" class="clickable"
|
2013-06-28 08:00:02 -05:00
|
|
|
onClick="toggleViewableElements()" align=":dleft" />
|
|
|
|
<x>:columnHeader</x>
|
|
|
|
</th>
|
|
|
|
<th></th>
|
2013-07-20 12:56:17 -05:00
|
|
|
<th width="20px"><img src=":url('select_elems')" class="clickable"
|
2013-07-15 04:23:29 -05:00
|
|
|
title=":_('select_delesect')" onClick="toggleCheckboxes()"/></th>
|
2013-06-28 08:00:02 -05:00
|
|
|
</tr>
|
2013-07-15 04:23:29 -05:00
|
|
|
<tr for="row in importElems[1]"
|
|
|
|
var2="alreadyImported=ztool.isAlreadyImported(className, row[0]);
|
2013-06-28 08:00:02 -05:00
|
|
|
allAreImported=allAreImported and alreadyImported;
|
|
|
|
odd=loop.row.odd"
|
2013-07-15 04:23:29 -05:00
|
|
|
id=":alreadyImported and 'importedElem' or 'notImportedElem'"
|
|
|
|
name=":alreadyImported and 'importedElem' or 'notImportedElem'"
|
|
|
|
style=":alreadyImported and 'display:none' or 'display:table-row'"
|
|
|
|
class=":odd and 'even' or 'odd'">
|
|
|
|
<td for="elem in row[1:]">:elem</td>
|
|
|
|
<td>
|
|
|
|
<input type="button" if="not alreadyImported"
|
|
|
|
onClick=":'importSingleElement(%s)' % q(row[0])"
|
|
|
|
value=":_('query_import')"/>
|
|
|
|
<x if="alreadyImported">:_('import_already')</x>
|
|
|
|
</td>
|
|
|
|
<td align="center">
|
|
|
|
<input if="not alreadyImported" type="checkbox" checked="checked"
|
|
|
|
id="cbElem" name="cbElem" value="row[0]"/>
|
|
|
|
</td>
|
|
|
|
</tr>
|
2013-06-28 08:00:02 -05:00
|
|
|
<tr if="not importElems[1] or allAreImported">
|
|
|
|
<td colspan="15">:_('query_no_result')</td></tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<!-- Button for importing several elements at once. -->
|
|
|
|
<p align=":dright"><br/>
|
|
|
|
<input if="importElems[1] and not allAreImported"
|
|
|
|
type="button" onClick="importManyElements()"
|
|
|
|
value=":_('import_many')"/>
|
|
|
|
</p>
|
|
|
|
</x>''', template=AbstractWrapper.pxTemplate, hook='content')
|
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
def validPythonWithUno(self, value):
|
|
|
|
'''This method represents the validator for field unoEnabledPython.'''
|
|
|
|
if value:
|
|
|
|
if not os.path.exists(value):
|
|
|
|
return FILE_NOT_FOUND % value
|
|
|
|
if not os.path.isfile(value):
|
|
|
|
return VALUE_NOT_FILE % value
|
|
|
|
if not os.path.basename(value).startswith('python'):
|
|
|
|
return NO_PYTHON % value
|
|
|
|
if os.system('%s -c "import uno"' % value):
|
|
|
|
return NOT_UNO_ENABLED_PYTHON % value
|
2011-03-18 10:52:15 -05:00
|
|
|
return True
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2012-05-05 10:04:19 -05:00
|
|
|
def isManager(self):
|
2012-11-26 06:58:27 -06:00
|
|
|
'''Some pages on the tool can only be accessed by managers.'''
|
2012-05-05 10:04:19 -05:00
|
|
|
if self.user.has_role('Manager'): return 'view'
|
|
|
|
|
2012-05-08 07:49:45 -05:00
|
|
|
def isManagerEdit(self):
|
2012-11-26 06:58:27 -06:00
|
|
|
'''Some pages on the tool can only be accessed by managers, also in
|
|
|
|
edit mode.'''
|
2012-05-08 07:49:45 -05:00
|
|
|
if self.user.has_role('Manager'): return True
|
|
|
|
|
2012-07-18 14:58:11 -05:00
|
|
|
def computeConnectedUsers(self):
|
|
|
|
'''Computes a table showing users that are currently connected.'''
|
2013-04-26 19:15:44 -05:00
|
|
|
res = '<table cellpadding="0" cellspacing="0" class="list">' \
|
|
|
|
'<tr><th></th><th>%s</th></tr>' % \
|
|
|
|
self.translate('last_user_access')
|
2012-07-18 14:58:11 -05:00
|
|
|
rows = []
|
2013-09-09 16:14:50 -05:00
|
|
|
for userId, lastAccess in self.o.loggedUsers.items():
|
2012-07-18 14:58:11 -05:00
|
|
|
user = self.search1('User', noSecurity=True, login=userId)
|
|
|
|
if not user: continue # Could have been deleted in the meanwhile
|
|
|
|
fmt = '%s (%s)' % (self.dateFormat, self.hourFormat)
|
|
|
|
access = time.strftime(fmt, time.localtime(lastAccess))
|
|
|
|
rows.append('<tr><td><a href="%s">%s</a></td><td>%s</td></tr>' % \
|
|
|
|
(user.o.absolute_url(), user.title,access))
|
|
|
|
return res + '\n'.join(rows) + '</table>'
|
|
|
|
|
2013-02-05 01:51:25 -06:00
|
|
|
podOutputFormats = ('odt', 'pdf', 'doc', 'rtf', 'ods', 'xls')
|
2011-01-28 07:36:30 -06:00
|
|
|
def getPodOutputFormats(self):
|
|
|
|
'''Gets the available output formats for POD documents.'''
|
|
|
|
return [(of, self.translate(of)) for of in self.podOutputFormats]
|
|
|
|
|
2012-05-05 10:04:19 -05:00
|
|
|
def getInitiator(self, field=False):
|
2009-06-29 07:06:01 -05:00
|
|
|
'''Retrieves the object that triggered the creation of the object
|
2012-05-05 10:04:19 -05:00
|
|
|
being currently created (if any), or the name of the field in this
|
|
|
|
object if p_field is given.'''
|
2011-11-25 11:01:20 -06:00
|
|
|
nav = self.o.REQUEST.get('nav', '')
|
2012-05-05 10:04:19 -05:00
|
|
|
if not nav or not nav.startswith('ref.'): return
|
|
|
|
if not field: return self.getObject(nav.split('.')[1])
|
|
|
|
return nav.split('.')[2].split(':')[0]
|
2009-11-17 03:05:19 -06:00
|
|
|
|
|
|
|
def getObject(self, uid):
|
|
|
|
'''Allow to retrieve an object from its unique identifier p_uid.'''
|
|
|
|
return self.o.getObject(uid, appy=True)
|
2009-12-01 13:36:59 -06:00
|
|
|
|
|
|
|
def getDiskFolder(self):
|
|
|
|
'''Returns the disk folder where the Appy application is stored.'''
|
2012-05-08 07:49:45 -05:00
|
|
|
return self.o.config.diskFolder
|
|
|
|
|
|
|
|
def getClass(self, zopeName):
|
|
|
|
'''Gets the Appy class corresponding to technical p_zopeName.'''
|
|
|
|
return self.o.getAppyClass(zopeName)
|
2010-10-14 07:43:56 -05:00
|
|
|
|
|
|
|
def getAttributeName(self, attributeType, klass, attrName=None):
|
|
|
|
'''Some names of Tool attributes are not easy to guess. For example,
|
|
|
|
the attribute that stores the names of the columns to display in
|
|
|
|
query results for class A that is in package x.y is
|
[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.
2012-10-31 07:20:25 -05:00
|
|
|
"tool.resultColumnsForx_y_A". This method generates the attribute
|
2010-10-14 07:43:56 -05:00
|
|
|
name based on p_attributeType, a p_klass from the application, and a
|
[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.
2012-10-31 07:20:25 -05:00
|
|
|
p_attrName (given only if needed). p_attributeType may be:
|
2010-10-14 07:43:56 -05:00
|
|
|
|
|
|
|
"podTemplate"
|
|
|
|
Stores the pod template for p_attrName.
|
|
|
|
|
|
|
|
"formats"
|
|
|
|
Stores the output format(s) of a given pod template for
|
|
|
|
p_attrName.
|
|
|
|
|
|
|
|
"resultColumns"
|
|
|
|
Stores the list of columns that must be shown when displaying
|
2013-02-18 08:03:26 -06:00
|
|
|
instances of a given root p_klass.
|
2010-10-14 07:43:56 -05:00
|
|
|
|
|
|
|
"numberOfSearchColumns"
|
|
|
|
Determines in how many columns the search screen for p_klass
|
|
|
|
is rendered.
|
|
|
|
|
|
|
|
"searchFields"
|
|
|
|
Determines, among all indexed fields for p_klass, which one will
|
|
|
|
really be used in the search screen.
|
|
|
|
'''
|
|
|
|
fullClassName = self.o.getPortalType(klass)
|
|
|
|
res = '%sFor%s' % (attributeType, fullClassName)
|
|
|
|
if attrName: res += '_%s' % attrName
|
|
|
|
return res
|
2011-01-14 02:06:25 -06:00
|
|
|
|
|
|
|
def getAvailableLanguages(self):
|
|
|
|
'''Returns the list of available languages for this application.'''
|
|
|
|
return [(t.id, t.title) for t in self.translations]
|
2011-01-18 08:48:55 -06:00
|
|
|
|
|
|
|
def convert(self, fileName, format):
|
|
|
|
'''Launches a UNO-enabled Python interpreter as defined in the self for
|
|
|
|
converting, using OpenOffice in server mode, a file named p_fileName
|
|
|
|
into an output p_format.'''
|
|
|
|
convScript = '%s/pod/converter.py' % os.path.dirname(appy.__file__)
|
|
|
|
cmd = '%s %s "%s" %s -p%d' % (self.unoEnabledPython, convScript,
|
|
|
|
fileName, format, self.openOfficePort)
|
|
|
|
self.log('Executing %s...' % cmd)
|
|
|
|
return executeCommand(cmd) # The result can contain an error message
|
2011-09-08 09:33:16 -05:00
|
|
|
|
2013-02-28 16:00:06 -06:00
|
|
|
def sendMail(self, to, subject, body, attachments=None):
|
|
|
|
'''Sends a mail. See doc for appy.gen.mail.sendMail.'''
|
|
|
|
sendMail(self, to, subject, body, attachments=attachments)
|
|
|
|
|
2012-03-19 11:00:44 -05:00
|
|
|
def refreshCatalog(self, startObject=None):
|
|
|
|
'''Reindex all Appy objects. For some unknown reason, method
|
|
|
|
catalog.refreshCatalog is not able to recatalog Appy objects.'''
|
|
|
|
if not startObject:
|
2012-05-14 10:35:34 -05:00
|
|
|
# This is a global refresh. Clear the catalog completely, and then
|
2012-03-19 11:00:44 -05:00
|
|
|
# reindex all Appy-managed objects, ie those in folders "config"
|
|
|
|
# and "data".
|
|
|
|
# First, clear the catalog.
|
2012-09-04 11:00:22 -05:00
|
|
|
self.log('Recomputing the whole catalog...')
|
2012-03-19 11:00:44 -05:00
|
|
|
app = self.o.getParentNode()
|
|
|
|
app.catalog._catalog.clear()
|
|
|
|
nb = 1
|
2012-09-04 11:00:22 -05:00
|
|
|
failed = []
|
2012-03-19 11:00:44 -05:00
|
|
|
for obj in app.config.objectValues():
|
2012-09-04 11:00:22 -05:00
|
|
|
subNb, subFailed = self.refreshCatalog(startObject=obj)
|
|
|
|
nb += subNb
|
|
|
|
failed += subFailed
|
|
|
|
try:
|
|
|
|
app.config.reindex()
|
|
|
|
except:
|
|
|
|
failed.append(app.config)
|
2012-03-19 11:00:44 -05:00
|
|
|
# Then, refresh objects in the "data" folder.
|
|
|
|
for obj in app.data.objectValues():
|
2012-09-04 11:00:22 -05:00
|
|
|
subNb, subFailed = self.refreshCatalog(startObject=obj)
|
|
|
|
nb += subNb
|
|
|
|
failed += subFailed
|
|
|
|
# Re-try to index all objects for which reindexation has failed.
|
|
|
|
for obj in failed: obj.reindex()
|
|
|
|
if failed:
|
|
|
|
failMsg = ' (%d retried)' % len(failed)
|
|
|
|
else:
|
|
|
|
failMsg = ''
|
|
|
|
self.log('%d object(s) were reindexed%s.' % (nb, failMsg))
|
2012-03-19 11:00:44 -05:00
|
|
|
else:
|
|
|
|
nb = 1
|
2012-09-04 11:00:22 -05:00
|
|
|
failed = []
|
2012-03-19 11:00:44 -05:00
|
|
|
for obj in startObject.objectValues():
|
2012-09-04 11:00:22 -05:00
|
|
|
subNb, subFailed = self.refreshCatalog(startObject=obj)
|
|
|
|
nb += subNb
|
|
|
|
failed += subFailed
|
|
|
|
try:
|
|
|
|
startObject.reindex()
|
|
|
|
except Exception, e:
|
|
|
|
failed.append(startObject)
|
|
|
|
return nb, failed
|
2013-02-05 01:51:25 -06:00
|
|
|
|
|
|
|
def validate(self, new, errors):
|
|
|
|
'''Validates that uploaded POD templates and output types are
|
|
|
|
compatible.'''
|
|
|
|
page = self.request.get('page', 'main')
|
|
|
|
if page == 'documents':
|
|
|
|
# Check that uploaded templates and output formats are compatible.
|
|
|
|
for fieldName in dir(new):
|
|
|
|
# Ignore fields which are not POD templates.
|
|
|
|
if not fieldName.startswith('podTemplate'): continue
|
|
|
|
# Get the file name, either from the newly uploaded file or
|
|
|
|
# from the existing file stored in the database.
|
|
|
|
if getattr(new, fieldName):
|
|
|
|
fileName = getattr(new, fieldName).filename
|
|
|
|
else:
|
|
|
|
fileName = getattr(self, fieldName).name
|
|
|
|
# Get the extension of the uploaded file.
|
|
|
|
ext = os.path.splitext(fileName)[1][1:]
|
|
|
|
# Get the chosen output formats for this template.
|
|
|
|
formatsFieldName = 'formatsFor%s' % fieldName[14:]
|
|
|
|
formats = getattr(new, formatsFieldName)
|
|
|
|
error = False
|
|
|
|
if ext == 'odt':
|
|
|
|
error = ('ods' in formats) or ('xls' in formats)
|
|
|
|
elif ext == 'ods':
|
|
|
|
error = ('odt' in formats) or ('pdf' in formats) or \
|
|
|
|
('doc' in formats) or ('rtf' in formats)
|
|
|
|
if error:
|
|
|
|
msg = 'This (these) format(s) cannot be used with ' \
|
|
|
|
'this template.'
|
|
|
|
setattr(errors, formatsFieldName, msg)
|
|
|
|
return self._callCustom('validate', new, errors)
|
2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|