Added a new feature allowing to create objects by importing them from external sources instead of from filling a web form.

This commit is contained in:
Gaetan Delannay 2009-10-20 16:57:00 +02:00
parent cbc7d257d4
commit 4c4b2d0f87
17 changed files with 502 additions and 181 deletions

View file

@ -8,11 +8,25 @@ r, w, d = ('read', 'write', 'delete')
# Descriptor classes used for refining descriptions of elements in types # Descriptor classes used for refining descriptions of elements in types
# (pages, groups,...) ---------------------------------------------------------- # (pages, groups,...) ----------------------------------------------------------
class Page: class Page:
'''Used for describing a page, its related phase, show condition, etc.'''
def __init__(self, name, phase='main', show=True): def __init__(self, name, phase='main', show=True):
self.name = name self.name = name
self.phase = phase self.phase = phase
self.show = show self.show = show
class Import:
'''Used for describing the place where to find the data to use for creating
an object.'''
def __init__(self, path, columnMethod=None, columnHeaders=(),
sortMethod=None):
self.id = 'import'
self.path = path
self.columnMethod = columnMethod
# This method allows to split every element into subElements that can
# be shown as column values in a table.
self.columnHeaders = columnHeaders
self.sortMethod = sortMethod
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Type: class Type:
'''Basic abstract class for defining any appy type.''' '''Basic abstract class for defining any appy type.'''

View file

@ -102,14 +102,24 @@ class Generator(AbstractGenerator):
msg('move_up', '', msg.REF_MOVE_UP), msg('move_up', '', msg.REF_MOVE_UP),
msg('move_down', '', msg.REF_MOVE_DOWN), msg('move_down', '', msg.REF_MOVE_DOWN),
msg('query_create', '', msg.QUERY_CREATE), msg('query_create', '', msg.QUERY_CREATE),
msg('query_import', '', msg.QUERY_IMPORT),
msg('query_no_result', '', msg.QUERY_NO_RESULT), msg('query_no_result', '', msg.QUERY_NO_RESULT),
msg('query_consult_all', '', msg.QUERY_CONSULT_ALL), msg('query_consult_all', '', msg.QUERY_CONSULT_ALL),
msg('import_title', '', msg.IMPORT_TITLE),
msg('import_show_hide', '', msg.IMPORT_SHOW_HIDE),
msg('import_already', '', msg.IMPORT_ALREADY),
msg('import_many', '', msg.IMPORT_MANY),
msg('import_done', '', msg.IMPORT_DONE),
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX), msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
msg('bad_int', '', msg.BAD_INT), msg('bad_int', '', msg.BAD_INT),
msg('bad_float', '', msg.BAD_FLOAT), msg('bad_float', '', msg.BAD_FLOAT),
msg('bad_email', '', msg.BAD_EMAIL), msg('bad_email', '', msg.BAD_EMAIL),
msg('bad_url', '', msg.BAD_URL), msg('bad_url', '', msg.BAD_URL),
msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC), msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC),
msg('select_delesect', '', msg.SELECT_DESELECT),
msg('no_elem_selected', '', msg.NO_SELECTION),
msg('delete_confirm', '', msg.DELETE_CONFIRM),
msg('delete_done', '', msg.DELETE_DONE),
] ]
# Create basic files (config.py, Install.py, etc) # Create basic files (config.py, Install.py, etc)
self.generateTool() self.generateTool()

View file

@ -133,7 +133,7 @@ class PloneInstaller:
updateRolesForPermission('Add portal content', tuple(allCreators), updateRolesForPermission('Add portal content', tuple(allCreators),
appFolder) appFolder)
# Creates the "appy" Directory view # Creates the "appy" Directory view
if not hasattr(site.aq_base, 'appy'): if not hasattr(site.aq_base, 'skyn'):
addDirView = self.ploneStuff['manage_addDirectoryView'] addDirView = self.ploneStuff['manage_addDirectoryView']
addDirView(site, appy.getPath() + '/gen/plone25/skin',id='skyn') addDirView(site, appy.getPath() + '/gen/plone25/skin',id='skyn')
@ -266,18 +266,15 @@ class PloneInstaller:
current.append(self.toolInstanceName) current.append(self.toolInstanceName)
nvProps.manage_changeProperties(**{'idsNotToList': current}) nvProps.manage_changeProperties(**{'idsNotToList': current})
# Remove workflow for the tool
#wfTool = self.ploneSite.portal_workflow
#wfTool.setChainForPortalTypes([self.toolName], '')
# Create the default flavour
self.tool = getattr(self.ploneSite, self.toolInstanceName) self.tool = getattr(self.ploneSite, self.toolInstanceName)
self.appyTool = self.tool._appy_getWrapper(force=True) self.appyTool = self.tool._appy_getWrapper(force=True)
if self.reinstall: if self.reinstall:
self.tool.at_post_edit_script() self.tool.createOrUpdate(False)
else: else:
self.tool.at_post_create_script() self.tool.createOrUpdate(True)
if not self.appyTool.flavours: if not self.appyTool.flavours:
# Create the default flavour
self.appyTool.create('flavours', title=self.productName, number=1) self.appyTool.create('flavours', title=self.productName, number=1)
self.updatePodTemplates() self.updatePodTemplates()

View file

@ -14,6 +14,7 @@ NOT_UNO_ENABLED_PYTHON = '"%s" is not a UNO-enabled Python interpreter. ' \
'To check if a Python interpreter is UNO-enabled, ' \ 'To check if a Python interpreter is UNO-enabled, ' \
'launch it and type "import uno". If you have no ' \ 'launch it and type "import uno". If you have no ' \
'ImportError exception it is ok.' 'ImportError exception it is ok.'
jsMessages = ('no_elem_selected', 'delete_confirm')
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class ToolMixin(AbstractMixin): class ToolMixin(AbstractMixin):
@ -206,4 +207,91 @@ class ToolMixin(AbstractMixin):
parent = obj.getParentNode() parent = obj.getParentNode()
if parent.id == 'skyn': return parent.getParentNode() if parent.id == 'skyn': return parent.getParentNode()
return rq['PUBLISHED'] return rq['PUBLISHED']
def getAppyClass(self, contentType):
'''Gets the Appy Python class that is related to p_contentType.'''
# Retrieve first the Archetypes class corresponding to p_ContentType
portalType = self.portal_types.get(contentType)
atClassName = portalType.getProperty('content_meta_type')
appName = self.getProductConfig().PROJECTNAME
exec 'from Products.%s.%s import %s as atClass' % \
(appName, atClassName, atClassName)
# Get then the Appy Python class
return atClass.wrapperClass.__bases__[-1]
def getCreateMeans(self, contentTypeOrAppyClass):
'''Gets the different ways objects of p_contentTypeOrAppyClass (which
can be a Plone content type or a Appy class) can be created
(via a web form, by importing external data, etc). Result is a
dict whose keys are strings (ie "form", "import"...) and whose
values are additional data bout the particular mean.'''
pythonClass = contentTypeOrAppyClass
if isinstance(contentTypeOrAppyClass, basestring):
pythonClass = self.getAppyClass(pythonClass)
res = {}
if not pythonClass.__dict__.has_key('create'):
res['form'] = None
# No additional data for this means, which is the default one.
else:
means = pythonClass.create
if means:
if isinstance(means, basestring): res[means] = None
elif isinstance(means, list) or isinstance(means, tuple):
for mean in means:
if isinstance(mean, basestring):
res[mean] = None
else:
res[mean.id] = mean.__dict__
else:
res[means.id] = means.__dict__
return res
def getImportElements(self, contentType):
'''Returns the list of elements that can be imported from p_path for
p_contentType.'''
appyClass = self.getAppyClass(contentType)
importParams = self.getCreateMeans(appyClass)['import']
columnMethod = importParams['columnMethod'].__get__('')
sortMethod = importParams['sortMethod']
if sortMethod: sortMethod = sortMethod.__get__('')
elems = []
for elem in os.listdir(importParams['path']):
elemFullPath = os.path.join(importParams['path'], elem)
niceElem = columnMethod(elemFullPath)
niceElem.insert(0, elemFullPath) # To the result, I add the full
# path of the elem, which will not be shown.
elems.append(niceElem)
if sortMethod:
elems = sortMethod(elems)
return [importParams['columnHeaders'], elems]
def onImportObjects(self):
'''This method is called when the user wants to create objects from
external data.'''
rq = self.REQUEST
appyClass = self.getAppyClass(rq.get('type_name'))
importPaths = rq.get('importPath').split('|')
appFolder = self.getAppFolder()
for importPath in importPaths:
if not importPath: continue
objectId = os.path.basename(importPath)
self.appy().create(appyClass, id=objectId)
self.plone_utils.addPortalMessage(self.translate('import_done'))
return rq.RESPONSE.redirect(rq['HTTP_REFERER'])
def isAlreadyImported(self, contentType, importPath):
appFolder = self.getAppFolder()
objectId = os.path.basename(importPath)
if hasattr(appFolder.aq_base, objectId):
return True
else:
return False
def getJavascriptMessages(self):
'''Returns the translated version of messages that must be shown in
Javascript popups.'''
res = ''
for msg in jsMessages:
res += 'var %s = "%s";\n' % (msg, self.translate(msg))
return res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -20,6 +20,10 @@ class AbstractMixin:
inherits from this class. It contains basic functions allowing to inherits from this class. It contains basic functions allowing to
minimize the amount of generated code.''' minimize the amount of generated code.'''
def getAppyAttribute(self, name):
'''Returns method or attribute value corresponding to p_name.'''
return eval('self.%s' % name)
def createOrUpdate(self, created): def createOrUpdate(self, created):
'''This method creates (if p_created is True) or updates an object. '''This method creates (if p_created is True) or updates an object.
In the case of an object creation, p_self is a temporary object In the case of an object creation, p_self is a temporary object
@ -53,7 +57,31 @@ class AbstractMixin:
if obj._appy_meta_type == 'tool': self.unindexObject() if obj._appy_meta_type == 'tool': self.unindexObject()
else: obj.reindexObject() else: obj.reindexObject()
return obj return obj
def delete(self):
'''This methods is self's suicide.'''
self.getParentNode().manage_delObjects([self.id])
def onCreate(self):
'''This method is called when a user wants to create a root object in
the application folder or an object through a reference field.'''
rq = self.REQUEST
if rq.get('initiator', None):
# The object to create will be linked to an initiator object through
# a ref field.
initiatorRes=self.uid_catalog.searchResults(UID=rq.get('initiator'))
rq.SESSION['initiator'] = rq.get('initiator')
rq.SESSION['initiatorField'] = rq.get('field')
rq.SESSION['initiatorTarget'] = rq.get('type_name')
if self._appy_meta_type == 'tool':
baseUrl = self.getAppFolder().absolute_url()
else:
baseUrl = self.absolute_url()
objId = self.generateUniqueId(rq.get('type_name'))
urlBack = '%s/portal_factory/%s/%s/skyn/edit' % \
(baseUrl, rq.get('type_name'), objId)
return rq.RESPONSE.redirect(urlBack)
def onUpdate(self): def onUpdate(self):
'''This method is executed when a user wants to update an object. '''This method is executed when a user wants to update an object.
The object may be a temporary object created by portal_factory in The object may be a temporary object created by portal_factory in
@ -106,6 +134,13 @@ class AbstractMixin:
rq.set('fieldset', rq.get('nextPage')) rq.set('fieldset', rq.get('nextPage'))
return obj.skyn.edit(obj) return obj.skyn.edit(obj)
def onDelete(self):
rq = self.REQUEST
msg = self.translate('delete_done')
self.delete()
self.plone_utils.addPortalMessage(msg)
rq.RESPONSE.redirect(rq['HTTP_REFERER'])
def getAppyType(self, fieldName): def getAppyType(self, fieldName):
'''Returns the Appy type corresponding to p_fieldName.''' '''Returns the Appy type corresponding to p_fieldName.'''
res = None res = None
@ -407,7 +442,7 @@ class AbstractMixin:
else: else:
return res return res
def changeAppyRefOrder(self, fieldName, objectUid, newIndex, isDelta): def changeRefOrder(self, fieldName, objectUid, newIndex, isDelta):
'''This method changes the position of object with uid p_objectUid in '''This method changes the position of object with uid p_objectUid in
reference field p_fieldName to p_newIndex i p_isDelta is False, or reference field p_fieldName to p_newIndex i p_isDelta is False, or
to actualIndex+p_newIndex if p_isDelta is True.''' to actualIndex+p_newIndex if p_isDelta is True.'''
@ -421,6 +456,27 @@ class AbstractMixin:
pass # To implement later on pass # To implement later on
sortedObjectsUids.insert(newIndex, objectUid) sortedObjectsUids.insert(newIndex, objectUid)
def onChangeRefOrder(self):
'''This method is called when the user wants to change order of an
item in a reference field.'''
rq = self.REQUEST
# Move the item up (-1), down (+1) or at a given position ?
move = -1 # Move up
isDelta = True
if rq.get('moveDown.x', None) != None:
move = 1 # Move down
elif rq.get('moveSeveral.x', None) != None:
try:
move = int(rq.get('moveValue'))
# In this case, it is not a delta value; it is the new position
# where the item must be moved.
isDelta = False
except ValueError:
self.plone_utils.addPortalMessage(
self.translate('ref_invalid_index'))
self.changeRefOrder(rq['fieldName'], rq['refObjectUid'], move, isDelta)
return rq.RESPONSE.redirect(rq['HTTP_REFERER'])
def getWorkflow(self, appy=True): def getWorkflow(self, appy=True):
'''Returns the Appy workflow instance that is relevant for this '''Returns the Appy workflow instance that is relevant for this
object. If p_appy is False, it returns the DC workflow.''' object. If p_appy is False, it returns the DC workflow.'''
@ -511,6 +567,38 @@ class AbstractMixin:
self.reindexObject() self.reindexObject()
return res return res
def onExecuteAppyAction(self):
'''This method is called every time a user wants to execute an Appy
action on an object.'''
rq = self.REQUEST
res, msg = self.executeAppyAction(rq['fieldName'])
if not msg:
# Use the default i18n messages
suffix = 'ko'
if res:
suffix = 'ok'
label='%s_action_%s' % (self.getLabelPrefix(rq['fieldName']),suffix)
msg = self.translate(label)
self.plone_utils.addPortalMessage(msg)
return rq.RESPONSE.redirect(rq['HTTP_REFERER'])
def onTriggerTransition(self):
'''This method is called whenever a user wants to trigger a workflow
transition on an object.'''
rq = self.REQUEST
self.portal_workflow.doActionFor(self, rq['workflow_action'],
comment = rq.get('comment', ''))
# Where to redirect the user back ?
urlBack = rq['HTTP_REFERER']
if urlBack.find('?') != -1:
# Remove params; this way, the user may be redirected to correct
# phase when relevant.
urlBack = urlBack[:urlBack.find('?')]
msg = self.translate(u'Your content\'s status has been modified.',
domain='plone')
self.plone_utils.addPortalMessage(msg)
return rq.RESPONSE.redirect(urlBack)
def callAppySelect(self, selectMethod, brains): def callAppySelect(self, selectMethod, brains):
'''Selects objects from a Reference field.''' '''Selects objects from a Reference field.'''
if selectMethod: if selectMethod:

View file

@ -2,63 +2,14 @@
##bind context=context ##bind context=context
##parameters=action ##parameters=action
rq = context.REQUEST rq = context.REQUEST
urlBack = rq['HTTP_REFERER']
if action == 'create': # Get the object impacted by the action.
# A user wants to create an object. if rq.get('objectUid', None):
if rq.get('initiator', None):
# The object to create will be linked to an initiator object through a
# ref field.
initiatorRes= context.uid_catalog.searchResults(UID=rq.get('initiator'))
rq.SESSION['initiator'] = rq.get('initiator')
rq.SESSION['initiatorField'] = rq.get('field')
rq.SESSION['initiatorTarget'] = rq.get('type_name')
objId = context.generateUniqueId(rq.get('type_name'))
urlBack = '%s/portal_factory/%s/%s/skyn/edit' % \
(context.getParentNode().absolute_url(), rq.get('type_name'), objId)
elif action == 'edit': return context.getParentNode().onUpdate()
elif action == 'appyAction':
obj = context.uid_catalog(UID=rq['objectUid'])[0].getObject() obj = context.uid_catalog(UID=rq['objectUid'])[0].getObject()
res, msg = obj.executeAppyAction(rq['fieldName']) else:
if not msg: obj = context.getParentNode() # An appy obj or in some cases the app folder.
# Use the default i18n messages if obj.portal_type == 'AppyFolder':
suffix = 'ko' from Products.CMFCore.utils import getToolByName
if res: portal = getToolByName(obj, 'portal_url').getPortalObject()
suffix = 'ok' obj = portal.get('portal_%s' % obj.id.lower()) # The tool
label = '%s_action_%s' % (obj.getLabelPrefix(rq['fieldName']), suffix) return obj.getAppyAttribute('on'+action)()
msg = obj.translate(label)
context.plone_utils.addPortalMessage(msg)
elif action == 'changeRefOrder':
# Move the item up (-1), down (+1) or at a given position ?
obj = context.getParentNode()
move = -1 # Move up
isDelta = True
if rq.get('moveDown.x', None) != None:
move = 1 # Move down
elif rq.get('moveSeveral.x', None) != None:
try:
move = int(rq.get('moveValue'))
# In this case, it is not a delta value; it is the new position where
# the item must be moved.
isDelta = False
except ValueError:
context.plone_utils.addPortalMessage(
obj.translate('ref_invalid_index'))
obj.changeAppyRefOrder(rq['fieldName'], rq['objectUid'], move, isDelta)
elif action == 'triggerTransition':
obj = context.getParentNode()
from Products.CMFPlone import PloneMessageFactory as _
context.portal_workflow.doActionFor(obj, rq['workflow_action'],
comment=rq.get('comment', ''))
if urlBack.find('?') != -1:
# Remove params; this way, the user may be redirected to correct phase
# when relevant.
urlBack = urlBack[:urlBack.find('?')]
context.plone_utils.addPortalMessage(
_(u'Your content\'s status has been modified.'))
return rq.RESPONSE.redirect(urlBack)

View file

@ -68,14 +68,14 @@
<body> <body>
<metal:fill fill-slot="main"> <metal:fill fill-slot="main">
<div metal:use-macro="here/skyn/macros/macros/showPagePrologue"/> <div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
<div metal:use-macro="here/skyn/macros/macros/showPageHeader"/> <div metal:use-macro="here/skyn/macros/macros/showPageHeader"/>
<form name="edit_form" method="post" enctype="multipart/form-data" <form name="edit_form" method="post" enctype="multipart/form-data"
class="enableUnloadProtection atBaseEditForm" class="enableUnloadProtection atBaseEditForm"
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do'"> tal:attributes="action python: contextObj.absolute_url()+'/skyn/do'">
<div metal:use-macro="here/skyn/macros/macros/listFields" /> <div metal:use-macro="here/skyn/macros/macros/listFields" />
<input type="hidden" name="action" value="edit"/> <input type="hidden" name="action" value="Update"/>
<input type="hidden" name="fieldset" tal:attributes="value fieldset"/> <input type="hidden" name="fieldset" tal:attributes="value fieldset"/>
<input type="hidden" name="pageName" tal:attributes="value pageName"/> <input type="hidden" name="pageName" tal:attributes="value pageName"/>
<input type="hidden" name="phase" tal:attributes="value phase"/> <input type="hidden" name="phase" tal:attributes="value phase"/>

BIN
gen/plone25/skin/eye.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

BIN
gen/plone25/skin/import.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

123
gen/plone25/skin/import.pt Normal file
View file

@ -0,0 +1,123 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
metal:use-macro="here/main_template/macros/master">
<tal:comment replace="nothing">Disable standard Plone green tabs</tal:comment>
<div metal:fill-slot="top_slot">
<metal:block metal:use-macro="here/global_defines/macros/defines" />
<div tal:define="dummy python:request.set('disable_border', 1)" />
</div>
<tal:comment replace="nothing">Fill main slot of Plone main_template</tal:comment>
<body>
<metal:fill fill-slot="main"
tal:define="appFolder context/getParentNode;
contentType request/type_name;
tool python: portal.get('portal_%s' % appFolder.id.lower());
importElems python: tool.getImportElements(contentType);
global allAreImported python:True">
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
<script language="javascript">
<!--
var importedElemsShown = false;
function toggleViewableElements() {
var rows = cssQuery('#importedElem');
var newDisplay = 'table-row';
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 = cssQuery('#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 = cssQuery('#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>
<tal:comment replace="nothing">Form for importing several meetings at once.</tal:comment>
<form name="importElements"
tal:attributes="action python: appFolder.absolute_url()+'/skyn/do'" method="post">
<input type="hidden" name="action" value="ImportObjects"/>
<input type="hidden" name="type_name" tal:attributes="value contentType"/>
<input type="hidden" name="importPath" value=""/>
</form>
<h1 tal:content="python: tool.translate('import_title')"></h1><br/>
<table cellpadding="0" cellspacing="0" class="vertical listing" width="100%">
<tr>
<th tal:repeat="columnHeader python: importElems[0]">
<img tal:condition="python: repeat['columnHeader'].number() == 1"
tal:attributes="src string:$portal_url/skyn/eye.png;
title python: tool.translate('import_show_hide')"
style="cursor:pointer" onClick="javascript:toggleViewableElements()" align="left" />
<span tal:replace="columnHeader"/>
</th>
<th tal:content="python: tool.translate('ref_actions')"></th>
<th width="20px"><img
tal:attributes="src string: $portal_url/skyn/select_elems.png;
title python: tool.translate('select_delesect')"
onClick="javascript:toggleCheckboxes()" style="cursor:pointer"/>
</tr>
<tal:row repeat="row python: importElems[1]">
<tr tal:define="alreadyImported python: tool.isAlreadyImported(contentType, row[0]);
global allAreImported python: allAreImported and alreadyImported"
tal:attributes="id python:test(alreadyImported, 'importedElem', 'notImportedElem');
style python:test(alreadyImported, 'display:none', 'display:table-row')">
<td tal:repeat="elem python: row[1:]" tal:content="elem">
</td>
<td>
<input type="button" tal:condition="not: alreadyImported"
tal:attributes="onClick python: 'javascript:importSingleElement(\'%s\')' % row[0];
value python: tool.translate('query_import')"/>
<span tal:condition="alreadyImported" tal:replace="python: tool.translate('import_already')"/>
</td>
<td align="center"><input type="checkbox" checked="checked" class="noborder" id="cbElem"
tal:attributes="value python: row[0]" tal:condition="not: alreadyImported"/></td>
</tr>
</tal:row>
<tr tal:condition="python: not importElems[1] or allAreImported"><td colspan="15" tal:content="python: tool.translate('query_no_result')"></td></tr>
</table>
<tal:comment replace="nothing">Button for importing several elements at once.</tal:comment>
<p align="right"><br/>
<input type="button" onClick="javascript:importManyElements()"
tal:condition="python: importElems[1] and not allAreImported"
tal:attributes="value python:tool.translate('import_many')"/>
</p>
</metal:fill>
</body>
</html>

View file

@ -104,7 +104,7 @@
<div metal:define-macro="showActionField"> <div metal:define-macro="showActionField">
<form name="executeAppyAction" action="skyn/do" method="POST"> <form name="executeAppyAction" action="skyn/do" method="POST">
<input type="hidden" name="actionType" value="appyAction"/> <input type="hidden" name="action" value="ExecuteAppyAction"/>
<input type="hidden" name="objectUid" tal:attributes="value contextObj/UID"/> <input type="hidden" name="objectUid" tal:attributes="value contextObj/UID"/>
<input type="hidden" name="fieldName" tal:attributes="value field/getName"/> <input type="hidden" name="fieldName" tal:attributes="value field/getName"/>
<input type="submit" name="do" tal:attributes="value label"/> <input type="submit" name="do" tal:attributes="value label"/>
@ -300,56 +300,72 @@
</dl> </dl>
</span> </span>
<div metal:define-macro="showPagePrologue"> <div metal:define-macro="pagePrologue">
<tal:comment replace="nothing">Global Javascript functions, used in edit and <tal:comment replace="nothing">Global elements used in every page.</tal:comment>
consult views, are defined gere.</tal:comment>
<tal:comment replace="nothing">Javascript messages</tal:comment>
<script language="javascript" tal:content="tool/getJavascriptMessages"></script>
<tal:comment replace="nothing">"Static" javascripts</tal:comment>
<script language="javascript"> <script language="javascript">
<!-- <!--
// This function turns a checkbox into a radio button... sort of // Function used by checkbox widgets for having radio-button-like behaviour
function toggleCheckbox(visibleCheckbox, hiddenBoolean) { function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
vis = document.getElementById(visibleCheckbox); vis = document.getElementById(visibleCheckbox);
hidden = document.getElementById(hiddenBoolean); hidden = document.getElementById(hiddenBoolean);
if (vis.checked) hidden.value = 'True'; if (vis.checked) hidden.value = 'True';
else hidden.value = 'False'; else hidden.value = 'False';
}
// Functions used for master/slave relationships between widgets
function getMasterValue(widget) {
// Returns an array of selected options in a select widget
res = new Array();
if (widget.type == 'checkbox') {
var mv = widget.checked + '';
mv = mv.charAt(0).toUpperCase() + mv.substr(1);
res.push(mv);
}
else { // SELECT widget
for (var i=0; i < widget.options.length; i++) {
if (widget.options[i].selected) res.push(widget.options[i].value);
} }
// Returns an array of selected options in a select widget }
function getMasterValue(widget) { return res;
res = new Array(); }
if (widget.type == 'checkbox') { function updateSlaves(masterValues, appyTypeId) {
var mv = widget.checked + ''; // Given the value(s) selected in a master field, this function updates the
mv = mv.charAt(0).toUpperCase() + mv.substr(1); // state of all corresponding slaves.
res.push(mv); var slaves = cssQuery('div.slave_' + appyTypeId);
} for (var i=0; i< slaves.length; i++){
else { // SELECT widget slaves[i].style.display = "none";
for (var i=0; i < widget.options.length; i++) { }
if (widget.options[i].selected) res.push(widget.options[i].value); for (var i=0; i < masterValues.length; i++) {
} var activeSlaves = cssQuery('div.slaveValue_' + appyTypeId + '_' + masterValues[i]);
} for (var j=0; j < activeSlaves.length; j++){
return res; activeSlaves[j].style.display = "";
} }
// Given the value(s) selected in a master field, this function updates the }
// state of all corresponding slaves. }
function updateSlaves(masterValues, appyTypeId) { // Function used for triggering a workflow transition
var slaves = cssQuery('div.slave_' + appyTypeId); function triggerTransition(transitionId) {
for (var i=0; i< slaves.length; i++){ var theForm = document.getElementById('triggerTransitionForm');
slaves[i].style.display = "none"; theForm.workflow_action.value = transitionId;
} theForm.submit();
for (var i=0; i < masterValues.length; i++) { }
var activeSlaves = cssQuery('div.slaveValue_' + appyTypeId + '_' + masterValues[i]); function onDeleteObject(objectUid) {
for (var j=0; j < activeSlaves.length; j++){ if (confirm(delete_confirm)) {
activeSlaves[j].style.display = ""; f = document.getElementById('deleteForm');
} f.objectUid.value = objectUid;
} f.submit();
} }
// Triggers a workflow transition }
function triggerTransition(transitionId) { -->
var theForm = document.getElementById('triggerTransitionForm'); </script>
theForm.workflow_action.value = transitionId; <tal:comment replace="nothing">Global form for deleting an object</tal:comment>
theForm.submit(); <form id="deleteForm" method="post" action="skyn/do">
} <input type="hidden" name="action" value="Delete"/>
--> <input type="hidden" name="objectUid"/>
</script> </form>
</div> </div>
<div metal:define-macro="showPageHeader" <div metal:define-macro="showPageHeader"
@ -681,10 +697,11 @@
<img src="edit.gif" title="Edit" i18n:domain="plone" i18n:attributes="title" /> <img src="edit.gif" title="Edit" i18n:domain="plone" i18n:attributes="title" />
</a></td> </a></td>
<tal:comment replace="nothing">Delete the element</tal:comment> <tal:comment replace="nothing">Delete the element</tal:comment>
<td class="noPadding"><a tal:attributes="href python: obj.absolute_url() + '/delete_confirmation'" <td class="noPadding">
tal:condition="python: member.has_permission('Delete objects', obj)"> <img tal:condition="python: member.has_permission('Delete objects', obj)"
<img src="delete_icon.gif" title="Delete" i18n:domain="plone" i18n:attributes="title" /> src="delete_icon.gif" title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
</a></td> tal:attributes="onClick python:'javascript:onDeleteObject(\'%s\')' % obj.UID()"/>
</td>
</tr> </tr>
</table> </table>
</td> </td>
@ -740,7 +757,7 @@
tal:condition="transitions"> tal:condition="transitions">
<form id="triggerTransitionForm" method="post" <form id="triggerTransitionForm" method="post"
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'"> tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'">
<input type="hidden" name="actionType" value="triggerTransition"/> <input type="hidden" name="action" value="TriggerTransition"/>
<input type="hidden" name="workflow_action"/> <input type="hidden" name="workflow_action"/>
<table> <table>
<tr> <tr>

View file

@ -3,62 +3,74 @@
<tal:comment replace="nothing">This page presents results of queries</tal:comment> <tal:comment replace="nothing">This page presents results of queries</tal:comment>
<body> <body>
<div metal:fill-slot="top_slot">
<metal:block metal:use-macro="here/global_defines/macros/defines" />
<div tal:define="dummy python:request.set('disable_border', 1)" />
</div>
<tal:comment replace="nothing">We suppose we are in the app folder here.</tal:comment> <tal:comment replace="nothing">Disable standard Plone green tabs</tal:comment>
<div metal:fill-slot="main" <div metal:fill-slot="top_slot">
tal:define="appFolder context/getParentNode; <metal:block metal:use-macro="here/global_defines/macros/defines" />
appName appFolder/id; <div tal:define="dummy python:request.set('disable_border', 1)" />
tool python: portal.get('portal_%s' % appName.lower()); </div>
queryName python:context.REQUEST.get('query');
flavourNumber python:context.REQUEST.get('flavourNumber');
rootClasses tool/getRootClasses;
rootTypes python: test(flavourNumber=='1', rootClasses, ['%s_%s' % (rc, flavourNumber) for rc in rootClasses]);
rootClassesQuery python:','.join(rootClasses);
mainTabSelected python: queryName.find(',') != -1">
<span tal:condition="python: queryName and (queryName != 'none')"> <tal:comment replace="nothing">We suppose we are in the app folder here.</tal:comment>
<span tal:define="queryResult python: tool.executeQuery(queryName, int(flavourNumber)); <div metal:fill-slot="main"
batch queryResult"> tal:define="appFolder context/getParentNode;
appName appFolder/id;
tool python: portal.get('portal_%s' % appName.lower());
queryName python:context.REQUEST.get('query');
flavourNumber python:context.REQUEST.get('flavourNumber');
rootClasses tool/getRootClasses;
rootTypes python: test(flavourNumber=='1', rootClasses, ['%s_%s' % (rc, flavourNumber) for rc in rootClasses]);
rootClassesQuery python:','.join(rootClasses);
mainTabSelected python: queryName.find(',') != -1">
<tal:comment replace="nothing">Tabs</tal:comment> <div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
<ul class="contentViews appyTabs"> <span tal:condition="python: queryName and (queryName != 'none')">
<tal:comment replace="nothing">Tab "All objects"</tal:comment> <span tal:define="queryResult python: tool.executeQuery(queryName, int(flavourNumber));
<li tal:define="selected python:mainTabSelected" batch queryResult">
tal:attributes="class python:test(selected, 'selected', 'plain')"
tal:condition="python: len(rootClasses)>1">
<a tal:content="python: tool.translate('query_consult_all')" <tal:comment replace="nothing">Tabs</tal:comment>
tal:attributes="href python: '%s/skyn/query?query=%s&flavourNumber=%s' % (appFolder.absolute_url(), rootClassesQuery, flavourNumber)"></a> <ul class="contentViews appyTabs">
</li> <tal:comment replace="nothing">Tab "All objects"</tal:comment>
<tal:comment replace="nothing">One tab for each root content type</tal:comment> <li tal:define="selected python:mainTabSelected"
<tal:tab repeat="rootContentType rootTypes"> tal:attributes="class python:test(selected, 'selected', 'plain')"
<li tal:define="selected python:queryName == rootContentType" tal:condition="python: len(rootClasses)>1">
tal:attributes="class python:test(selected, 'selected', 'plain')">
<a tal:content="python: tool.translate(rootContentType)"
tal:attributes="href python: '%s/skyn/query?query=%s&flavourNumber=%s' % (appFolder.absolute_url(), rootContentType, flavourNumber)"/>
<img style="cursor:pointer" class="appyPlusImg"
tal:define="addPermission python: '%s: Add %s' % (appName, rootContentType)"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/do?action=create&type_name=%s\'' % (appFolder.absolute_url(), rootContentType);
src string: $portal_url/skyn/plus.png;
title python: tool.translate('query_create')"
tal:condition="python: member.has_permission(addPermission, appFolder)"/>
</li>
</tal:tab>
</ul>
<br/>
<tal:comment replace="nothing">Query result</tal:comment> <a tal:content="python: tool.translate('query_consult_all')"
<span tal:condition="queryResult"> tal:attributes="href python: '%s/skyn/query?query=%s&flavourNumber=%s' % (appFolder.absolute_url(), rootClassesQuery, flavourNumber)"></a>
<span metal:use-macro="here/skyn/macros/macros/queryResult"></span> </li>
</span> <tal:comment replace="nothing">One tab for each root content type</tal:comment>
<span tal:condition="not: queryResult" <tal:tab repeat="rootContentType rootTypes">
tal:content="python: tool.translate('query_no_result')">No result.</span> <li tal:define="selected python:queryName == rootContentType;
</span> addPermission python: '%s: Add %s' % (appName, rootContentType);
</span> userMayAdd python: member.has_permission(addPermission, appFolder);
</div> createMeans python: tool.getCreateMeans(rootContentType)"
tal:attributes="class python:test(selected, 'selected', 'plain')">
<a tal:content="python: tool.translate(rootContentType)"
tal:attributes="href python: '%s/skyn/query?query=%s&flavourNumber=%s' % (appFolder.absolute_url(), rootContentType, flavourNumber)"/>
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
<img style="cursor:pointer" class="appyPlusImg"
tal:condition="python: ('form' in createMeans) and userMayAdd"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/do?action=Create&type_name=%s\'' % (appFolder.absolute_url(), rootContentType);
src string: $portal_url/skyn/plus.png;
title python: tool.translate('query_create')"/>
<tal:comment replace="nothing">Create (a) new object(s) by importing data</tal:comment>
<img style="cursor:pointer" class="appyPlusImg"
tal:condition="python: ('import' in createMeans) and userMayAdd"
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/import?type_name=%s\'' % (appFolder.absolute_url(), rootContentType);
src string: $portal_url/skyn/import.png;
title python: tool.translate('query_import')"/>
</li>
</tal:tab>
</ul>
<br/>
<tal:comment replace="nothing">Query result</tal:comment>
<span tal:condition="queryResult">
<span metal:use-macro="here/skyn/macros/macros/queryResult"></span>
</span>
<span tal:condition="not: queryResult"
tal:content="python: tool.translate('query_no_result')">No result.</span>
</span>
</span>
</div>
</body> </body>
</html> </html>

View file

@ -26,13 +26,13 @@
<img src="delete_icon.gif" title="label_remove" i18n:domain="plone" i18n:attributes="title" /> <img src="delete_icon.gif" title="label_remove" i18n:domain="plone" i18n:attributes="title" />
</a></td> </a></td>
<tal:comment replace="nothing">Arrows for moving objects up or down</tal:comment> <tal:comment replace="nothing">Arrows for moving objects up or down</tal:comment>
<td class="noPadding" tal:condition="python: len(objs)>1"> <td class="noPadding" tal:condition="python: len(objs)&gt;1">
<form tal:condition="python: member.has_permission('Modify portal content', obj)" <form tal:condition="python: member.has_permission('Modify portal content', obj)"
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'" tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'"
tal:define="objectIndex python:contextObj.getAppyRefIndex(field.getName(), obj)"> tal:define="objectIndex python:contextObj.getAppyRefIndex(field.getName(), obj)">
<input type="hidden" name="actionType" value="changeRefOrder"/> <input type="hidden" name="action" value="ChangeRefOrder"/>
<input type="hidden" name="fieldName" tal:attributes="value field/getName"/> <input type="hidden" name="fieldName" tal:attributes="value field/getName"/>
<input type="hidden" name="objectUid" tal:attributes="value obj/UID"/> <input type="hidden" name="refObjectUid" tal:attributes="value obj/UID"/>
<tal:comment replace="nothing">Arrow up</tal:comment> <tal:comment replace="nothing">Arrow up</tal:comment>
<span tal:condition="python: objectIndex &gt; 0"> <span tal:condition="python: objectIndex &gt; 0">
<input type="image" name="moveUp" class="imageInput" <input type="image" name="moveUp" class="imageInput"
@ -58,7 +58,7 @@
<img style="cursor:pointer" tal:condition="showPlusIcon" <img style="cursor:pointer" tal:condition="showPlusIcon"
tal:attributes="src string:$portal_url/skyn/plus.png; tal:attributes="src string:$portal_url/skyn/plus.png;
title python: tool.translate('add_ref'); title python: tool.translate('add_ref');
onClick python: 'href: window.location=\'%s/skyn/do?action=create&initiator=%s&field=%s&type_name=%s\'' % (folder.absolute_url(), contextObj.UID(), field.getName(), linkedPortalType)"/> onClick python: 'href: window.location=\'%s/skyn/do?action=Create&initiator=%s&field=%s&type_name=%s\'' % (folder.absolute_url(), contextObj.UID(), field.getName(), linkedPortalType)"/>
</metal:plusIcon> </metal:plusIcon>
<div metal:define-macro="showReference" <div metal:define-macro="showReference"

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

View file

@ -27,7 +27,7 @@
phase request/phase|phaseInfo/name; phase request/phase|phaseInfo/name;
pageName python: contextObj.getAppyPage(isEdit, phaseInfo); pageName python: contextObj.getAppyPage(isEdit, phaseInfo);
showWorkflow python: flavour.getAttr('showWorkflowFor' + contextObj.meta_type)"> showWorkflow python: flavour.getAttr('showWorkflowFor' + contextObj.meta_type)">
<div metal:use-macro="here/skyn/macros/macros/showPagePrologue"/> <div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
<div metal:use-macro="here/skyn/macros/macros/showPageHeader"/> <div metal:use-macro="here/skyn/macros/macros/showPageHeader"/>
<div metal:use-macro="here/skyn/macros/macros/listFields" /> <div metal:use-macro="here/skyn/macros/macros/listFields" />
<div metal:use-macro="here/skyn/macros/macros/showPageFooter"/> <div metal:use-macro="here/skyn/macros/macros/showPageFooter"/>

View file

@ -10,6 +10,11 @@
float:right; float:right;
} }
#importedElem {
color: grey;
font-style: italic;
}
.appyTabs { .appyTabs {
margin-bottom: 1em; margin-bottom: 1em;
} }
@ -126,10 +131,13 @@ dl.expandedInlineCollapsible dt.collapsibleHeader, dl.expandedBlockCollapsible d
/* Minor layout changes in fieldsets and tables */ /* Minor layout changes in fieldsets and tables */
fieldset { fieldset {
margin: 0em 0em; margin: 0 0 0 0;
line-height: 1.0em; line-height: 1em;
} }
.fieldset {
line-height: 1em;
}
/* Group fieldsets */ /* Group fieldsets */
.appyGroup { .appyGroup {
border-width: 2px; border-width: 2px;
@ -192,6 +200,9 @@ fieldset {
line-height: 1.0em; line-height: 1.0em;
} }
.field {
margin: 0 0.2em 0.2em 0;
}
/* Portlet elements */ /* Portlet elements */
.portletAppyItem { .portletAppyItem {
margin: 0; margin: 0;

View file

@ -49,8 +49,14 @@ class PoMessage:
REF_MOVE_DOWN = 'Move down' REF_MOVE_DOWN = 'Move down'
REF_INVALID_INDEX = 'No move occurred: please specify a valid number.' REF_INVALID_INDEX = 'No move occurred: please specify a valid number.'
QUERY_CREATE = 'create' QUERY_CREATE = 'create'
QUERY_IMPORT = 'import'
QUERY_CONSULT_ALL = 'consult all' QUERY_CONSULT_ALL = 'consult all'
QUERY_NO_RESULT = 'Nothing to see for the moment.' QUERY_NO_RESULT = 'Nothing to see for the moment.'
IMPORT_TITLE = 'Importing data into your application'
IMPORT_SHOW_HIDE = 'Show / hide alreadly imported elements.'
IMPORT_ALREADY = 'Already imported.'
IMPORT_MANY = 'Import selected elements'
IMPORT_DONE = 'Import terminated successfully.'
WORKFLOW_COMMENT = 'Optional comment' WORKFLOW_COMMENT = 'Optional comment'
WORKFLOW_STATE = 'state' WORKFLOW_STATE = 'state'
PHASE = 'phase' PHASE = 'phase'
@ -71,6 +77,10 @@ class PoMessage:
EMAIL_SUBJECT = '${siteTitle} - Action \\"${transitionName}\\" has been ' \ EMAIL_SUBJECT = '${siteTitle} - Action \\"${transitionName}\\" has been ' \
'performed on element entitled \\"${objectTitle}\\".' 'performed on element entitled \\"${objectTitle}\\".'
EMAIL_BODY = 'You can consult this element at ${objectUrl}.' EMAIL_BODY = 'You can consult this element at ${objectUrl}.'
SELECT_DESELECT = '(Un)select all'
NO_SELECTION = 'You must select at least one element.'
DELETE_CONFIRM = 'Are you sure you want to delete this element?'
DELETE_DONE = 'The element has been deleted.'
def __init__(self, id, msg, default, fuzzy=False, comments=[]): def __init__(self, id, msg, default, fuzzy=False, comments=[]):
self.id = id self.id = id