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
|
2012-07-18 14:58:11 -05:00
|
|
|
from appy.gen.installer import loggedUsers
|
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-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"
|
|
|
|
if="zobj.showField(field.name, 'result')">field.pxView</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('''
|
|
|
|
<table class="list" width="100%">
|
|
|
|
<!-- Headers, with filters and sort arrows -->
|
|
|
|
<tr if="showHeaders">
|
2013-07-15 04:23:29 -05:00
|
|
|
<th for="column in columns"
|
|
|
|
var2="widget=column['field'];
|
|
|
|
sortable=ztool.isSortable(field.name, className, 'search');
|
|
|
|
filterable=widget.filterable"
|
|
|
|
width=":column['width']" align=":column['align']">
|
|
|
|
<x>::ztool.truncateText(_(field.labelId))</x>
|
|
|
|
<x>:self.pxSortAndFilter</x><x>:self.pxShowDetails</x>
|
|
|
|
</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"
|
|
|
|
var2="widget=column['field']" id=":'field_%s' % field.name"
|
|
|
|
width=":column['width']"
|
|
|
|
align=":column['align']">:self.pxQueryField</td>
|
|
|
|
</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"
|
|
|
|
var2="widget = column['field']">:self.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', '');
|
|
|
|
searchDescr=ztool.getSearch(className, searchName, descr=True);
|
|
|
|
sortKey=req.get('sortKey', '');
|
|
|
|
sortOrder=req.get('sortOrder', 'asc');
|
|
|
|
filterKey=req.get('filterKey', '');
|
|
|
|
filterValue=req.get('filterValue', '');
|
|
|
|
queryResult=ztool.executeQuery(className, \
|
|
|
|
search=searchDescr['search'], startNumber=startNumber, \
|
|
|
|
remember=True, sortBy=sortKey, sortOrder=sortOrder, \
|
|
|
|
filterKey=filterKey, filterValue=filterValue, \
|
|
|
|
refObject=refObject, refField=refField);
|
2013-07-23 03:29:39 -05:00
|
|
|
zobjects=queryResult['objects'];
|
2013-06-27 04:57:39 -05:00
|
|
|
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-06-27 04:57:39 -05:00
|
|
|
newSearchUrl='%s/ui/search?className=%s%s' % \
|
|
|
|
(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. -->
|
|
|
|
<table var="widgets=ztool.getResultPodFields(className);
|
|
|
|
layoutType='view'"
|
2013-07-23 03:29:39 -05:00
|
|
|
if="zobjects and widgets" 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()"
|
|
|
|
for="field in widgets">:field.pxView</td>
|
2013-06-27 04:57:39 -05:00
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<!-- The title of the search -->
|
|
|
|
<p>
|
2013-07-23 03:29:39 -05:00
|
|
|
<x>:searchDescr['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 -->
|
|
|
|
<td if="searchDescr['translatedDescr']">
|
|
|
|
<span class="discreet">:searchDescr['translatedDescr']</span><br/>
|
|
|
|
</td>
|
|
|
|
<!-- Appy (top) navigation -->
|
|
|
|
<td align=":dright" width="25%"><x>:self.pxAppyNavigate</x></td>
|
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<!-- Results, as a list or grid -->
|
|
|
|
<x var="columnLayouts=ztool.getResultColumnsLayouts(className, refInfo);
|
2013-07-23 03:29:39 -05:00
|
|
|
columns=zobjects[0].getColumnsSpecifiers(columnLayouts, dir);
|
2013-06-27 04:57:39 -05:00
|
|
|
currentNumber=0">
|
|
|
|
<x if="resultMode == 'list'">:self.pxQueryResultList</x>
|
|
|
|
<x if="resultMode != 'list'">:self.pxQueryResultGrid</x>
|
|
|
|
</x>
|
|
|
|
|
|
|
|
<!-- Appy (bottom) navigation -->
|
|
|
|
<x>:self.pxAppyNavigate</x>
|
|
|
|
</x>
|
|
|
|
|
2013-07-23 03:29:39 -05:00
|
|
|
<x if="not zobjects">
|
2013-06-27 04:57:39 -05:00
|
|
|
<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('''
|
|
|
|
<x var="className=req['className'];
|
|
|
|
searchName=req.get('search', '');
|
|
|
|
cssJs=None;
|
|
|
|
showNewSearch=True;
|
|
|
|
showHeaders=True;
|
|
|
|
enableLinks=True">
|
|
|
|
<x>:self.pxPagePrologue</x><x>:self.pxQueryResult</x>
|
|
|
|
</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={};
|
|
|
|
x=ztool.getCssJs(searchInfo['fields'], 'edit', cssJs)">
|
|
|
|
|
|
|
|
<!-- 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"
|
|
|
|
width=":'%d%%' % ((100/searchInfo['nbOfColumns'])*scolspan)">
|
|
|
|
<x if="field"
|
|
|
|
var2="name=field.name;
|
|
|
|
widgetName='w_%s' % name">field.pxSearch</x>
|
|
|
|
<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">
|
|
|
|
<x>:self.pxPagePrologue</x>
|
|
|
|
<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>
|
|
|
|
|
|
|
|
<h1>:_('import_title')"></h1><br/>
|
|
|
|
<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 = []
|
|
|
|
for userId, lastAccess in loggedUsers.items():
|
|
|
|
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)
|
|
|
|
|
2011-09-08 09:33:16 -05:00
|
|
|
def refreshSecurity(self):
|
|
|
|
'''Refreshes, on every object in the database, security-related,
|
|
|
|
workflow-managed information.'''
|
|
|
|
context = {'nb': 0}
|
|
|
|
for className in self.o.getProductConfig().allClassNames:
|
|
|
|
self.compute(className, context=context, noSecurity=True,
|
|
|
|
expression="ctx['nb'] += int(obj.o.refreshSecurity())")
|
|
|
|
msg = 'Security refresh: %d object(s) updated.' % context['nb']
|
|
|
|
self.log(msg)
|
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
|
|
|
# ------------------------------------------------------------------------------
|