[gen] Added the possibility to span widgets on several columns on the search screen (param Field.scolspan).

This commit is contained in:
Gaetan Delannay 2012-12-14 09:23:33 +01:00
parent 3f75d14e92
commit 0dae8b1888
9 changed files with 79 additions and 56 deletions

View file

@ -379,7 +379,7 @@ class Type:
layouts, move, indexed, searchable, specificReadPermission, layouts, move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, maxChars, colspan, specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, sync, mapping, label, master, masterValue, focus, historized, sync, mapping, label,
defaultForSearch): sdefault, scolspan):
# The validator restricts which values may be defined. It can be an # The validator restricts which values may be defined. It can be an
# interval (1,None), a list of string values ['choice1', 'choice2'], # interval (1,None), a list of string values ['choice1', 'choice2'],
# a regular expression, a custom function, a Selection instance, etc. # a regular expression, a custom function, a Selection instance, etc.
@ -473,10 +473,12 @@ class Type:
# prefix and another name. If you want to specify a new name only, and # prefix and another name. If you want to specify a new name only, and
# not a prefix, write (None, newName). # not a prefix, write (None, newName).
self.label = label self.label = label
# When you specify a default value "for search", on a search screen, in # When you specify a default value "for search" (= "sdefault"), on a
# the search field corresponding to this field, this default value will # search screen, in the search field corresponding to this field, this
# be present. # default value will be present.
self.defaultForSearch = defaultForSearch self.sdefault = sdefault
# Colspan for rendering the search widget corresponding to this field.
self.scolspan = scolspan
def init(self, name, klass, appName): def init(self, name, klass, appName):
'''When the application server starts, this secondary constructor is '''When the application server starts, this secondary constructor is
@ -952,12 +954,12 @@ class Integer(Type):
specificWritePermission=False, width=6, height=None, specificWritePermission=False, width=6, height=None,
maxChars=13, colspan=1, master=None, masterValue=None, maxChars=13, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
defaultForSearch=('','')): sdefault=('',''), scolspan=1):
Type.__init__(self, validator, multiplicity, default, show, page, group, Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, searchable,specificReadPermission, layouts, move, indexed, searchable,specificReadPermission,
specificWritePermission, width, height, maxChars, colspan, specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, True, mapping, master, masterValue, focus, historized, True, mapping,
label, defaultForSearch) label, sdefault, scolspan)
self.pythonType = long self.pythonType = long
def validateValue(self, obj, value): def validateValue(self, obj, value):
@ -982,7 +984,7 @@ class Float(Type):
specificWritePermission=False, width=6, height=None, specificWritePermission=False, width=6, height=None,
maxChars=13, colspan=1, master=None, masterValue=None, maxChars=13, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
defaultForSearch=('',''), precision=None, sep=(',', '.'), sdefault=('',''), scolspan=1, precision=None, sep=(',', '.'),
tsep=' '): tsep=' '):
# The precision is the number of decimal digits. This number is used # The precision is the number of decimal digits. This number is used
# for rendering the float, but the internal float representation is not # for rendering the float, but the internal float representation is not
@ -1004,7 +1006,7 @@ class Float(Type):
layouts, move, indexed, False, specificReadPermission, layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, maxChars, colspan, specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, True, mapping, master, masterValue, focus, historized, True, mapping,
label, defaultForSearch) label, sdefault, scolspan)
self.pythonType = float self.pythonType = float
def getFormattedValue(self, obj, value): def getFormattedValue(self, obj, value):
@ -1135,9 +1137,9 @@ class String(Type):
specificReadPermission=False, specificWritePermission=False, specificReadPermission=False, specificWritePermission=False,
width=None, height=None, maxChars=None, colspan=1, master=None, width=None, height=None, maxChars=None, colspan=1, master=None,
masterValue=None, focus=False, historized=False, mapping=None, masterValue=None, focus=False, historized=False, mapping=None,
label=None, defaultForSearch='', transform='none', label=None, sdefault='', scolspan=1,
styles=('p','h1','h2','h3','h4'), allowImageUpload=True, transform='none', styles=('p','h1','h2','h3','h4'),
richText=False): allowImageUpload=True, richText=False):
# According to format, the widget will be different: input field, # According to format, the widget will be different: input field,
# textarea, inline editor... Note that there can be only one String # textarea, inline editor... Note that there can be only one String
# field of format CAPTCHA by page, because the captcha challenge is # field of format CAPTCHA by page, because the captcha challenge is
@ -1164,11 +1166,11 @@ class String(Type):
layouts, move, indexed, searchable,specificReadPermission, layouts, move, indexed, searchable,specificReadPermission,
specificWritePermission, width, height, maxChars, colspan, specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, True, mapping, master, masterValue, focus, historized, True, mapping,
label, defaultForSearch) label, sdefault, scolspan)
self.isSelect = self.isSelection() self.isSelect = self.isSelection()
# If self.isSelect, self.defaultForSearch must be a list of value(s). # If self.isSelect, self.sdefault must be a list of value(s).
if self.isSelect and not defaultForSearch: if self.isSelect and not sdefault:
self.defaultForSearch = [] self.sdefault = []
# Default width, height and maxChars vary according to String format # Default width, height and maxChars vary according to String format
if width == None: if width == None:
if format == String.TEXT: self.width = 60 if format == String.TEXT: self.width = 60
@ -1446,12 +1448,12 @@ class Boolean(Type):
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
maxChars=None, colspan=1, master=None, masterValue=None, maxChars=None, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
defaultForSearch=False): sdefault=False, scolspan=1):
Type.__init__(self, validator, multiplicity, default, show, page, group, Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, searchable,specificReadPermission, layouts, move, indexed, searchable,specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, True, mapping, master, masterValue, focus, historized, True, mapping,
label, defaultForSearch) label, sdefault, scolspan)
self.pythonType = bool self.pythonType = bool
dLayouts = {'view': 'lf', 'edit': Table('flrv;=d', width=None)} dLayouts = {'view': 'lf', 'edit': Table('flrv;=d', width=None)}
@ -1495,7 +1497,7 @@ class Date(Type):
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
maxChars=None, colspan=1, master=None, masterValue=None, maxChars=None, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
defaultForSearch=None): sdefault=None, scolspan=1):
self.format = format self.format = format
self.calendar = calendar self.calendar = calendar
self.startYear = startYear self.startYear = startYear
@ -1507,7 +1509,7 @@ class Date(Type):
layouts, move, indexed, searchable,specificReadPermission, layouts, move, indexed, searchable,specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, True, mapping, master, masterValue, focus, historized, True, mapping,
label, defaultForSearch) label, sdefault, scolspan)
def getCss(self, layoutType, res): def getCss(self, layoutType, res):
# CSS files are only required if the calendar must be shown. # CSS files are only required if the calendar must be shown.
@ -1574,13 +1576,13 @@ class File(Type):
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
maxChars=None, colspan=1, master=None, masterValue=None, maxChars=None, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
isImage=False, defaultForSearch=''): isImage=False, sdefault='', scolspan=1):
self.isImage = isImage self.isImage = isImage
Type.__init__(self, validator, multiplicity, default, show, page, group, Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, False, specificReadPermission, layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, True, mapping, master, masterValue, focus, historized, True, mapping,
label, defaultForSearch) label, sdefault, scolspan)
@staticmethod @staticmethod
def getFileObject(filePath, fileName=None, zope=False): def getFileObject(filePath, fileName=None, zope=False):
@ -1735,7 +1737,7 @@ class Ref(Type):
masterValue=None, focus=False, historized=False, mapping=None, masterValue=None, focus=False, historized=False, mapping=None,
label=None, queryable=False, queryFields=None, queryNbCols=1, label=None, queryable=False, queryFields=None, queryNbCols=1,
navigable=False, searchSelect=None, changeOrder=True, navigable=False, searchSelect=None, changeOrder=True,
defaultForSearch=''): sdefault='', scolspan=1):
self.klass = klass self.klass = klass
self.attribute = attribute self.attribute = attribute
# May the user add new objects through this ref ? # May the user add new objects through this ref ?
@ -1815,7 +1817,7 @@ class Ref(Type):
layouts, move, indexed, False, specificReadPermission, layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, sync, mapping, master, masterValue, focus, historized, sync, mapping,
label, defaultForSearch) label, sdefault, scolspan)
self.validable = self.link self.validable = self.link
def getDefaultLayouts(self): def getDefaultLayouts(self):
@ -2078,7 +2080,7 @@ class Computed(Type):
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
maxChars=None, colspan=1, method=None, plainText=True, maxChars=None, colspan=1, method=None, plainText=True,
master=None, masterValue=None, focus=False, historized=False, master=None, masterValue=None, focus=False, historized=False,
sync=True, mapping=None, label=None, defaultForSearch='', sync=True, mapping=None, label=None, sdefault='', scolspan=1,
context={}): context={}):
# The Python method used for computing the field value # The Python method used for computing the field value
self.method = method self.method = method
@ -2096,7 +2098,7 @@ class Computed(Type):
layouts, move, indexed, False, specificReadPermission, layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, sync, mapping, master, masterValue, focus, historized, sync, mapping,
label, defaultForSearch) label, sdefault, scolspan)
self.validable = False self.validable = False
def callMacro(self, obj, macroPath): def callMacro(self, obj, macroPath):
@ -2166,7 +2168,7 @@ class Action(Type):
move, indexed, False, specificReadPermission, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, False, mapping, master, masterValue, focus, historized, False, mapping,
label, None) label, None, None)
self.validable = False self.validable = False
def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'} def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'}
@ -2214,7 +2216,7 @@ class Info(Type):
move, indexed, False, specificReadPermission, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, False, mapping, master, masterValue, focus, historized, False, mapping,
label, None) label, None, None)
self.validable = False self.validable = False
class Pod(Type): class Pod(Type):
@ -2253,7 +2255,7 @@ class Pod(Type):
move, indexed, searchable, specificReadPermission, move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, False, mapping, master, masterValue, focus, historized, False, mapping,
label, None) label, None, None)
self.validable = False self.validable = False
def isFrozen(self, obj): def isFrozen(self, obj):
@ -2397,7 +2399,7 @@ class List(Type):
layouts, move, indexed, False, specificReadPermission, layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, True, mapping, master, masterValue, focus, historized, True, mapping,
label, None) label, None, None)
self.validable = True self.validable = True
# Tuples of (names, Type instances) determining the format of every # Tuples of (names, Type instances) determining the format of every
# element in the list. # element in the list.

View file

@ -25,7 +25,7 @@ class Calendar(Type):
layouts, move, False, False, specificReadPermission, layouts, move, False, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, True, mapping, label, master, masterValue, focus, False, True, mapping, label,
None) None, None)
# eventTypes can be a "static" list or tuple of strings that identify # eventTypes can be a "static" list or tuple of strings that identify
# the types of events that are supported by this calendar. It can also # the types of events that are supported by this calendar. It can also
# be a method that computes such a "dynamic" list or tuple. When # be a method that computes such a "dynamic" list or tuple. When

View file

@ -870,21 +870,35 @@ class ToolMixin(BaseMixin):
page=self.REQUEST.get('page', 'main')) page=self.REQUEST.get('page', 'main'))
return res return res
def tabularize(self, data, numberOfRows): def getGroupedSearchFields(self, searchInfo):
'''This method transforms p_data, which must be a "flat" list or tuple, '''This method transforms p_searchInfo['fieldDicts'], which is a "flat"
into a list of lists, where every sub-list has length p_numberOfRows. list of fields, into a list of lists, where every sub-list having
This method is typically used for rendering elements in a table of length p_searchInfo['nbOfColumns']. For every field, scolspan
p_numberOfRows rows.''' (=colspan "for search") is taken into account.'''
res = [] res = []
row = [] row = []
for elem in data: rowLength = 0
row.append(elem) for field in searchInfo['fieldDicts']:
if len(row) == numberOfRows: # Can I insert this field in the current row?
remaining = searchInfo['nbOfColumns'] - rowLength
if field['scolspan'] <= remaining:
# Yes.
row.append(field)
rowLength += field['scolspan']
else:
# We must put the field on a new line. Complete the current one
# if not complete.
while rowLength < searchInfo['nbOfColumns']:
row.append(None)
rowLength += 1
res.append(row) res.append(row)
row = [] row = [field]
rowLength = field['scolspan']
# Complete the last unfinished line if required. # Complete the last unfinished line if required.
if row: if row:
while len(row) < numberOfRows: row.append(None) while rowLength < searchInfo['nbOfColumns']:
row.append(None)
rowLength += 1
res.append(row) res.append(row)
return res return res
@ -1171,6 +1185,9 @@ class ToolMixin(BaseMixin):
klass = self.getAppyClass(className, wrapper=True) klass = self.getAppyClass(className, wrapper=True)
method = getattr(klass, name).searchSelect method = getattr(klass, name).searchSelect
tool = self.appy() tool = self.appy()
if method.__class__.__name__ == 'function':
objects = method(tool)
else:
objects = method.__get__(tool)(tool) objects = method.__get__(tool)(tool)
return [(o.uid, o) for o in objects] return [(o.uid, o) for o in objects]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -39,7 +39,7 @@ class Ogone(Type):
False, False,specificReadPermission, False, False,specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, True, mapping, label, master, masterValue, focus, False, True, mapping, label,
None) None, None)
# orderMethod must contain a method returning a dict containing info # orderMethod must contain a method returning a dict containing info
# about the order. Following keys are mandatory: # about the order. Following keys are mandatory:
# * orderID An identifier for the order. Don't use the object UID # * orderID An identifier for the order. Don't use the object UID

View file

@ -24,9 +24,12 @@
<input tal:condition="refInfo" type="hidden" name="ref" tal:attributes="value refInfo"/> <input tal:condition="refInfo" type="hidden" name="ref" tal:attributes="value refInfo"/>
<table width="100%"> <table width="100%">
<tr tal:repeat="searchRow python: tool.tabularize(searchInfo['fieldDicts'], searchInfo['nbOfColumns'])" <tr tal:repeat="searchRow python: tool.getGroupedSearchFields(searchInfo)"
valign="top"> valign="top">
<td tal:repeat="widget searchRow" tal:attributes="width python:'%d%%' % (100/searchInfo['nbOfColumns'])"> <tal:widgets repeat="widget searchRow">
<td tal:define="scolspan widget/scolspan|python: 1"
tal:attributes="width python:'%d%%' % ((100/searchInfo['nbOfColumns'])*scolspan);
colspan scolspan">
<tal:field condition="widget"> <tal:field condition="widget">
<tal:show define="name widget/name; <tal:show define="name widget/name;
widgetName python: 'w_%s' % name; widgetName python: 'w_%s' % name;
@ -35,6 +38,7 @@
</tal:show> </tal:show>
</tal:field><br class="discreet"/> </tal:field><br class="discreet"/>
</td> </td>
</tal:widgets>
</tr> </tr>
</table> </table>

View file

@ -38,6 +38,6 @@
<label tal:attributes="for widgetName" tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp; <label tal:attributes="for widgetName" tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp;
<input type="text" tal:define="maxChars python: test(widget['maxChars'], widget['maxChars'], '')" <input type="text" tal:define="maxChars python: test(widget['maxChars'], widget['maxChars'], '')"
tal:attributes="name python: '%s*string' % widgetName; tal:attributes="name python: '%s*string' % widgetName;
maxlength maxChars; maxlength maxChars; size widget/width;
value widget/defaultForSearch"/> value widget/sdefault"/>
</metal:search> </metal:search>

View file

@ -25,12 +25,12 @@
<label tal:attributes="for fromName" tal:content="python: _('search_from')"></label> <label tal:attributes="for fromName" tal:content="python: _('search_from')"></label>
<input type="text" size="4" <input type="text" size="4"
tal:attributes="name fromName; maxlength maxChars; tal:attributes="name fromName; maxlength maxChars;
value python: widget['defaultForSearch'][0]"/> value python: widget['sdefault'][0]"/>
</tal:from> </tal:from>
<tal:to define="toName python: '%s_to' % name"> <tal:to define="toName python: '%s_to' % name">
<label tal:attributes="for toName" tal:content="python: _('search_to')"></label> <label tal:attributes="for toName" tal:content="python: _('search_to')"></label>
<input type="text" size="4" <input type="text" size="4"
tal:attributes="name toName; maxlength maxChars; tal:attributes="name toName; maxlength maxChars;
value python: widget['defaultForSearch'][1]"/> value python: widget['sdefault'][1]"/>
</tal:to><br/> </tal:to><br/>
</metal:search> </metal:search>

View file

@ -25,12 +25,12 @@
<label tal:attributes="for fromName" tal:content="python: _('search_from')"></label> <label tal:attributes="for fromName" tal:content="python: _('search_from')"></label>
<input type="text" size="4" <input type="text" size="4"
tal:attributes="name fromName; maxlength maxChars; tal:attributes="name fromName; maxlength maxChars;
value python: widget['defaultForSearch'][0]"/> value python: widget['sdefault'][0]"/>
</tal:from> </tal:from>
<tal:to define="toName python: '%s_to' % name"> <tal:to define="toName python: '%s_to' % name">
<label tal:attributes="for toName" tal:content="python: _('search_to')"></label> <label tal:attributes="for toName" tal:content="python: _('search_to')"></label>
<input type="text" size="4" <input type="text" size="4"
tal:attributes="name toName; maxlength maxChars; tal:attributes="name toName; maxlength maxChars;
value python: widget['defaultForSearch'][1]"/> value python: widget['sdefault'][1]"/>
</tal:to><br/> </tal:to><br/>
</metal:search> </metal:search>

View file

@ -94,9 +94,9 @@
<tal:simpleSearch condition="not: widget/isSelect"> <tal:simpleSearch condition="not: widget/isSelect">
<input type="text" tal:define="maxChars python: test(widget['maxChars'], widget['maxChars'], '')" <input type="text" tal:define="maxChars python: test(widget['maxChars'], widget['maxChars'], '')"
tal:attributes="name python: '%s*string-%s' % (widgetName, widget['transform']); tal:attributes="name python: '%s*string-%s' % (widgetName, widget['transform']);
maxlength maxChars; maxlength maxChars; size widget/width;
style python: 'text-transform:%s' % widget['transform']; style python: 'text-transform:%s' % widget['transform'];
value widget/defaultForSearch"/> value widget/sdefault"/>
</tal:simpleSearch> </tal:simpleSearch>
<tal:comment replace="nothing">Show a multi-selection box for fields whose <tal:comment replace="nothing">Show a multi-selection box for fields whose
validator defines a list of values, with a "AND/OR" checkbox.</tal:comment> validator defines a list of values, with a "AND/OR" checkbox.</tal:comment>
@ -112,7 +112,7 @@
<label tal:attributes="for andName" tal:content="python: _('search_and')"></label><br/> <label tal:attributes="for andName" tal:content="python: _('search_and')"></label><br/>
</tal:operator> </tal:operator>
<tal:comment replace="nothing">The list of values</tal:comment> <tal:comment replace="nothing">The list of values</tal:comment>
<select tal:define="preSelected widget/defaultForSearch" <select tal:define="preSelected widget/sdefault"
tal:attributes="name widgetName; size widget/height" multiple="multiple"> tal:attributes="name widgetName; size widget/height" multiple="multiple">
<option tal:repeat="v python:tool.getPossibleValues(name, withTranslations=True, withBlankValue=False, className=className)" <option tal:repeat="v python:tool.getPossibleValues(name, withTranslations=True, withBlankValue=False, className=className)"
tal:attributes="value python:v[0]; title python: v[1]; tal:attributes="value python:v[0]; title python: v[1];