appy.gen: added a widget 'List' for rendering grids of data.
This commit is contained in:
parent
f1136eb786
commit
c11378c747
|
@ -5,7 +5,7 @@ from appy.gen.layout import Table
|
||||||
from appy.gen.layout import defaultFieldLayouts
|
from appy.gen.layout import defaultFieldLayouts
|
||||||
from appy.gen.po import PoMessage
|
from appy.gen.po import PoMessage
|
||||||
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \
|
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \
|
||||||
getClassName, SomeObjects
|
getClassName, SomeObjects, AppyObject
|
||||||
import appy.pod
|
import appy.pod
|
||||||
from appy.pod.renderer import Renderer
|
from appy.pod.renderer import Renderer
|
||||||
from appy.shared.data import countries
|
from appy.shared.data import countries
|
||||||
|
@ -517,6 +517,11 @@ class Type:
|
||||||
# We must initialise the corresponding back reference
|
# We must initialise the corresponding back reference
|
||||||
self.back.klass = klass
|
self.back.klass = klass
|
||||||
self.back.init(self.back.attribute, self.klass, appName)
|
self.back.init(self.back.attribute, self.klass, appName)
|
||||||
|
if isinstance(self, List):
|
||||||
|
for subName, subField in self.fields:
|
||||||
|
fullName = '%s_%s' % (name, subName)
|
||||||
|
subField.init(fullName, klass, appName)
|
||||||
|
subField.name = '%s*%s' % (name, subName)
|
||||||
|
|
||||||
def reload(self, klass, obj):
|
def reload(self, klass, obj):
|
||||||
'''In debug mode, we want to reload layouts without restarting Zope.
|
'''In debug mode, we want to reload layouts without restarting Zope.
|
||||||
|
@ -1534,7 +1539,6 @@ class File(Type):
|
||||||
return value._atFile
|
return value._atFile
|
||||||
|
|
||||||
def getRequestValue(self, request):
|
def getRequestValue(self, request):
|
||||||
res = request.get('%s_file' % self.name)
|
|
||||||
return request.get('%s_file' % self.name)
|
return request.get('%s_file' % self.name)
|
||||||
|
|
||||||
def getDefaultLayouts(self): return {'view':'lf','edit':'lrv-f'}
|
def getDefaultLayouts(self): return {'view':'lf','edit':'lrv-f'}
|
||||||
|
@ -1784,9 +1788,6 @@ class Ref(Type):
|
||||||
def getFormattedValue(self, obj, value):
|
def getFormattedValue(self, obj, value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def getRequestValue(self, request):
|
|
||||||
return request.get('appy_ref_%s' % self.name, None)
|
|
||||||
|
|
||||||
def validateValue(self, obj, value):
|
def validateValue(self, obj, value):
|
||||||
if not self.link: return None
|
if not self.link: return None
|
||||||
# We only check "link" Refs because in edit views, "add" Refs are
|
# We only check "link" Refs because in edit views, "add" Refs are
|
||||||
|
@ -2210,6 +2211,94 @@ class Pod(Type):
|
||||||
value = value._atFile
|
value = value._atFile
|
||||||
setattr(obj, self.name, value)
|
setattr(obj, self.name, value)
|
||||||
|
|
||||||
|
class List(Type):
|
||||||
|
'''A list.'''
|
||||||
|
def __init__(self, fields, validator=None, multiplicity=(0,1), index=None,
|
||||||
|
default=None, optional=False, editDefault=False, show=True,
|
||||||
|
page='main', group=None, layouts=None, move=0, indexed=False,
|
||||||
|
searchable=False, specificReadPermission=False,
|
||||||
|
specificWritePermission=False, width=None, height=None,
|
||||||
|
maxChars=None, colspan=1, master=None, masterValue=None,
|
||||||
|
focus=False, historized=False, mapping=None, label=None,
|
||||||
|
subLayouts=Table('fv', width=None)):
|
||||||
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
|
editDefault, show, page, group, layouts, move, indexed,
|
||||||
|
False, specificReadPermission, specificWritePermission,
|
||||||
|
width, height, None, colspan, master, masterValue, focus,
|
||||||
|
historized, True, mapping, label)
|
||||||
|
self.validable = True
|
||||||
|
# Tuples of (names, Type instances) determining the format of every
|
||||||
|
# element in the list.
|
||||||
|
self.fields = fields
|
||||||
|
self.fieldsd = [(n, f.__dict__) for (n,f) in self.fields]
|
||||||
|
# Force some layouting for sub-fields, if subLayouts are given. So the
|
||||||
|
# one who wants freedom on tuning layouts at the field level must
|
||||||
|
# specify subLayouts=None.
|
||||||
|
if subLayouts:
|
||||||
|
for name, field in self.fields:
|
||||||
|
field.layouts = field.formatLayouts(subLayouts)
|
||||||
|
|
||||||
|
def getField(self, name):
|
||||||
|
'''Gets the field definition whose name is p_name.'''
|
||||||
|
for n, field in self.fields:
|
||||||
|
if n == name: return field
|
||||||
|
|
||||||
|
def isEmptyValue(self, value, obj=None):
|
||||||
|
'''Returns True if the p_value must be considered as an empty value.'''
|
||||||
|
return not value
|
||||||
|
|
||||||
|
def getRequestValue(self, request):
|
||||||
|
'''Concatenates the list from distinct form elements in the request.'''
|
||||||
|
prefix = self.name + '*' + self.fields[0][0] + '*'
|
||||||
|
res = {}
|
||||||
|
for key in request.keys():
|
||||||
|
if not key.startswith(prefix): continue
|
||||||
|
# I have found a row. Gets its index
|
||||||
|
row = AppyObject()
|
||||||
|
rowIndex = int(key.split('*')[-1])
|
||||||
|
if rowIndex == -1: continue # Ignore the template row.
|
||||||
|
for name, field in self.fields:
|
||||||
|
keyName = '%s*%s*%s' % (self.name, name, rowIndex)
|
||||||
|
if request.has_key(keyName):
|
||||||
|
# Simulate the request as if it was for a single value
|
||||||
|
request.set(field.name, request[keyName])
|
||||||
|
v = field.getRequestValue(request)
|
||||||
|
else:
|
||||||
|
v = None
|
||||||
|
setattr(row, name, v)
|
||||||
|
res[rowIndex] = row
|
||||||
|
# Produce a sorted list.
|
||||||
|
keys = res.keys()
|
||||||
|
keys.sort()
|
||||||
|
res = [res[key] for key in keys]
|
||||||
|
print 'REQUEST VALUE FOR LIST (%d)' % len(res)
|
||||||
|
for value in res:
|
||||||
|
for k, v in value.__dict__.iteritems():
|
||||||
|
print k, '=', v
|
||||||
|
# I store in the request this computed value. This way, when individual
|
||||||
|
# subFields will need to get their value, they will take it from here,
|
||||||
|
# instead of taking it from the specific request key. Indeed, specific
|
||||||
|
# request keys contain row indexes that may be wrong after row deletions
|
||||||
|
# by the user.
|
||||||
|
request.set(self.name, res)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def getStorableValue(self, value):
|
||||||
|
'''Gets p_value in a form that can be stored in the database.'''
|
||||||
|
for v in value:
|
||||||
|
for name, field in self.fields:
|
||||||
|
setattr(v, name, field.getStorableValue(getattr(v, name)))
|
||||||
|
return value
|
||||||
|
|
||||||
|
def getInnerValue(self, obj, name, i):
|
||||||
|
'''Returns the value of inner field named p_name in row number p_i
|
||||||
|
with the list of values from this field on p_obj.'''
|
||||||
|
if i == -1: return ''
|
||||||
|
value = getattr(obj, self.name, None)
|
||||||
|
if not value: return ''
|
||||||
|
if i >= len(value): return ''
|
||||||
|
return getattr(value[i], name, '')
|
||||||
|
|
||||||
# Workflow-specific types and default workflows --------------------------------
|
# Workflow-specific types and default workflows --------------------------------
|
||||||
appyToZopePermissions = {
|
appyToZopePermissions = {
|
||||||
'read': ('View', 'Access contents information'),
|
'read': ('View', 'Access contents information'),
|
||||||
|
|
|
@ -120,6 +120,14 @@ class FieldDescriptor:
|
||||||
# Add the POD-related fields on the Tool
|
# Add the POD-related fields on the Tool
|
||||||
self.generator.tool.addPodRelatedFields(self)
|
self.generator.tool.addPodRelatedFields(self)
|
||||||
|
|
||||||
|
def walkList(self):
|
||||||
|
# Add i18n-specific messages
|
||||||
|
for name, field in self.appyType.fields:
|
||||||
|
label = '%s_%s_%s' % (self.classDescr.name, self.fieldName, name)
|
||||||
|
msg = PoMessage(label, '', name)
|
||||||
|
msg.produceNiceDefault()
|
||||||
|
self.generator.labels.append(msg)
|
||||||
|
|
||||||
def walkAppyType(self):
|
def walkAppyType(self):
|
||||||
'''Walks into the Appy type definition and gathers data about the
|
'''Walks into the Appy type definition and gathers data about the
|
||||||
i18n labels.'''
|
i18n labels.'''
|
||||||
|
@ -176,6 +184,8 @@ class FieldDescriptor:
|
||||||
elif self.appyType.type == 'Ref': self.walkRef()
|
elif self.appyType.type == 'Ref': self.walkRef()
|
||||||
# Manage things which are specific to Pod types
|
# Manage things which are specific to Pod types
|
||||||
elif self.appyType.type == 'Pod': self.walkPod()
|
elif self.appyType.type == 'Pod': self.walkPod()
|
||||||
|
# Manage things which are specific to List types
|
||||||
|
elif self.appyType.type == 'List': self.walkList()
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
'''Generates the i18n labels for this type.'''
|
'''Generates the i18n labels for this type.'''
|
||||||
|
|
|
@ -142,6 +142,15 @@ class BaseMixin:
|
||||||
setattr(errors, appyType.name, message)
|
setattr(errors, appyType.name, message)
|
||||||
else:
|
else:
|
||||||
setattr(values, appyType.name, appyType.getStorableValue(value))
|
setattr(values, appyType.name, appyType.getStorableValue(value))
|
||||||
|
# Validate sub-fields within Lists
|
||||||
|
if appyType.type != 'List': continue
|
||||||
|
i = -1
|
||||||
|
for row in value:
|
||||||
|
i += 1
|
||||||
|
for name, field in appyType.fields:
|
||||||
|
message = field.validate(self, getattr(row,name,None))
|
||||||
|
if message:
|
||||||
|
setattr(errors, '%s*%d' % (field.name, i), message)
|
||||||
|
|
||||||
def interFieldValidation(self, errors, values):
|
def interFieldValidation(self, errors, values):
|
||||||
'''This method is called when individual validation of all fields
|
'''This method is called when individual validation of all fields
|
||||||
|
@ -432,13 +441,32 @@ class BaseMixin:
|
||||||
retrieved in synchronous mode.'''
|
retrieved in synchronous mode.'''
|
||||||
appyType = self.getAppyType(name)
|
appyType = self.getAppyType(name)
|
||||||
if not onlyIfSync or (onlyIfSync and appyType.sync[layoutType]):
|
if not onlyIfSync or (onlyIfSync and appyType.sync[layoutType]):
|
||||||
return appyType.getValue(self)
|
# We must really get the field value.
|
||||||
|
if '*' not in name: return appyType.getValue(self)
|
||||||
|
# The field is an inner field from a List.
|
||||||
|
listName, name, i = name.split('*')
|
||||||
|
return self.getAppyType(listName).getInnerValue(self, name, int(i))
|
||||||
|
|
||||||
def getFormattedFieldValue(self, name, value):
|
def getFormattedFieldValue(self, name, value):
|
||||||
'''Gets a nice, string representation of p_value which is a value from
|
'''Gets a nice, string representation of p_value which is a value from
|
||||||
field named p_name.'''
|
field named p_name.'''
|
||||||
return self.getAppyType(name).getFormattedValue(self, value)
|
return self.getAppyType(name).getFormattedValue(self, value)
|
||||||
|
|
||||||
|
def getRequestFieldValue(self, name):
|
||||||
|
'''Gets the value of field p_name as may be present in the request.'''
|
||||||
|
# Return the request value for standard fields.
|
||||||
|
if '*' not in name:
|
||||||
|
return self.getAppyType(name).getRequestValue(self.REQUEST)
|
||||||
|
# For sub-fields within Lists, the corresponding request values have
|
||||||
|
# already been computed in the request key corresponding to the whole
|
||||||
|
# List.
|
||||||
|
listName, name, rowIndex = name.split('*')
|
||||||
|
rowIndex = int(rowIndex)
|
||||||
|
if rowIndex == -1: return ''
|
||||||
|
allValues = self.REQUEST.get(listName)
|
||||||
|
if not allValues: return ''
|
||||||
|
return getattr(allValues[rowIndex], name, '')
|
||||||
|
|
||||||
def getFileInfo(self, fileObject):
|
def getFileInfo(self, fileObject):
|
||||||
'''Returns filename and size of p_fileObject.'''
|
'''Returns filename and size of p_fileObject.'''
|
||||||
if not fileObject: return {'filename': '', 'size': 0}
|
if not fileObject: return {'filename': '', 'size': 0}
|
||||||
|
@ -533,11 +561,15 @@ class BaseMixin:
|
||||||
def getAppyType(self, name, asDict=False, className=None):
|
def getAppyType(self, name, asDict=False, className=None):
|
||||||
'''Returns the Appy type named p_name. If no p_className is defined, the
|
'''Returns the Appy type named p_name. If no p_className is defined, the
|
||||||
field is supposed to belong to self's class.'''
|
field is supposed to belong to self's class.'''
|
||||||
|
isInnerType = '*' in name # An inner type lies within a List type.
|
||||||
|
subName = None
|
||||||
|
if isInnerType: name, subName, i = name.split('*')
|
||||||
if not className:
|
if not className:
|
||||||
klass = self.__class__.wrapperClass
|
klass = self.__class__.wrapperClass
|
||||||
else:
|
else:
|
||||||
klass = self.getTool().getAppyClass(className, wrapper=True)
|
klass = self.getTool().getAppyClass(className, wrapper=True)
|
||||||
res = getattr(klass, name, None)
|
res = getattr(klass, name, None)
|
||||||
|
if res and isInnerType: res = res.getField(subName)
|
||||||
if res and asDict: return res.__dict__
|
if res and asDict: return res.__dict__
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,9 @@ img {border: 0;}
|
||||||
.list td, .list th { border: 1px solid grey;
|
.list td, .list th { border: 1px solid grey;
|
||||||
padding-left: 5px; padding-right: 5px; padding-top: 3px;}
|
padding-left: 5px; padding-right: 5px; padding-top: 3px;}
|
||||||
.list th { background-color: #cbcbcb; font-style: italic; font-weight: normal;}
|
.list th { background-color: #cbcbcb; font-style: italic; font-weight: normal;}
|
||||||
|
.grid th { font-style: italic; font-weight: normal;
|
||||||
|
border-bottom: 2px solid grey; padding: 2px 2px;}
|
||||||
|
.grid td { padding-right: 5px; }
|
||||||
.noStyle { border: 0 !important; padding: 0 !important; margin: 0 !important; }
|
.noStyle { border: 0 !important; padding: 0 !important; margin: 0 !important; }
|
||||||
.noStyle td { border:0 !important; padding:0 !important; margin:0 !important; }
|
.noStyle td { border:0 !important; padding:0 !important; margin:0 !important; }
|
||||||
.translationLabel { background-color: #EAEAEA; border-bottom: 1px dashed grey;
|
.translationLabel { background-color: #EAEAEA; border-bottom: 1px dashed grey;
|
||||||
|
|
|
@ -492,3 +492,59 @@ function initTab(cookieId, defaultValue) {
|
||||||
if (!toSelect) { showTab(defaultValue) }
|
if (!toSelect) { showTab(defaultValue) }
|
||||||
else { showTab(toSelect); }
|
else { showTab(toSelect); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List-related Javascript functions
|
||||||
|
function updateRowNumber(row, rowIndex, action) {
|
||||||
|
/* Within p_row, we update every field whose name and id include the row index
|
||||||
|
with new p_rowIndex. If p_action is 'set', p_rowIndex becomes the new
|
||||||
|
index. If p_action is 'add', new index becomes:
|
||||||
|
existing index + p_rowIndex. */
|
||||||
|
tagTypes = ['input', 'select'];
|
||||||
|
currentIndex = -1;
|
||||||
|
for (var i=0; i < tagTypes.length; i++) {
|
||||||
|
widgets = row.getElementsByTagName(tagTypes[i]);
|
||||||
|
for (var j=0; j < widgets.length; j++) {
|
||||||
|
id = widgets[j].id;
|
||||||
|
name = widgets[j].name;
|
||||||
|
idNbIndex = id.lastIndexOf('*') + 1;
|
||||||
|
nameNbIndex = name.lastIndexOf('*') + 1;
|
||||||
|
// Compute the current row index if not already done.
|
||||||
|
if (currentIndex == -1) {
|
||||||
|
currentIndex = parseInt(id.substring(idNbIndex));
|
||||||
|
}
|
||||||
|
// Compute the new values for attributes "id" and "name".
|
||||||
|
newId = id.substring(0, idNbIndex);
|
||||||
|
newName = id.substring(0, nameNbIndex);
|
||||||
|
newIndex = rowIndex;
|
||||||
|
if (action == 'add') newIndex = newIndex + currentIndex;
|
||||||
|
widgets[j].id = newId + String(newIndex);
|
||||||
|
widgets[j].name = newName + String(newIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function insertRow(tableId) {
|
||||||
|
// This function adds a new row in table with ID p_tableId.
|
||||||
|
table = document.getElementById(tableId);
|
||||||
|
newRow = table.rows[1].cloneNode(true);
|
||||||
|
newRow.style.display = 'table-row';
|
||||||
|
// Within newRow, I must include in field names and ids the row number
|
||||||
|
updateRowNumber(newRow, table.rows.length-3, 'set');
|
||||||
|
table.tBodies[0].appendChild(newRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteRow(tableId, deleteImg) {
|
||||||
|
row = deleteImg.parentNode.parentNode;
|
||||||
|
table = document.getElementById(tableId);
|
||||||
|
allRows = table.rows;
|
||||||
|
toDeleteIndex = -1; // Will hold the index of the row to delete.
|
||||||
|
for (var i=0; i < allRows.length; i++) {
|
||||||
|
if (toDeleteIndex == -1) {
|
||||||
|
if (row == allRows[i]) toDeleteIndex = i;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Decrement higher row numbers by 1 because of the deletion
|
||||||
|
updateRowNumber(allRows[i], -1, 'add');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.deleteRow(toDeleteIndex);
|
||||||
|
}
|
||||||
|
|
72
gen/plone25/skin/widgets/list.pt
Normal file
72
gen/plone25/skin/widgets/list.pt
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<tal:comment replace="nothing">Single row.</tal:comment>
|
||||||
|
<tr metal:define-macro="row"
|
||||||
|
tal:attributes="style python: (rowIndex==-1) and 'display: none' or ''">
|
||||||
|
<td align="center" tal:repeat="fieldInfo widget/fieldsd">
|
||||||
|
<tal:show define="widget python: fieldInfo[1];
|
||||||
|
tagCss python: 'noStyle';
|
||||||
|
widgetName python: '%s*%d' % (widget['name'], rowIndex)">
|
||||||
|
<metal:call use-macro="app/skyn/widgets/show/macros/field"/>
|
||||||
|
</tal:show>
|
||||||
|
</td>
|
||||||
|
<tal:comment replace="nothing">Icon for removing the row</tal:comment>
|
||||||
|
<td align="right" tal:condition="python: layoutType=='edit'">
|
||||||
|
<img style="cursor:pointer"
|
||||||
|
tal:attributes="src string:$appUrl/skyn/delete.png;
|
||||||
|
title python: 'Delete';
|
||||||
|
onClick python: 'deleteRow(\'list_%s\',this)' % name"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">The whole table, edit or view.</tal:comment>
|
||||||
|
<table metal:define-macro="table" class="grid"
|
||||||
|
tal:define="isEdit python: layoutType == 'edit'"
|
||||||
|
tal:condition="python: isEdit or value"
|
||||||
|
tal:attributes="id python: 'list_%s' % name">
|
||||||
|
<tal:comment replace="nothing">Header</tal:comment>
|
||||||
|
<tr>
|
||||||
|
<th tal:repeat="fieldInfo widget/fieldsd"
|
||||||
|
tal:content="python: _(fieldInfo[1]['labelId'])">
|
||||||
|
</th>
|
||||||
|
<tal:comment replace="nothing">Icon for adding a new row.</tal:comment>
|
||||||
|
<th tal:condition="isEdit">
|
||||||
|
<img style="cursor:pointer"
|
||||||
|
tal:attributes="src string:$appUrl/skyn/plus.png;
|
||||||
|
title python: _('add_ref');
|
||||||
|
onClick python: 'insertRow(\'list_%s\')' % name"/>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tal:comment replace="nothing">Template row (edit only)</tal:comment>
|
||||||
|
<tal:templateRow define="rowIndex python:-1" condition="isEdit">
|
||||||
|
<metal:call use-macro="app/skyn/widgets/list/macros/row"/>
|
||||||
|
</tal:templateRow>
|
||||||
|
<tr height="7px"><td></td></tr>
|
||||||
|
<tal:comment replace="nothing">Rows of data</tal:comment>
|
||||||
|
<tal:rows define="rows python: inRequest and requestValue or value"
|
||||||
|
repeat="row rows">
|
||||||
|
<tal:row define="rowIndex repeat/row/index">
|
||||||
|
<metal:call use-macro="app/skyn/widgets/list/macros/row"/>
|
||||||
|
</tal:row>
|
||||||
|
</tal:rows>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">View</tal:comment>
|
||||||
|
<metal:view define-macro="view">
|
||||||
|
<metal:call use-macro="app/skyn/widgets/list/macros/table"/>
|
||||||
|
</metal:view>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Edit</tal:comment>
|
||||||
|
<metal:edit define-macro="edit">
|
||||||
|
<tal:comment replace="nothing">
|
||||||
|
The following input makes Appy aware that this field is in the request.
|
||||||
|
</tal:comment>
|
||||||
|
<input type="hidden" tal:attributes="name name" value="">
|
||||||
|
<metal:call use-macro="app/skyn/widgets/list/macros/table"/>
|
||||||
|
</metal:edit>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Cell</tal:comment>
|
||||||
|
<metal:cell define-macro="cell">
|
||||||
|
<metal:call use-macro="app/skyn/widgets/list/macros/table"/>
|
||||||
|
</metal:cell>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Search</tal:comment>
|
||||||
|
<metal:search define-macro="search"></metal:search>
|
|
@ -28,13 +28,13 @@
|
||||||
<tal:comment replace="nothing">Move up</tal:comment>
|
<tal:comment replace="nothing">Move up</tal:comment>
|
||||||
<img tal:condition="python: objectIndex > 0"
|
<img tal:condition="python: objectIndex > 0"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowUp.png;
|
tal:attributes="src string: $appUrl/skyn/arrowUp.png;
|
||||||
title python: tool.translate('move_up');
|
title python: _('move_up');
|
||||||
onClick python: ajaxBaseCall.replace('**v**', 'up')"
|
onClick python: ajaxBaseCall.replace('**v**', 'up')"
|
||||||
style="cursor:pointer"/>
|
style="cursor:pointer"/>
|
||||||
<tal:comment replace="nothing">Move down</tal:comment>
|
<tal:comment replace="nothing">Move down</tal:comment>
|
||||||
<img tal:condition="python: objectIndex < (totalNumber-1)"
|
<img tal:condition="python: objectIndex < (totalNumber-1)"
|
||||||
tal:attributes="src string: $appUrl/skyn/arrowDown.png;
|
tal:attributes="src string: $appUrl/skyn/arrowDown.png;
|
||||||
title python: tool.translate('move_down');
|
title python: _('move_down');
|
||||||
onClick python: ajaxBaseCall.replace('**v**', 'down')"
|
onClick python: ajaxBaseCall.replace('**v**', 'down')"
|
||||||
style="cursor:pointer"/>
|
style="cursor:pointer"/>
|
||||||
</tal:moveRef>
|
</tal:moveRef>
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
noFormCall python: navBaseCall.replace('**v**', '%d, \'CreateWithoutForm\'' % startNumber);
|
noFormCall python: navBaseCall.replace('**v**', '%d, \'CreateWithoutForm\'' % startNumber);
|
||||||
noFormCall python: test(appyType['addConfirm'], 'askConfirm(\'script\', "%s", "%s")' % (noFormCall, addConfirmMsg), noFormCall)"
|
noFormCall python: test(appyType['addConfirm'], 'askConfirm(\'script\', "%s", "%s")' % (noFormCall, addConfirmMsg), noFormCall)"
|
||||||
tal:attributes="src string:$appUrl/skyn/plus.png;
|
tal:attributes="src string:$appUrl/skyn/plus.png;
|
||||||
title python: tool.translate('add_ref');
|
title python: _('add_ref');
|
||||||
onClick python: test(appyType['noForm'], noFormCall, formCall)"/>
|
onClick python: test(appyType['noForm'], noFormCall, formCall)"/>
|
||||||
</metal:plusIcon>
|
</metal:plusIcon>
|
||||||
|
|
||||||
|
@ -108,6 +108,7 @@
|
||||||
ajaxHookId python: contextObj.UID()+fieldName;
|
ajaxHookId python: contextObj.UID()+fieldName;
|
||||||
startNumber python: int(request.get('%s_startNumber' % ajaxHookId, 0));
|
startNumber python: int(request.get('%s_startNumber' % ajaxHookId, 0));
|
||||||
tool contextObj/getTool;
|
tool contextObj/getTool;
|
||||||
|
_ python: tool.translate;
|
||||||
refObjects python:contextObj.getAppyRefs(fieldName, startNumber);
|
refObjects python:contextObj.getAppyRefs(fieldName, startNumber);
|
||||||
objs refObjects/objects;
|
objs refObjects/objects;
|
||||||
totalNumber refObjects/totalNumber;
|
totalNumber refObjects/totalNumber;
|
||||||
|
@ -121,7 +122,7 @@
|
||||||
showPlusIcon python:not appyType['isBack'] and appyType['add'] and not maxReached and user.has_permission(addPermission, folder) and canWrite;
|
showPlusIcon python:not appyType['isBack'] and appyType['add'] and not maxReached and user.has_permission(addPermission, folder) and canWrite;
|
||||||
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1);
|
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1);
|
||||||
label python: contextObj.translate('label', field=appyType);
|
label python: contextObj.translate('label', field=appyType);
|
||||||
addConfirmMsg python: appyType['addConfirm'] and tool.translate('%s_addConfirm' % appyType['labelId']) or '';
|
addConfirmMsg python: appyType['addConfirm'] and _('%s_addConfirm' % appyType['labelId']) or '';
|
||||||
navBaseCall python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url(), fieldName, innerRef)">
|
navBaseCall python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url(), fieldName, innerRef)">
|
||||||
|
|
||||||
<tal:comment replace="nothing">This macro displays the Reference widget on a "view" page.
|
<tal:comment replace="nothing">This macro displays the Reference widget on a "view" page.
|
||||||
|
@ -139,7 +140,7 @@
|
||||||
|
|
||||||
<tal:comment replace="nothing">If there is no object...</tal:comment>
|
<tal:comment replace="nothing">If there is no object...</tal:comment>
|
||||||
<tal:noObject condition="not:objs">
|
<tal:noObject condition="not:objs">
|
||||||
<td tal:content="python: tool.translate('no_ref')"></td>
|
<td tal:content="python: _('no_ref')"></td>
|
||||||
<td><metal:plusIcon use-macro="app/skyn/widgets/ref/macros/plusIcon"/></td>
|
<td><metal:plusIcon use-macro="app/skyn/widgets/ref/macros/plusIcon"/></td>
|
||||||
</tal:noObject>
|
</tal:noObject>
|
||||||
|
|
||||||
|
@ -162,7 +163,7 @@
|
||||||
<tal:comment replace="nothing">The search icon if field is queryable</tal:comment>
|
<tal:comment replace="nothing">The search icon if field is queryable</tal:comment>
|
||||||
<a tal:condition="appyType/queryable"
|
<a tal:condition="appyType/queryable"
|
||||||
tal:attributes="href python: '%s/skyn/search?type_name=%s&ref=%s:%s' % (tool.absolute_url(), linkedPortalType, contextObj.UID(), appyType['name'])">
|
tal:attributes="href python: '%s/skyn/search?type_name=%s&ref=%s:%s' % (tool.absolute_url(), linkedPortalType, contextObj.UID(), appyType['name'])">
|
||||||
<img src="search.gif" tal:attributes="title python: tool.translate('search_objects')"/></a>
|
<img src="search.gif" tal:attributes="title python: _('search_objects')"/></a>
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Object description</tal:comment>
|
<tal:comment replace="nothing">Object description</tal:comment>
|
||||||
|
@ -173,7 +174,7 @@
|
||||||
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
||||||
|
|
||||||
<tal:comment replace="nothing">No object is present</tal:comment>
|
<tal:comment replace="nothing">No object is present</tal:comment>
|
||||||
<p tal:condition="not:objs" tal:content="python: tool.translate('no_ref')"></p>
|
<p tal:condition="not:objs" tal:content="python: _('no_ref')"></p>
|
||||||
|
|
||||||
<table width="100%" tal:condition="python: objs"
|
<table width="100%" tal:condition="python: objs"
|
||||||
tal:attributes="class python:test(innerRef, 'innerAppyTable', '')">
|
tal:attributes="class python:test(innerRef, 'innerAppyTable', '')">
|
||||||
|
@ -185,10 +186,10 @@
|
||||||
<tal:widgets define="widgets python: objs[0].getAppyTypesFromNames(appyType['shownInfo'])">
|
<tal:widgets define="widgets python: objs[0].getAppyTypesFromNames(appyType['shownInfo'])">
|
||||||
<tr tal:condition="appyType/showHeaders">
|
<tr tal:condition="appyType/showHeaders">
|
||||||
<th tal:repeat="widget widgets">
|
<th tal:repeat="widget widgets">
|
||||||
<span tal:content="python: tool.translate(widget['labelId'])"></span>
|
<span tal:content="python: _(widget['labelId'])"></span>
|
||||||
<metal:sortIcons use-macro="app/skyn/widgets/ref/macros/sortIcons" />
|
<metal:sortIcons use-macro="app/skyn/widgets/ref/macros/sortIcons" />
|
||||||
</th>
|
</th>
|
||||||
<th tal:content="python: tool.translate('ref_actions')"></th>
|
<th tal:content="python: _('ref_actions')"></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tal:row repeat="obj objs">
|
<tal:row repeat="obj objs">
|
||||||
<tr valign="middle" tal:define="odd repeat/obj/odd"
|
<tr valign="middle" tal:define="odd repeat/obj/odd"
|
||||||
|
@ -199,7 +200,7 @@
|
||||||
<metal:showObjectTitle use-macro="app/skyn/widgets/ref/macros/objectTitle"/>
|
<metal:showObjectTitle use-macro="app/skyn/widgets/ref/macros/objectTitle"/>
|
||||||
</tal:title>
|
</tal:title>
|
||||||
<tal:state condition="python: widget['name'] == 'state'"
|
<tal:state condition="python: widget['name'] == 'state'"
|
||||||
content="python: tool.translate(obj.getWorkflowLabel())">
|
content="python: _(obj.getWorkflowLabel())">
|
||||||
</tal:state>
|
</tal:state>
|
||||||
<tal:other condition="python: widget['name'] not in ('title', 'state')">
|
<tal:other condition="python: widget['name'] not in ('title', 'state')">
|
||||||
<tal:field define="contextObj python:obj;
|
<tal:field define="contextObj python:obj;
|
||||||
|
@ -231,14 +232,13 @@
|
||||||
<tal:comment replace="nothing">Edit macro for an Ref.</tal:comment>
|
<tal:comment replace="nothing">Edit macro for an Ref.</tal:comment>
|
||||||
<metal:editRef define-macro="edit"
|
<metal:editRef define-macro="edit"
|
||||||
tal:condition="widget/link"
|
tal:condition="widget/link"
|
||||||
tal:define="rname python: 'appy_ref_%s' % name;
|
tal:define="requestValue python: request.get(name, []);
|
||||||
requestValue python: request.get(rname, []);
|
inRequest python: request.has_key(name);
|
||||||
inRequest python: request.has_key(rname);
|
|
||||||
allObjects python: contextObj.getSelectableAppyRefs(name);
|
allObjects python: contextObj.getSelectableAppyRefs(name);
|
||||||
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
|
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
|
||||||
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
|
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
|
||||||
|
|
||||||
<select tal:attributes="name rname;
|
<select tal:attributes="name name;
|
||||||
size python: test(isMultiple, widget['height'], '');
|
size python: test(isMultiple, widget['height'], '');
|
||||||
multiple python: test(isMultiple, 'multiple', '')">
|
multiple python: test(isMultiple, 'multiple', '')">
|
||||||
<option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option>
|
<option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option>
|
||||||
|
|
|
@ -9,23 +9,24 @@
|
||||||
contextMacro The base folder containing the macros to call for
|
contextMacro The base folder containing the macros to call for
|
||||||
rendering the elements within the layout.
|
rendering the elements within the layout.
|
||||||
Defaults to app.skyn
|
Defaults to app.skyn
|
||||||
slaveId The name and id of the main tag for this layout (used
|
tagId The name and id of the main tag for this layout (used
|
||||||
for master/slave relationships).
|
a.o. for master/slave relationships).
|
||||||
slaveCss The CSS class for a slave.
|
tagCss Some additional CSS class for the main tag
|
||||||
|
(ie, the CSS class for a slave).
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<metal:show define-macro="layout"
|
<metal:show define-macro="layout"
|
||||||
tal:define="contextMacro contextMacro| python: app.skyn;
|
tal:define="contextMacro contextMacro| python: app.skyn;
|
||||||
slaveId slaveId|python:'';
|
tagId tagId|python:'';
|
||||||
slaveCss slaveCss|python:'';
|
tagCss tagCss|python:'';
|
||||||
layoutCss layout/css_class;">
|
layoutCss layout/css_class;">
|
||||||
<table tal:attributes="cellpadding layout/cellpadding;
|
<table tal:attributes="cellpadding layout/cellpadding;
|
||||||
cellspacing layout/cellspacing;
|
cellspacing layout/cellspacing;
|
||||||
width layout/width;
|
width layout/width;
|
||||||
align layout/align;
|
align layout/align;
|
||||||
class python: slaveCss and ('%s %s' % (slaveCss, layoutCss)) or layoutCss;
|
class python: tagCss and ('%s %s' % (tagCss, layoutCss)) or layoutCss;
|
||||||
style layout/style;
|
style layout/style;
|
||||||
id slaveId;
|
id tagId;
|
||||||
name slaveId;">
|
name tagId;">
|
||||||
<tal:comment replace="nothing">The table header row</tal:comment>
|
<tal:comment replace="nothing">The table header row</tal:comment>
|
||||||
<tr tal:condition="layout/headerRow" tal:attributes="valign layout/headerRow/valign">
|
<tr tal:condition="layout/headerRow" tal:attributes="valign layout/headerRow/valign">
|
||||||
<th tal:repeat="cell layout/headerRow/cells"
|
<th tal:repeat="cell layout/headerRow/cells"
|
||||||
|
@ -48,23 +49,27 @@
|
||||||
page The page where the widget lies
|
page The page where the widget lies
|
||||||
layoutType "edit"? "view"? "cell?"
|
layoutType "edit"? "view"? "cell?"
|
||||||
widget The widget to render
|
widget The widget to render
|
||||||
|
|
||||||
|
Optionally:
|
||||||
|
widgetName If the field to show is within a List, we cheat and
|
||||||
|
include, within the widgetName, the row index.
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<metal:field define-macro="field"
|
<metal:field define-macro="field"
|
||||||
tal:define="contextMacro python: app.skyn.widgets;
|
tal:define="contextMacro python: app.skyn.widgets;
|
||||||
layout python: widget['layouts'][layoutType];
|
layout python: widget['layouts'][layoutType];
|
||||||
name widget/name;
|
name widgetName| widget/name;
|
||||||
sync python: widget['sync'][layoutType];
|
sync python: widget['sync'][layoutType];
|
||||||
rawValue python: contextObj.getFieldValue(name, onlyIfSync=True, layoutType=layoutType);
|
rawValue python: contextObj.getFieldValue(name, onlyIfSync=True, layoutType=layoutType);
|
||||||
value python: contextObj.getFormattedFieldValue(name, rawValue);
|
value python: contextObj.getFormattedFieldValue(name, rawValue);
|
||||||
requestValue python: request.get(name, None);
|
requestValue python: contextObj.getRequestFieldValue(name);
|
||||||
inRequest python: request.has_key(name);
|
inRequest python: request.has_key(name);
|
||||||
errors errors | python: ();
|
errors errors | python: ();
|
||||||
inError python: (widget['name'] in errors) and True or False;
|
inError python: name in errors;
|
||||||
isMultiple python: (widget['multiplicity'][1] == None) or (widget['multiplicity'][1] > 1);
|
isMultiple python: (widget['multiplicity'][1] == None) or (widget['multiplicity'][1] > 1);
|
||||||
masterCss python: widget['slaves'] and ('master_%s' % name) or '';
|
masterCss python: widget['slaves'] and ('master_%s' % name) or '';
|
||||||
slaveCss python: widget['master'] and ('slave_%s_%s' % (widget['masterName'], '_'.join(widget['masterValue']))) or '';
|
slaveCss python: widget['master'] and ('slave_%s_%s' % (widget['masterName'], '_'.join(widget['masterValue']))) or '';
|
||||||
slaveId python: widget['master'] and 'slave' or ''">
|
tagCss tagCss | slaveCss;
|
||||||
|
tagId python: widget['master'] and 'slave' or ''">
|
||||||
<metal:layout use-macro="here/skyn/widgets/show/macros/layout"/>
|
<metal:layout use-macro="here/skyn/widgets/show/macros/layout"/>
|
||||||
</metal:field>
|
</metal:field>
|
||||||
|
|
||||||
|
@ -77,10 +82,10 @@
|
||||||
widget The widget to render
|
widget The widget to render
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<metal:group define-macro="group"
|
<metal:group define-macro="group"
|
||||||
tal:define="slaveCss python: widget['master'] and ('slave_%s_%s' % (widget['masterName'], '_'.join(widget['masterValue']))) or '';
|
tal:define="tagCss python: widget['master'] and ('slave_%s_%s' % (widget['masterName'], '_'.join(widget['masterValue']))) or '';
|
||||||
widgetCss widget/css_class;
|
widgetCss widget/css_class;
|
||||||
groupCss python: slaveCss and ('%s %s' % (slaveCss, widgetCss)) or widgetCss;
|
groupCss python: tagCss and ('%s %s' % (tagCss, widgetCss)) or widgetCss;
|
||||||
slaveId python: widget['master'] and 'slave' or ''">
|
tagId python: widget['master'] and 'slave' or ''">
|
||||||
<fieldset tal:condition="python: widget['style'] == 'fieldset'">
|
<fieldset tal:condition="python: widget['style'] == 'fieldset'">
|
||||||
<legend tal:condition="widget/hasLabel">
|
<legend tal:condition="widget/hasLabel">
|
||||||
<i tal:content="structure python: contextObj.translate(widget['labelId'])"></i>
|
<i tal:content="structure python: contextObj.translate(widget['labelId'])"></i>
|
||||||
|
@ -98,7 +103,7 @@
|
||||||
<tal:asTabs condition="python: widget['style'] == 'tabs'">
|
<tal:asTabs condition="python: widget['style'] == 'tabs'">
|
||||||
<table tal:attributes="width widget/wide;
|
<table tal:attributes="width widget/wide;
|
||||||
class groupCss;
|
class groupCss;
|
||||||
id slaveId; name slaveId">
|
id tagId; name tagId">
|
||||||
<tal:comment replace="nothing">First row: the tabs.</tal:comment>
|
<tal:comment replace="nothing">First row: the tabs.</tal:comment>
|
||||||
<tr valign="middle"><td style="border-bottom: 1px solid #ff8040">
|
<tr valign="middle"><td style="border-bottom: 1px solid #ff8040">
|
||||||
<table style="position:relative; bottom:-1px;">
|
<table style="position:relative; bottom:-1px;">
|
||||||
|
@ -150,7 +155,7 @@
|
||||||
class groupCss;
|
class groupCss;
|
||||||
cellspacing widget/cellspacing;
|
cellspacing widget/cellspacing;
|
||||||
cellpadding widget/cellpadding;
|
cellpadding widget/cellpadding;
|
||||||
id slaveId; name slaveId">
|
id tagId; name tagId">
|
||||||
<tal:comment replace="nothing">Display the title of the group if it is not rendered a fieldset.</tal:comment>
|
<tal:comment replace="nothing">Display the title of the group if it is not rendered a fieldset.</tal:comment>
|
||||||
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
|
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
|
||||||
<td tal:attributes="colspan python: len(widget['columnsWidths']);
|
<td tal:attributes="colspan python: len(widget['columnsWidths']);
|
||||||
|
|
|
@ -8,10 +8,10 @@ from appy.gen.plone25.mixins.TestMixin import TestMixin, beforeTest, afterTest
|
||||||
<!imports!>
|
<!imports!>
|
||||||
|
|
||||||
# Initialize Zope & Plone test systems -----------------------------------------
|
# Initialize Zope & Plone test systems -----------------------------------------
|
||||||
ZopeTestCase.installProduct('<!applicationName!>')
|
|
||||||
ZopeTestCase.installProduct('PloneLanguageTool')
|
ZopeTestCase.installProduct('PloneLanguageTool')
|
||||||
PloneTestCase.setupPloneSite(products=['<!applicationName!>',
|
ZopeTestCase.installProduct('<!applicationName!>')
|
||||||
'PloneLanguageTool'])
|
PloneTestCase.setupPloneSite(products=['PloneLanguageTool',
|
||||||
|
'<!applicationName!>'])
|
||||||
|
|
||||||
class Test(PloneTestCase.PloneTestCase, TestMixin):
|
class Test(PloneTestCase.PloneTestCase, TestMixin):
|
||||||
'''Base test class for <!applicationName!> test cases.'''
|
'''Base test class for <!applicationName!> test cases.'''
|
||||||
|
|
|
@ -189,6 +189,7 @@ class StylesManager:
|
||||||
self.textStyles = self.styles.getStyles('text')
|
self.textStyles = self.styles.getStyles('text')
|
||||||
# List of paragraph styles derived from self.styles
|
# List of paragraph styles derived from self.styles
|
||||||
self.paragraphStyles = self.styles.getStyles('paragraph')
|
self.paragraphStyles = self.styles.getStyles('paragraph')
|
||||||
|
|
||||||
def checkStylesAdequation(self, htmlStyle, odtStyle):
|
def checkStylesAdequation(self, htmlStyle, odtStyle):
|
||||||
'''Checks that p_odtStyle my be used for style p_htmlStyle.'''
|
'''Checks that p_odtStyle my be used for style p_htmlStyle.'''
|
||||||
if (htmlStyle in XHTML_PARAGRAPH_TAGS_NO_LISTS) and \
|
if (htmlStyle in XHTML_PARAGRAPH_TAGS_NO_LISTS) and \
|
||||||
|
@ -199,6 +200,7 @@ class StylesManager:
|
||||||
(odtStyle in self.paragraphStyles):
|
(odtStyle in self.paragraphStyles):
|
||||||
raise PodError(HTML_TEXT_ODT_PARA % (
|
raise PodError(HTML_TEXT_ODT_PARA % (
|
||||||
htmlStyle, odtStyle.displayName))
|
htmlStyle, odtStyle.displayName))
|
||||||
|
|
||||||
def checkStylesMapping(self, stylesMapping):
|
def checkStylesMapping(self, stylesMapping):
|
||||||
'''Checks that the given p_stylesMapping is correct. Returns the same
|
'''Checks that the given p_stylesMapping is correct. Returns the same
|
||||||
dict as p_stylesMapping, but with Style instances as values, instead
|
dict as p_stylesMapping, but with Style instances as values, instead
|
||||||
|
|
|
@ -148,11 +148,15 @@ class HtmlDiff:
|
||||||
if action == 'equal':
|
if action == 'equal':
|
||||||
toAdd = sep.join(a[i1:i2])
|
toAdd = sep.join(a[i1:i2])
|
||||||
elif action == 'insert':
|
elif action == 'insert':
|
||||||
|
print 'INSERT', b[j1:j2]
|
||||||
toAdd = self.getModifiedChunk(b[j1:j2], action, sep)
|
toAdd = self.getModifiedChunk(b[j1:j2], action, sep)
|
||||||
elif action == 'delete':
|
elif action == 'delete':
|
||||||
|
print 'DELETE', a[i1:i2]
|
||||||
toAdd = self.getModifiedChunk(a[i1:i2], action, sep)
|
toAdd = self.getModifiedChunk(a[i1:i2], action, sep)
|
||||||
elif action == 'replace':
|
elif action == 'replace':
|
||||||
if sep == '\n':
|
if sep == '\n':
|
||||||
|
print 'REPLACE', a[i1:i2]
|
||||||
|
print 'WITH', b[j1:j2]
|
||||||
# We know that some lines have been replaced from a to b. By
|
# We know that some lines have been replaced from a to b. By
|
||||||
# identifying similarities between those lines, consider
|
# identifying similarities between those lines, consider
|
||||||
# some as having been deleted, modified or inserted.
|
# some as having been deleted, modified or inserted.
|
||||||
|
@ -184,5 +188,6 @@ class HtmlDiff:
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
'''Produces the result.'''
|
'''Produces the result.'''
|
||||||
|
print 'RUN'
|
||||||
return self.getHtmlDiff(self.old, self.new, '\n')
|
return self.getHtmlDiff(self.old, self.new, '\n')
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in a new issue