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
|
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):
|
|
|
|
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.'''
|
|
|
|
res = '<table cellpadding="0" cellspacing="0" class="list"><tr>' \
|
|
|
|
'<th></th><th>%s</th></tr>' % self.translate('last_user_access')
|
|
|
|
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
|
|
|
|
|
|
|
"enableAdvancedSearch"
|
|
|
|
Determines if the advanced search screen must be enabled for
|
|
|
|
p_klass.
|
|
|
|
|
|
|
|
"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
|
|
|
# ------------------------------------------------------------------------------
|