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
# (pages, groups,...) ----------------------------------------------------------
class Page:
'''Used for describing a page, its related phase, show condition, etc.'''
def __init__(self, name, phase='main', show=True):
self.name = name
self.phase = phase
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:
'''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_down', '', msg.REF_MOVE_DOWN),
msg('query_create', '', msg.QUERY_CREATE),
msg('query_import', '', msg.QUERY_IMPORT),
msg('query_no_result', '', msg.QUERY_NO_RESULT),
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('bad_int', '', msg.BAD_INT),
msg('bad_float', '', msg.BAD_FLOAT),
msg('bad_email', '', msg.BAD_EMAIL),
msg('bad_url', '', msg.BAD_URL),
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)
self.generateTool()

View file

@ -133,7 +133,7 @@ class PloneInstaller:
updateRolesForPermission('Add portal content', tuple(allCreators),
appFolder)
# 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(site, appy.getPath() + '/gen/plone25/skin',id='skyn')
@ -266,18 +266,15 @@ class PloneInstaller:
current.append(self.toolInstanceName)
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.appyTool = self.tool._appy_getWrapper(force=True)
if self.reinstall:
self.tool.at_post_edit_script()
self.tool.createOrUpdate(False)
else:
self.tool.at_post_create_script()
self.tool.createOrUpdate(True)
if not self.appyTool.flavours:
# Create the default flavour
self.appyTool.create('flavours', title=self.productName, number=1)
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, ' \
'launch it and type "import uno". If you have no ' \
'ImportError exception it is ok.'
jsMessages = ('no_elem_selected', 'delete_confirm')
# ------------------------------------------------------------------------------
class ToolMixin(AbstractMixin):
@ -206,4 +207,91 @@ class ToolMixin(AbstractMixin):
parent = obj.getParentNode()
if parent.id == 'skyn': return parent.getParentNode()
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
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):
'''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
@ -54,6 +58,30 @@ class AbstractMixin:
else: obj.reindexObject()
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):
'''This method is executed when a user wants to update an object.
The object may be a temporary object created by portal_factory in
@ -106,6 +134,13 @@ class AbstractMixin:
rq.set('fieldset', rq.get('nextPage'))
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):
'''Returns the Appy type corresponding to p_fieldName.'''
res = None
@ -407,7 +442,7 @@ class AbstractMixin:
else:
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
reference field p_fieldName to p_newIndex i p_isDelta is False, or
to actualIndex+p_newIndex if p_isDelta is True.'''
@ -421,6 +456,27 @@ class AbstractMixin:
pass # To implement later on
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):
'''Returns the Appy workflow instance that is relevant for this
object. If p_appy is False, it returns the DC workflow.'''
@ -511,6 +567,38 @@ class AbstractMixin:
self.reindexObject()
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):
'''Selects objects from a Reference field.'''
if selectMethod:

View file

@ -2,63 +2,14 @@
##bind context=context
##parameters=action
rq = context.REQUEST
urlBack = rq['HTTP_REFERER']
if action == 'create':
# A user wants to create an object.
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':
# Get the object impacted by the action.
if rq.get('objectUid', None):
obj = context.uid_catalog(UID=rq['objectUid'])[0].getObject()
res, msg = obj.executeAppyAction(rq['fieldName'])
if not msg:
# Use the default i18n messages
suffix = 'ko'
if res:
suffix = 'ok'
label = '%s_action_%s' % (obj.getLabelPrefix(rq['fieldName']), suffix)
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)
else:
obj = context.getParentNode() # An appy obj or in some cases the app folder.
if obj.portal_type == 'AppyFolder':
from Products.CMFCore.utils import getToolByName
portal = getToolByName(obj, 'portal_url').getPortalObject()
obj = portal.get('portal_%s' % obj.id.lower()) # The tool
return obj.getAppyAttribute('on'+action)()

View file

@ -68,14 +68,14 @@
<body>
<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"/>
<form name="edit_form" method="post" enctype="multipart/form-data"
class="enableUnloadProtection atBaseEditForm"
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do'">
<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="pageName" tal:attributes="value pageName"/>
<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">
<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="fieldName" tal:attributes="value field/getName"/>
<input type="submit" name="do" tal:attributes="value label"/>
@ -300,21 +300,25 @@
</dl>
</span>
<div metal:define-macro="showPagePrologue">
<tal:comment replace="nothing">Global Javascript functions, used in edit and
consult views, are defined gere.</tal:comment>
<div metal:define-macro="pagePrologue">
<tal:comment replace="nothing">Global elements used in every page.</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">
<!--
// 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) {
vis = document.getElementById(visibleCheckbox);
hidden = document.getElementById(hiddenBoolean);
if (vis.checked) hidden.value = 'True';
else hidden.value = 'False';
}
// Returns an array of selected options in a select widget
// 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 + '';
@ -328,9 +332,9 @@
}
return res;
}
function updateSlaves(masterValues, appyTypeId) {
// Given the value(s) selected in a master field, this function updates the
// state of all corresponding slaves.
function updateSlaves(masterValues, appyTypeId) {
var slaves = cssQuery('div.slave_' + appyTypeId);
for (var i=0; i< slaves.length; i++){
slaves[i].style.display = "none";
@ -342,14 +346,26 @@
}
}
}
// Triggers a workflow transition
// Function used for triggering a workflow transition
function triggerTransition(transitionId) {
var theForm = document.getElementById('triggerTransitionForm');
theForm.workflow_action.value = transitionId;
theForm.submit();
}
-->
function onDeleteObject(objectUid) {
if (confirm(delete_confirm)) {
f = document.getElementById('deleteForm');
f.objectUid.value = objectUid;
f.submit();
}
}
-->
</script>
<tal:comment replace="nothing">Global form for deleting an object</tal:comment>
<form id="deleteForm" method="post" action="skyn/do">
<input type="hidden" name="action" value="Delete"/>
<input type="hidden" name="objectUid"/>
</form>
</div>
<div metal:define-macro="showPageHeader"
@ -681,10 +697,11 @@
<img src="edit.gif" title="Edit" i18n:domain="plone" i18n:attributes="title" />
</a></td>
<tal:comment replace="nothing">Delete the element</tal:comment>
<td class="noPadding"><a tal:attributes="href python: obj.absolute_url() + '/delete_confirmation'"
tal:condition="python: member.has_permission('Delete objects', obj)">
<img src="delete_icon.gif" title="Delete" i18n:domain="plone" i18n:attributes="title" />
</a></td>
<td class="noPadding">
<img tal:condition="python: member.has_permission('Delete objects', obj)"
src="delete_icon.gif" title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
tal:attributes="onClick python:'javascript:onDeleteObject(\'%s\')' % obj.UID()"/>
</td>
</tr>
</table>
</td>
@ -740,7 +757,7 @@
tal:condition="transitions">
<form id="triggerTransitionForm" method="post"
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"/>
<table>
<tr>

View file

@ -3,13 +3,15 @@
<tal:comment replace="nothing">This page presents results of queries</tal:comment>
<body>
<div metal:fill-slot="top_slot">
<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>
</div>
<tal:comment replace="nothing">We suppose we are in the app folder here.</tal:comment>
<div metal:fill-slot="main"
<tal:comment replace="nothing">We suppose we are in the app folder here.</tal:comment>
<div metal:fill-slot="main"
tal:define="appFolder context/getParentNode;
appName appFolder/id;
tool python: portal.get('portal_%s' % appName.lower());
@ -20,6 +22,7 @@
rootClassesQuery python:','.join(rootClasses);
mainTabSelected python: queryName.find(',') != -1">
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
<span tal:condition="python: queryName and (queryName != 'none')">
<span tal:define="queryResult python: tool.executeQuery(queryName, int(flavourNumber));
batch queryResult">
@ -36,16 +39,25 @@
</li>
<tal:comment replace="nothing">One tab for each root content type</tal:comment>
<tal:tab repeat="rootContentType rootTypes">
<li tal:define="selected python:queryName == rootContentType"
<li tal:define="selected python:queryName == rootContentType;
addPermission python: '%s: Add %s' % (appName, rootContentType);
userMayAdd python: member.has_permission(addPermission, appFolder);
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: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);
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:condition="python: member.has_permission(addPermission, appFolder)"/>
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>
@ -59,6 +71,6 @@
tal:content="python: tool.translate('query_no_result')">No result.</span>
</span>
</span>
</div>
</div>
</body>
</html>

View file

@ -26,13 +26,13 @@
<img src="delete_icon.gif" title="label_remove" i18n:domain="plone" i18n:attributes="title" />
</a></td>
<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)"
tal:attributes="action python: contextObj.absolute_url() + '/skyn/do'"
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="objectUid" tal:attributes="value obj/UID"/>
<input type="hidden" name="refObjectUid" tal:attributes="value obj/UID"/>
<tal:comment replace="nothing">Arrow up</tal:comment>
<span tal:condition="python: objectIndex &gt; 0">
<input type="image" name="moveUp" class="imageInput"
@ -58,7 +58,7 @@
<img style="cursor:pointer" tal:condition="showPlusIcon"
tal:attributes="src string:$portal_url/skyn/plus.png;
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>
<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;
pageName python: contextObj.getAppyPage(isEdit, phaseInfo);
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/listFields" />
<div metal:use-macro="here/skyn/macros/macros/showPageFooter"/>

View file

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

View file

@ -49,8 +49,14 @@ class PoMessage:
REF_MOVE_DOWN = 'Move down'
REF_INVALID_INDEX = 'No move occurred: please specify a valid number.'
QUERY_CREATE = 'create'
QUERY_IMPORT = 'import'
QUERY_CONSULT_ALL = 'consult all'
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_STATE = 'state'
PHASE = 'phase'
@ -71,6 +77,10 @@ class PoMessage:
EMAIL_SUBJECT = '${siteTitle} - Action \\"${transitionName}\\" has been ' \
'performed on element entitled \\"${objectTitle}\\".'
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=[]):
self.id = id