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.po import PoMessage
|
||||
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \
|
||||
getClassName, SomeObjects
|
||||
getClassName, SomeObjects, AppyObject
|
||||
import appy.pod
|
||||
from appy.pod.renderer import Renderer
|
||||
from appy.shared.data import countries
|
||||
|
@ -517,6 +517,11 @@ class Type:
|
|||
# We must initialise the corresponding back reference
|
||||
self.back.klass = klass
|
||||
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):
|
||||
'''In debug mode, we want to reload layouts without restarting Zope.
|
||||
|
@ -1534,7 +1539,6 @@ class File(Type):
|
|||
return value._atFile
|
||||
|
||||
def getRequestValue(self, request):
|
||||
res = request.get('%s_file' % self.name)
|
||||
return request.get('%s_file' % self.name)
|
||||
|
||||
def getDefaultLayouts(self): return {'view':'lf','edit':'lrv-f'}
|
||||
|
@ -1784,9 +1788,6 @@ class Ref(Type):
|
|||
def getFormattedValue(self, obj, value):
|
||||
return value
|
||||
|
||||
def getRequestValue(self, request):
|
||||
return request.get('appy_ref_%s' % self.name, None)
|
||||
|
||||
def validateValue(self, obj, value):
|
||||
if not self.link: return None
|
||||
# We only check "link" Refs because in edit views, "add" Refs are
|
||||
|
@ -2210,6 +2211,94 @@ class Pod(Type):
|
|||
value = value._atFile
|
||||
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 --------------------------------
|
||||
appyToZopePermissions = {
|
||||
'read': ('View', 'Access contents information'),
|
||||
|
|
|
@ -120,6 +120,14 @@ class FieldDescriptor:
|
|||
# Add the POD-related fields on the Tool
|
||||
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):
|
||||
'''Walks into the Appy type definition and gathers data about the
|
||||
i18n labels.'''
|
||||
|
@ -176,6 +184,8 @@ class FieldDescriptor:
|
|||
elif self.appyType.type == 'Ref': self.walkRef()
|
||||
# Manage things which are specific to Pod types
|
||||
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):
|
||||
'''Generates the i18n labels for this type.'''
|
||||
|
|
|
@ -142,6 +142,15 @@ class BaseMixin:
|
|||
setattr(errors, appyType.name, message)
|
||||
else:
|
||||
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):
|
||||
'''This method is called when individual validation of all fields
|
||||
|
@ -432,13 +441,32 @@ class BaseMixin:
|
|||
retrieved in synchronous mode.'''
|
||||
appyType = self.getAppyType(name)
|
||||
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):
|
||||
'''Gets a nice, string representation of p_value which is a value from
|
||||
field named p_name.'''
|
||||
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):
|
||||
'''Returns filename and size of p_fileObject.'''
|
||||
if not fileObject: return {'filename': '', 'size': 0}
|
||||
|
@ -533,11 +561,15 @@ class BaseMixin:
|
|||
def getAppyType(self, name, asDict=False, className=None):
|
||||
'''Returns the Appy type named p_name. If no p_className is defined, the
|
||||
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:
|
||||
klass = self.__class__.wrapperClass
|
||||
else:
|
||||
klass = self.getTool().getAppyClass(className, wrapper=True)
|
||||
res = getattr(klass, name, None)
|
||||
if res and isInnerType: res = res.getField(subName)
|
||||
if res and asDict: return res.__dict__
|
||||
return res
|
||||
|
||||
|
|
|
@ -65,6 +65,9 @@ img {border: 0;}
|
|||
.list td, .list th { border: 1px solid grey;
|
||||
padding-left: 5px; padding-right: 5px; padding-top: 3px;}
|
||||
.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 td { border:0 !important; padding:0 !important; margin:0 !important; }
|
||||
.translationLabel { background-color: #EAEAEA; border-bottom: 1px dashed grey;
|
||||
|
|
|
@ -492,3 +492,59 @@ function initTab(cookieId, defaultValue) {
|
|||
if (!toSelect) { showTab(defaultValue) }
|
||||
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>
|
||||
<img tal:condition="python: objectIndex > 0"
|
||||
tal:attributes="src string: $appUrl/skyn/arrowUp.png;
|
||||
title python: tool.translate('move_up');
|
||||
title python: _('move_up');
|
||||
onClick python: ajaxBaseCall.replace('**v**', 'up')"
|
||||
style="cursor:pointer"/>
|
||||
<tal:comment replace="nothing">Move down</tal:comment>
|
||||
<img tal:condition="python: objectIndex < (totalNumber-1)"
|
||||
tal:attributes="src string: $appUrl/skyn/arrowDown.png;
|
||||
title python: tool.translate('move_down');
|
||||
title python: _('move_down');
|
||||
onClick python: ajaxBaseCall.replace('**v**', 'down')"
|
||||
style="cursor:pointer"/>
|
||||
</tal:moveRef>
|
||||
|
@ -69,7 +69,7 @@
|
|||
noFormCall python: navBaseCall.replace('**v**', '%d, \'CreateWithoutForm\'' % startNumber);
|
||||
noFormCall python: test(appyType['addConfirm'], 'askConfirm(\'script\', "%s", "%s")' % (noFormCall, addConfirmMsg), noFormCall)"
|
||||
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)"/>
|
||||
</metal:plusIcon>
|
||||
|
||||
|
@ -108,6 +108,7 @@
|
|||
ajaxHookId python: contextObj.UID()+fieldName;
|
||||
startNumber python: int(request.get('%s_startNumber' % ajaxHookId, 0));
|
||||
tool contextObj/getTool;
|
||||
_ python: tool.translate;
|
||||
refObjects python:contextObj.getAppyRefs(fieldName, startNumber);
|
||||
objs refObjects/objects;
|
||||
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;
|
||||
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1);
|
||||
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)">
|
||||
|
||||
<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: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>
|
||||
</tal:noObject>
|
||||
|
||||
|
@ -162,7 +163,7 @@
|
|||
<tal:comment replace="nothing">The search icon if field is queryable</tal:comment>
|
||||
<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'])">
|
||||
<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>
|
||||
|
||||
<tal:comment replace="nothing">Object description</tal:comment>
|
||||
|
@ -173,7 +174,7 @@
|
|||
<metal:nav use-macro="here/skyn/navigate/macros/appyNavigate"/>
|
||||
|
||||
<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"
|
||||
tal:attributes="class python:test(innerRef, 'innerAppyTable', '')">
|
||||
|
@ -185,10 +186,10 @@
|
|||
<tal:widgets define="widgets python: objs[0].getAppyTypesFromNames(appyType['shownInfo'])">
|
||||
<tr tal:condition="appyType/showHeaders">
|
||||
<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" />
|
||||
</th>
|
||||
<th tal:content="python: tool.translate('ref_actions')"></th>
|
||||
<th tal:content="python: _('ref_actions')"></th>
|
||||
</tr>
|
||||
<tal:row repeat="obj objs">
|
||||
<tr valign="middle" tal:define="odd repeat/obj/odd"
|
||||
|
@ -199,7 +200,7 @@
|
|||
<metal:showObjectTitle use-macro="app/skyn/widgets/ref/macros/objectTitle"/>
|
||||
</tal:title>
|
||||
<tal:state condition="python: widget['name'] == 'state'"
|
||||
content="python: tool.translate(obj.getWorkflowLabel())">
|
||||
content="python: _(obj.getWorkflowLabel())">
|
||||
</tal:state>
|
||||
<tal:other condition="python: widget['name'] not in ('title', 'state')">
|
||||
<tal:field define="contextObj python:obj;
|
||||
|
@ -231,14 +232,13 @@
|
|||
<tal:comment replace="nothing">Edit macro for an Ref.</tal:comment>
|
||||
<metal:editRef define-macro="edit"
|
||||
tal:condition="widget/link"
|
||||
tal:define="rname python: 'appy_ref_%s' % name;
|
||||
requestValue python: request.get(rname, []);
|
||||
inRequest python: request.has_key(rname);
|
||||
tal:define="requestValue python: request.get(name, []);
|
||||
inRequest python: request.has_key(name);
|
||||
allObjects python: contextObj.getSelectableAppyRefs(name);
|
||||
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
|
||||
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'], '');
|
||||
multiple python: test(isMultiple, 'multiple', '')">
|
||||
<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
|
||||
rendering the elements within the layout.
|
||||
Defaults to app.skyn
|
||||
slaveId The name and id of the main tag for this layout (used
|
||||
for master/slave relationships).
|
||||
slaveCss The CSS class for a slave.
|
||||
tagId The name and id of the main tag for this layout (used
|
||||
a.o. for master/slave relationships).
|
||||
tagCss Some additional CSS class for the main tag
|
||||
(ie, the CSS class for a slave).
|
||||
</tal:comment>
|
||||
<metal:show define-macro="layout"
|
||||
tal:define="contextMacro contextMacro| python: app.skyn;
|
||||
slaveId slaveId|python:'';
|
||||
slaveCss slaveCss|python:'';
|
||||
tagId tagId|python:'';
|
||||
tagCss tagCss|python:'';
|
||||
layoutCss layout/css_class;">
|
||||
<table tal:attributes="cellpadding layout/cellpadding;
|
||||
cellspacing layout/cellspacing;
|
||||
width layout/width;
|
||||
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;
|
||||
id slaveId;
|
||||
name slaveId;">
|
||||
id tagId;
|
||||
name tagId;">
|
||||
<tal:comment replace="nothing">The table header row</tal:comment>
|
||||
<tr tal:condition="layout/headerRow" tal:attributes="valign layout/headerRow/valign">
|
||||
<th tal:repeat="cell layout/headerRow/cells"
|
||||
|
@ -48,23 +49,27 @@
|
|||
page The page where the widget lies
|
||||
layoutType "edit"? "view"? "cell?"
|
||||
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>
|
||||
<metal:field define-macro="field"
|
||||
tal:define="contextMacro python: app.skyn.widgets;
|
||||
layout python: widget['layouts'][layoutType];
|
||||
name widget/name;
|
||||
name widgetName| widget/name;
|
||||
sync python: widget['sync'][layoutType];
|
||||
rawValue python: contextObj.getFieldValue(name, onlyIfSync=True, layoutType=layoutType);
|
||||
value python: contextObj.getFormattedFieldValue(name, rawValue);
|
||||
requestValue python: request.get(name, None);
|
||||
requestValue python: contextObj.getRequestFieldValue(name);
|
||||
inRequest python: request.has_key(name);
|
||||
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);
|
||||
masterCss python: widget['slaves'] and ('master_%s' % name) 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:field>
|
||||
|
||||
|
@ -77,10 +82,10 @@
|
|||
widget The widget to render
|
||||
</tal:comment>
|
||||
<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;
|
||||
groupCss python: slaveCss and ('%s %s' % (slaveCss, widgetCss)) or widgetCss;
|
||||
slaveId python: widget['master'] and 'slave' or ''">
|
||||
groupCss python: tagCss and ('%s %s' % (tagCss, widgetCss)) or widgetCss;
|
||||
tagId python: widget['master'] and 'slave' or ''">
|
||||
<fieldset tal:condition="python: widget['style'] == 'fieldset'">
|
||||
<legend tal:condition="widget/hasLabel">
|
||||
<i tal:content="structure python: contextObj.translate(widget['labelId'])"></i>
|
||||
|
@ -98,7 +103,7 @@
|
|||
<tal:asTabs condition="python: widget['style'] == 'tabs'">
|
||||
<table tal:attributes="width widget/wide;
|
||||
class groupCss;
|
||||
id slaveId; name slaveId">
|
||||
id tagId; name tagId">
|
||||
<tal:comment replace="nothing">First row: the tabs.</tal:comment>
|
||||
<tr valign="middle"><td style="border-bottom: 1px solid #ff8040">
|
||||
<table style="position:relative; bottom:-1px;">
|
||||
|
@ -150,7 +155,7 @@
|
|||
class groupCss;
|
||||
cellspacing widget/cellspacing;
|
||||
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>
|
||||
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
|
||||
<td tal:attributes="colspan python: len(widget['columnsWidths']);
|
||||
|
|
|
@ -8,10 +8,10 @@ from appy.gen.plone25.mixins.TestMixin import TestMixin, beforeTest, afterTest
|
|||
<!imports!>
|
||||
|
||||
# Initialize Zope & Plone test systems -----------------------------------------
|
||||
ZopeTestCase.installProduct('<!applicationName!>')
|
||||
ZopeTestCase.installProduct('PloneLanguageTool')
|
||||
PloneTestCase.setupPloneSite(products=['<!applicationName!>',
|
||||
'PloneLanguageTool'])
|
||||
ZopeTestCase.installProduct('<!applicationName!>')
|
||||
PloneTestCase.setupPloneSite(products=['PloneLanguageTool',
|
||||
'<!applicationName!>'])
|
||||
|
||||
class Test(PloneTestCase.PloneTestCase, TestMixin):
|
||||
'''Base test class for <!applicationName!> test cases.'''
|
||||
|
|
|
@ -189,6 +189,7 @@ class StylesManager:
|
|||
self.textStyles = self.styles.getStyles('text')
|
||||
# List of paragraph styles derived from self.styles
|
||||
self.paragraphStyles = self.styles.getStyles('paragraph')
|
||||
|
||||
def checkStylesAdequation(self, htmlStyle, odtStyle):
|
||||
'''Checks that p_odtStyle my be used for style p_htmlStyle.'''
|
||||
if (htmlStyle in XHTML_PARAGRAPH_TAGS_NO_LISTS) and \
|
||||
|
@ -199,6 +200,7 @@ class StylesManager:
|
|||
(odtStyle in self.paragraphStyles):
|
||||
raise PodError(HTML_TEXT_ODT_PARA % (
|
||||
htmlStyle, odtStyle.displayName))
|
||||
|
||||
def checkStylesMapping(self, stylesMapping):
|
||||
'''Checks that the given p_stylesMapping is correct. Returns the same
|
||||
dict as p_stylesMapping, but with Style instances as values, instead
|
||||
|
|
|
@ -148,11 +148,15 @@ class HtmlDiff:
|
|||
if action == 'equal':
|
||||
toAdd = sep.join(a[i1:i2])
|
||||
elif action == 'insert':
|
||||
print 'INSERT', b[j1:j2]
|
||||
toAdd = self.getModifiedChunk(b[j1:j2], action, sep)
|
||||
elif action == 'delete':
|
||||
print 'DELETE', a[i1:i2]
|
||||
toAdd = self.getModifiedChunk(a[i1:i2], action, sep)
|
||||
elif action == 'replace':
|
||||
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
|
||||
# identifying similarities between those lines, consider
|
||||
# some as having been deleted, modified or inserted.
|
||||
|
@ -184,5 +188,6 @@ class HtmlDiff:
|
|||
|
||||
def get(self):
|
||||
'''Produces the result.'''
|
||||
print 'RUN'
|
||||
return self.getHtmlDiff(self.old, self.new, '\n')
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue