appy.gen: added a widget 'List' for rendering grids of data.

This commit is contained in:
Gaetan Delannay 2011-10-19 09:37:44 +02:00
parent f1136eb786
commit c11378c747
11 changed files with 315 additions and 41 deletions

View file

@ -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'),

View file

@ -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.'''

View file

@ -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

View file

@ -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;

View file

@ -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);
}

View 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>

View file

@ -28,13 +28,13 @@
<tal:comment replace="nothing">Move up</tal:comment>
<img tal:condition="python: objectIndex &gt; 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 &lt; (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\', &quot;%s&quot;, &quot;%s&quot;)' % (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)&lt;=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>

View file

@ -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] &gt; 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']);

View file

@ -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.'''

View file

@ -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

View file

@ -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')
# ------------------------------------------------------------------------------