[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.

This commit is contained in:
Gaetan Delannay 2012-10-31 13:20:25 +01:00
parent 1505264887
commit 7240561f7f
29 changed files with 255 additions and 575 deletions

View file

@ -300,12 +300,15 @@ class Import:
class Search:
'''Used for specifying a search for a given type.'''
def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None,
**fields):
default=False, **fields):
self.name = name
self.group = group # Searches may be visually grouped in the portlet
self.sortBy = sortBy
self.sortOrder = sortOrder
self.limit = limit
# If this search is the default one, it will be triggered by clicking
# on main link.
self.default = default
# In the dict below, keys are indexed field names and values are
# search values.
self.fields = fields
@ -368,46 +371,32 @@ class Type:
jsFiles = {}
dLayouts = 'lrv-d-f'
def __init__(self, validator, multiplicity, index, default, optional,
editDefault, show, page, group, layouts, move, indexed,
searchable, specificReadPermission, specificWritePermission,
width, height, maxChars, colspan, master, masterValue, focus,
historized, sync, mapping, label):
def __init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, sync, mapping, label,
defaultForSearch):
# The validator restricts which values may be defined. It can be an
# interval (1,None), a list of string values ['choice1', 'choice2'],
# a regular expression, a custom function, a Selection instance, etc.
self.validator = validator
# Multiplicity is a tuple indicating the minimum and maximum
# Multiplicity is a 2-tuple indicating the minimum and maximum
# occurrences of values.
self.multiplicity = multiplicity
# Type of the index on the values. If you want to represent a simple
# (ordered) list of values, specify None. If you want to
# index your values with unordered integers or with other types like
# strings (thus creating a dictionary of values instead of a list),
# specify a type specification for the index, like Integer() or
# String(). Note that this concept of "index" has nothing to do with
# the concept of "database index" (see fields "indexed" and
# "searchable" below). self.index is not yet used.
self.index = index
# Default value
self.default = default
# Is the field optional or not ?
self.optional = optional
# Is the field required or not ? (derived from multiplicity)
self.required = self.multiplicity[0] > 0
# May the user configure a default value ?
self.editDefault = editDefault
# Default value
self.default = default
# Must the field be visible or not?
self.show = show
# When displaying/editing the whole object, on what page and phase must
# this field value appear?
self.page = Page.get(page)
self.pageName = self.page.name
# Within self.page, in what group of fields must this field value
# appear?
# Within self.page, in what group of fields must this one appear?
self.group = Group.get(group)
# The following attribute allows to move a field back to a previous
# position (useful for content types that inherit from others).
# position (useful for moving fields above predefined ones).
self.move = move
# If indexed is True, a database index will be set on the field for
# fast access.
@ -448,8 +437,7 @@ class Type:
self.slaves = []
# The behaviour of this field may depend on another, "master" field
self.master = master
if master:
self.master.slaves.append(self)
if master: self.master.slaves.append(self)
# When master has some value(s), there is impact on this field.
self.masterValue = initMasterValue(masterValue)
# If a field must retain attention in a particular way, set focus=True.
@ -481,6 +469,10 @@ class Type:
# prefix and another name. If you want to specify a new name only, and
# not a prefix, write (None, newName).
self.label = label
# When you specify a default value "for search", on a search screen, in
# the search field corresponding to this field, this default value will
# be present.
self.defaultForSearch = defaultForSearch
def init(self, name, klass, appName):
'''When the application server starts, this secondary constructor is
@ -569,13 +561,6 @@ class Type:
def isShowable(self, obj, layoutType):
'''When displaying p_obj on a given p_layoutType, must we show this
field?'''
# Do not show field if it is optional and not selected in tool
if self.optional:
tool = obj.getTool().appy()
fieldName = 'optionalFieldsFor%s' % obj.meta_type
fieldValue = getattr(tool, fieldName, ())
if self.name not in fieldValue:
return False
# Check if the user has the permission to view or edit the field
if layoutType == 'edit': perm = self.writePermission
else: perm = self.readPermission
@ -759,9 +744,8 @@ class Type:
'''Gets, on_obj, the value conforming to self's type definition.'''
value = getattr(obj.aq_base, self.name, None)
if self.isEmptyValue(value):
# If there is no value, get the default value if any
if not self.editDefault:
# Return self.default, of self.default() if it is a method
# If there is no value, get the default value if any: return
# self.default, of self.default() if it is a method.
if callable(self.default):
try:
return self.callMethod(obj, self.default)
@ -773,10 +757,6 @@ class Type:
return None
else:
return self.default
# If value is editable, get the default value from the tool
portalTypeName = obj._appy_getPortalType(obj.REQUEST)
tool = obj.getTool().appy()
return getattr(tool, 'defaultValueFor%s' % self.labelId)
return value
def getFormattedValue(self, obj, value):
@ -932,10 +912,6 @@ class Type:
res.group = copy.copy(self.group)
res.page = copy.copy(self.page)
if not forTool: return res
# A field added to the tool can't have parameters that would lead to the
# creation of new fields in the tool.
res.editDefault = False
res.optional = False
res.show = True
# Set default layouts for all Tool fields
res.layouts = res.formatLayouts(None)
@ -984,19 +960,18 @@ class Type:
return obj.goto(obj.absolute_url())
class Integer(Type):
def __init__(self, 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,
def __init__(self, validator=None, multiplicity=(0,1), default=None,
show=True, page='main', group=None, layouts=None, move=0,
indexed=False, searchable=False, specificReadPermission=False,
specificWritePermission=False, width=6, height=None,
maxChars=13, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None):
Type.__init__(self, validator, multiplicity, index, default, optional,
editDefault, show, page, group, layouts, move, indexed,
searchable, specificReadPermission,
focus=False, historized=False, mapping=None, label=None,
defaultForSearch=('','')):
Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, searchable,specificReadPermission,
specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, True, mapping,
label)
label, defaultForSearch)
self.pythonType = long
def validateValue(self, obj, value):
@ -1015,14 +990,14 @@ class Integer(Type):
class Float(Type):
allowedDecimalSeps = (',', '.')
allowedThousandsSeps = (' ', '')
def __init__(self, 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,
def __init__(self, validator=None, multiplicity=(0,1), default=None,
show=True, page='main', group=None, layouts=None, move=0,
indexed=False, searchable=False, specificReadPermission=False,
specificWritePermission=False, width=6, height=None,
maxChars=13, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None,
precision=None, sep=(',', '.'), tsep=' '):
defaultForSearch=('',''), precision=None, sep=(',', '.'),
tsep=' '):
# The precision is the number of decimal digits. This number is used
# for rendering the float, but the internal float representation is not
# rounded.
@ -1036,13 +1011,14 @@ class Float(Type):
# Check that the separator(s) are among allowed decimal separators
for sep in self.sep:
if sep not in Float.allowedDecimalSeps:
raise 'Char "%s" is not allowed as decimal separator.' % sep
raise Exception('Char "%s" is not allowed as decimal ' \
'separator.' % sep)
self.tsep = tsep
Type.__init__(self, validator, multiplicity, index, default, optional,
editDefault, show, page, group, layouts, move, indexed,
False, specificReadPermission, specificWritePermission,
width, height, maxChars, colspan, master, masterValue,
focus, historized, True, mapping, label)
Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, True, mapping,
label, defaultForSearch)
self.pythonType = float
def getFormattedValue(self, obj, value):
@ -1167,15 +1143,15 @@ class String(Type):
XHTML = 2
PASSWORD = 3
CAPTCHA = 4
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, format=LINE,
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,
transform='none', styles=('p','h1','h2','h3','h4'),
allowImageUpload=True, richText=False):
def __init__(self, validator=None, multiplicity=(0,1), default=None,
format=LINE, 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, defaultForSearch='', transform='none',
styles=('p','h1','h2','h3','h4'), allowImageUpload=True,
richText=False):
# According to format, the widget will be different: input field,
# textarea, inline editor... Note that there can be only one String
# field of format CAPTCHA by page, because the captcha challenge is
@ -1197,13 +1173,15 @@ class String(Type):
# CSS property: "none" (default), "uppercase", "capitalize" or
# "lowercase".
self.transform = transform
Type.__init__(self, validator, multiplicity, index, default, optional,
editDefault, show, page, group, layouts, move, indexed,
searchable, specificReadPermission,
Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, searchable,specificReadPermission,
specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, True, mapping,
label)
label, defaultForSearch)
self.isSelect = self.isSelection()
# If self.isSelect, self.defaultForSearch must be a list of value(s).
if self.isSelect and not defaultForSearch:
self.defaultForSearch = []
# Default width, height and maxChars vary according to String format
if width == None:
if format == String.TEXT: self.width = 60
@ -1473,20 +1451,19 @@ class String(Type):
return self.getCaptchaChallenge({})['text']
class Boolean(Type):
def __init__(self, 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,
'''Field for storing boolean values.'''
def __init__(self, validator=None, multiplicity=(0,1), default=None,
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):
Type.__init__(self, validator, multiplicity, index, default, optional,
editDefault, show, page, group, layouts, move, indexed,
searchable, specificReadPermission,
focus=False, historized=False, mapping=None, label=None,
defaultForSearch=False):
Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, searchable,specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, True, mapping,
label)
label, defaultForSearch)
self.pythonType = bool
dLayouts = {'view': 'lf', 'edit': Table('flrv;=d', width=None)}
@ -1521,8 +1498,7 @@ class Date(Type):
WITHOUT_HOUR = 1
dateParts = ('year', 'month', 'day')
hourParts = ('hour', 'minute')
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False,
def __init__(self, validator=None, multiplicity=(0,1), default=None,
format=WITH_HOUR, calendar=True,
startYear=time.localtime()[0]-10,
endYear=time.localtime()[0]+10, reverseYears=False,
@ -1530,7 +1506,8 @@ class Date(Type):
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):
focus=False, historized=False, mapping=None, label=None,
defaultForSearch=None):
self.format = format
self.calendar = calendar
self.startYear = startYear
@ -1538,12 +1515,11 @@ class Date(Type):
# If reverseYears is True, in the selection box, available years, from
# self.startYear to self.endYear will be listed in reverse order.
self.reverseYears = reverseYears
Type.__init__(self, validator, multiplicity, index, default, optional,
editDefault, show, page, group, layouts, move, indexed,
searchable, specificReadPermission,
Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, searchable,specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, True, mapping,
label)
label, defaultForSearch)
def getCss(self, layoutType, res):
# CSS files are only required if the calendar must be shown.
@ -1598,20 +1574,19 @@ class Date(Type):
return DateTime.DateTime(value)
class File(Type):
def __init__(self, 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,
def __init__(self, validator=None, multiplicity=(0,1), default=None,
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,
isImage=False):
isImage=False, defaultForSearch=''):
self.isImage = isImage
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)
Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, True, mapping,
label, defaultForSearch)
@staticmethod
def getFileObject(filePath, fileName=None, zope=False):
@ -1756,17 +1731,17 @@ class Ref(Type):
wdLayouts = {'view': Table('l-d-f', width='100%')}
def __init__(self, klass=None, attribute=None, validator=None,
multiplicity=(0,1), index=None, default=None, optional=False,
editDefault=False, add=False, addConfirm=False, delete=None,
noForm=False, link=True, unlink=None, back=None, show=True,
page='main', group=None, layouts=None, showHeaders=False,
shownInfo=(), select=None, maxPerPage=30, move=0,
indexed=False, searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=5,
maxChars=None, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None,
queryable=False, queryFields=None, queryNbCols=1,
navigable=False, searchSelect=None, changeOrder=True):
multiplicity=(0,1), default=None, add=False, addConfirm=False,
delete=None, noForm=False, link=True, unlink=None, back=None,
show=True, page='main', group=None, layouts=None,
showHeaders=False, shownInfo=(), select=None, maxPerPage=30,
move=0, indexed=False, searchable=False,
specificReadPermission=False, specificWritePermission=False,
width=None, height=5, maxChars=None, colspan=1, master=None,
masterValue=None, focus=False, historized=False, mapping=None,
label=None, queryable=False, queryFields=None, queryNbCols=1,
navigable=False, searchSelect=None, changeOrder=True,
defaultForSearch=''):
self.klass = klass
self.attribute = attribute
# May the user add new objects through this ref ?
@ -1852,11 +1827,11 @@ class Ref(Type):
# If changeOrder is False, it even if the user has the right to modify
# the field, it will not be possible to move objects or sort them.
self.changeOrder = changeOrder
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, sync, mapping, label)
Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, sync, mapping,
label, defaultForSearch)
self.validable = self.link
def getDefaultLayouts(self):
@ -2125,14 +2100,14 @@ def autoref(klass, field):
setattr(klass, field.back.attribute, field.back)
class Computed(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, show='view',
page='main', group=None, layouts=None, move=0, indexed=False,
searchable=False, specificReadPermission=False,
def __init__(self, validator=None, multiplicity=(0,1), default=None,
show='view', page='main', group=None, layouts=None, move=0,
indexed=False, searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None,
maxChars=None, colspan=1, method=None, plainText=True,
master=None, masterValue=None, focus=False, historized=False,
sync=True, mapping=None, label=None, context={}):
sync=True, mapping=None, label=None, defaultForSearch='',
context={}):
# The Python method used for computing the field value
self.method = method
# Does field computation produce plain text or XHTML?
@ -2145,11 +2120,11 @@ class Computed(Type):
# to the macro specified in self.method. If the dict contains key
# "someKey", it will be available to the macro as "options/someKey".
self.context = context
Type.__init__(self, None, multiplicity, index, default, optional,
False, show, page, group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus,
historized, sync, mapping, label)
Type.__init__(self, None, multiplicity, default, show, page, group,
layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, sync, mapping,
label, defaultForSearch)
self.validable = False
def callMacro(self, obj, macroPath):
@ -2192,10 +2167,9 @@ class Action(Type):
by the user on a given gen-class. For example, the custom installation
procedure of a gen-application is implemented by an action on the custom
tool class. An action is rendered as a button.'''
def __init__(self, validator=None, multiplicity=(1,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,
def __init__(self, validator=None, multiplicity=(1,1), default=None,
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, action=None, result='computation',
confirm=False, master=None, masterValue=None, focus=False,
@ -2216,11 +2190,11 @@ class Action(Type):
# If following field "confirm" is True, a popup will ask the user if
# she is really sure about triggering this action.
self.confirm = confirm
Type.__init__(self, None, (0,1), index, default, optional,
False, show, page, group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus,
historized, False, mapping, label)
Type.__init__(self, None, (0,1), default, show, page, group, layouts,
move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, False, mapping,
label, None)
self.validable = False
def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'}
@ -2258,18 +2232,17 @@ class Action(Type):
class Info(Type):
'''An info is a field whose purpose is to present information
(text, html...) to the user.'''
def __init__(self, validator=None, multiplicity=(1,1), index=None,
default=None, optional=False, editDefault=False, show='view',
page='main', group=None, layouts=None, move=0, indexed=False,
searchable=False, specificReadPermission=False,
def __init__(self, validator=None, multiplicity=(1,1), default=None,
show='view', 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):
Type.__init__(self, None, (0,1), index, default, optional,
False, show, page, group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus,
historized, False, mapping, label)
Type.__init__(self, None, (0,1), default, show, page, group, layouts,
move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, False, mapping,
label, None)
self.validable = False
class Pod(Type):
@ -2279,8 +2252,7 @@ class Pod(Type):
POD_ERROR = 'An error occurred while generating the document. Please ' \
'contact the system administrator.'
DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
def __init__(self, validator=None, index=None, default=None,
optional=False, editDefault=False, show=('view', 'result'),
def __init__(self, validator=None, default=None, show=('view', 'result'),
page='main', group=None, layouts=None, move=0, indexed=False,
searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None,
@ -2303,12 +2275,11 @@ class Pod(Type):
self.stylesMapping = stylesMapping
# Freeze format is by PDF by default
self.freezeFormat = freezeFormat
Type.__init__(self, None, (0,1), index, default, optional,
False, show, page, group, layouts, move, indexed,
searchable, specificReadPermission,
Type.__init__(self, None, (0,1), default, show, page, group, layouts,
move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, False, mapping,
label)
label, None)
self.validable = False
def isFrozen(self, obj):
@ -2441,19 +2412,18 @@ class Pod(Type):
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,
def __init__(self, fields, validator=None, multiplicity=(0,1), default=None,
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)
Type.__init__(self, validator, multiplicity, default, show, page, group,
layouts, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, True, mapping,
label, None)
self.validable = True
# Tuples of (names, Type instances) determining the format of every
# element in the list.
@ -2941,8 +2911,7 @@ class Selection:
# ------------------------------------------------------------------------------
class Model: pass
class Tool(Model):
'''If you want so define a custom tool class, she must inherit from this
class.'''
'''If you want to extend or modify the Tool class, subclass me.'''
class User(Model):
'''If you want to extend or modify the User class, subclass me.'''

View file

@ -21,11 +21,11 @@ class Calendar(Type):
otherCalendars=None, additionalInfo=None, startDate=None,
endDate=None, defaultDate=None, preCompute=None,
applicableEvents=None):
Type.__init__(self, validator, (0,1), None, default, False, False,
show, page, group, layouts, move, False, False,
specificReadPermission, specificWritePermission,
width, height, None, colspan, master, masterValue, focus,
False, True, mapping, label)
Type.__init__(self, validator, (0,1), default, show, page, group,
layouts, move, False, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, True, mapping, label,
None)
# 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
# be a method that computes such a "dynamic" list or tuple. When

View file

@ -131,21 +131,14 @@ class ClassDescriptor(Descriptor):
parentWrapper = '%s_Wrapper' % k.name
return (parentWrapper, parentClass)
def generateSchema(self, configClass=False):
'''Generates i18n and other related stuff for this class. If this class
is in the configuration (tool, user, etc) we must avoid having
attributes that rely on the configuration (ie attributes that are
optional, with editDefault=True, etc).'''
def generateSchema(self):
'''Generates i18n and other related stuff for this class.'''
for attrName in self.orderedAttributes:
try:
attrValue = getattr(self.klass, attrName)
except AttributeError:
attrValue = getattr(self.modelClass, attrName)
if isinstance(attrValue, gen.Type):
if configClass:
attrValue = copy.copy(attrValue)
attrValue.optional = False
attrValue.editDefault = False
if not isinstance(attrValue, gen.Type): continue
FieldDescriptor(attrName, attrValue, self).generate()
def isAbstract(self):
@ -392,13 +385,7 @@ class FieldDescriptor:
'''Walks into the Appy type definition and gathers data about the
i18n labels.'''
# Manage things common to all Appy types
# - optional ?
if self.appyType.optional:
self.generator.tool.addOptionalField(self)
# - edit default value ?
if self.appyType.editDefault:
self.generator.tool.addDefaultField(self)
# - put an index on this field?
# Put an index on this field?
if self.appyType.indexed and (self.fieldName != 'title'):
self.classDescr.addIndexMethod(self)
# i18n labels
@ -477,28 +464,6 @@ class ToolClassDescriptor(ClassDescriptor):
def isFolder(self, klass=None): return True
def isRoot(self): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
def addOptionalField(self, fieldDescr):
className = fieldDescr.classDescr.name
fieldName = 'optionalFieldsFor%s' % className
fieldType = getattr(self.modelClass, fieldName, None)
if not fieldType:
fieldType = String(multiplicity=(0,None))
fieldType.validator = []
self.addField(fieldName, fieldType)
fieldType.validator.append(fieldDescr.fieldName)
fieldType.page.name = 'data'
fieldType.group = gen.Group(fieldDescr.classDescr.klass.__name__)
def addDefaultField(self, fieldDescr):
className = fieldDescr.classDescr.name
fieldName = 'defaultValueFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = fieldDescr.appyType.clone()
self.addField(fieldName, fieldType)
fieldType.page.name = 'data'
fieldType.group = gen.Group(fieldDescr.classDescr.klass.__name__)
def addPodRelatedFields(self, fieldDescr):
'''Adds the fields needed in the Tool for configuring a Pod field.'''
@ -607,8 +572,6 @@ class UserClassDescriptor(ClassDescriptor):
self.klass = klass
self.customized = True
def isFolder(self, klass=None): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
class GroupClassDescriptor(ClassDescriptor):
'''Represents the class that corresponds to the Group for the generated
@ -634,8 +597,6 @@ class GroupClassDescriptor(ClassDescriptor):
self.klass = klass
self.customized = True
def isFolder(self, klass=None): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
class TranslationClassDescriptor(ClassDescriptor):
'''Represents the set of translation ids for a gen-application.'''
@ -648,8 +609,6 @@ class TranslationClassDescriptor(ClassDescriptor):
def getParents(self, allClasses=()): return ('Translation',)
def isFolder(self, klass=None): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
def addLabelField(self, messageId, page):
'''Adds a Computed field that will display, in the source language, the
@ -714,6 +673,4 @@ class PageClassDescriptor(ClassDescriptor):
self.klass = klass
self.customized = True
def isFolder(self, klass=None): return True
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
# ------------------------------------------------------------------------------

View file

@ -762,6 +762,8 @@ class ZopeGenerator(Generator):
if classDescr.name in self.referers:
for field in self.referers[classDescr.name]:
names.append(field.appyType.back.attribute)
# Add the 'state' attribute
names.append('state')
qNames = ['"%s"' % name for name in names]
attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames)))
repls['attributes'] = ',\n '.join(attributes)

View file

@ -7,7 +7,7 @@ from appy.shared.utils import normalizeText
# Default Appy indexes ---------------------------------------------------------
defaultIndexes = {
'State': 'FieldIndex', 'UID': 'FieldIndex', 'Title': 'TextIndex',
'State': 'ListIndex', 'UID': 'FieldIndex', 'Title': 'TextIndex',
'SortableTitle': 'FieldIndex', 'SearchableText': 'TextIndex',
'Creator': 'FieldIndex', 'Created': 'DateIndex', 'ClassName': 'FieldIndex',
'Allowed': 'KeywordIndex'}

View file

@ -354,9 +354,14 @@ class ZopeInstaller:
wrapperClass = klass.wrapperClass
if not hasattr(wrapperClass, 'title'):
# Special field "type" is mandatory for every class.
title = gen.String(multiplicity=(1,1), show='edit',indexed=True)
title = gen.String(multiplicity=(1,1),show='edit',indexed=True)
title.init('title', None, 'appy')
setattr(wrapperClass, 'title', title)
# Special field "state" must be added for every class
state = gen.String(validator=gen.Selection('_appy_listStates'),
show='result')
state.init('state', None, 'workflow')
setattr(wrapperClass, 'state', state)
names = self.config.attributes[wrapperClass.__name__[:-8]]
wrapperClass.__fields__ = [getattr(wrapperClass, n) for n in names]
# Post-initialise every Appy type

View file

@ -1,8 +1,6 @@
# ------------------------------------------------------------------------------
import os, os.path, sys, re, time, random, types, base64, urllib
from appy.shared import mimeTypes
from appy.shared.utils import getOsTempFolder, sequenceTypes
from appy.shared.data import languages
from appy import Object
import appy.gen
from appy.gen import Type, Search, Selection, String
from appy.gen.utils import SomeObjects, getClassName
@ -10,6 +8,9 @@ from appy.gen.mixins import BaseMixin
from appy.gen.wrappers import AbstractWrapper
from appy.gen.descriptors import ClassDescriptor
from appy.gen.mail import sendMail
from appy.shared import mimeTypes
from appy.shared.utils import getOsTempFolder, sequenceTypes
from appy.shared.data import languages
try:
from AccessControl.ZopeSecurityPolicy import _noroles
except ImportError:
@ -681,11 +682,15 @@ class ToolMixin(BaseMixin):
return obj, fieldName
def getSearches(self, contentType):
'''Returns the list of searches that are defined for p_contentType.
Every list item is a dict that contains info about a search or about
a group of searches.'''
'''Returns an object with 2 attributes:
* "searches" stores the searches that are defined for p_contentType;
* "default" stores the search defined as default one.
Every item representing a search is a dict containing info about a
search or about a group of searches.
'''
appyClass = self.getAppyClass(contentType)
res = []
searches = []
default = None # Also retrieve the default one here.
visitedGroups = {} # Names of already visited search groups
for search in ClassDescriptor.getSearches(appyClass):
# Determine first group label, we will need it.
@ -699,7 +704,7 @@ class ToolMixin(BaseMixin):
'label': self.translate(groupLabel),
'descr': self.translate('%s_descr' % groupLabel),
}
res.append(group)
searches.append(group)
visitedGroups[search.group] = group
# Add the search itself
searchLabel = '%s_search_%s' % (contentType, search.name)
@ -709,8 +714,10 @@ class ToolMixin(BaseMixin):
if search.group:
visitedGroups[search.group]['searches'].append(dSearch)
else:
res.append(dSearch)
return res
searches.append(dSearch)
if search.default:
default = dSearch
return Object(searches=searches, default=default).__dict__
def getQueryUrl(self, contentType, searchName, startNumber=None):
'''This method creates the URL that allows to perform a (non-Ajax)

View file

@ -1285,6 +1285,15 @@ class BaseMixin:
return stateShow(workflow, self.appy())
else: return stateShow
def _appy_listStates(self):
'''Lists the possible states for this object.'''
res = []
workflow = self.getWorkflow()
for elem in dir(workflow):
if getattr(workflow, elem).__class__.__name__ != 'State': continue
res.append((elem, self.translate(self.getWorkflowLabel(elem))))
return res
def _appy_managePermissions(self):
'''When an object is created or updated, we must update "add"
permissions accordingly: if the object is a folder, we must set on

View file

@ -209,10 +209,9 @@ setattr(Page, Page.pages.back.attribute, Page.pages.back)
# The Tool class ---------------------------------------------------------------
# Prefixes of the fields generated on the Tool.
toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
toolFieldPrefixes = ('podTemplate', 'formats', 'resultColumns',
'enableAdvancedSearch', 'numberOfSearchColumns',
'searchFields', 'optionalFields', 'showWorkflow',
'showAllStatesInPhase')
'searchFields', 'showWorkflow', 'showAllStatesInPhase')
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
'appyVersion', 'dateFormat', 'hourFormat', 'users',
'connectedUsers', 'groups', 'translations',

View file

@ -35,10 +35,11 @@ class Ogone(Type):
specificWritePermission=False, width=None, height=None,
colspan=1, master=None, masterValue=None, focus=False,
mapping=None, label=None):
Type.__init__(self, None, (0,1), None, None, False, False, show, page,
group, layouts, move, False, False,specificReadPermission,
Type.__init__(self, None, (0,1), None, show, page, group, layouts, move,
False, False,specificReadPermission,
specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, True, mapping, label)
master, masterValue, focus, False, True, mapping, label,
None)
# orderMethod must contain a method returning a dict containing info
# about the order. Following keys are mandatory:
# * orderID An identifier for the order. Don't use the object UID

View file

@ -29,14 +29,12 @@ class PoMessage:
# The following messages (starting with MSG_) correspond to tool
# attributes added for every gen-class (warning: the message IDs correspond
# to MSG_<attributePrefix>).
MSG_defaultValue = "Default value for field '%s'"
MSG_podTemplate = "POD template for field '%s'"
MSG_formats = "Output format(s) for field '%s'"
MSG_resultColumns = "Columns to display while showing query results"
MSG_enableAdvancedSearch = "Enable advanced search"
MSG_numberOfSearchColumns = "Number of search columns"
MSG_searchFields = "Search fields"
MSG_optionalFields = 'Optional fields'
MSG_showWorkflow = 'Show workflow-related information'
MSG_showAllStatesInPhase = 'Show all states in phase'
POD_ASKACTION = 'Trigger related action'

View file

@ -1,6 +0,0 @@
from appy.gen import *
class Engine:
engineType = String()
description = String(format=String.XHTML)
pod = True

View file

@ -1,5 +0,0 @@
from appy.gen import *
class Radio:
abstract = True
name = String()

View file

@ -1,6 +0,0 @@
from appy.gen import *
class Wheel:
title = String(multiplicity=(1,1))
description = String(format=String.XHTML)

View file

@ -1,34 +0,0 @@
from appy.gen import *
from AppyCar.Interior.Radio import Radio
class RallyCarWorkflow:
# Roles
carDriver = 'CarDriver'
driverM = ('Manager', carDriver)
# Specific permissions
readColor = ReadPermission('Car.color')
writeColor = WritePermission('Car.color')
# States
created = State({r:driverM, w:driverM, d:driverM,
readColor: driverM, writeColor: driverM}, initial=True)
running = State({r:driverM, w:driverM, d:driverM,
readColor: 'Manager', writeColor: 'Manager'})
# Transitions
run = Transition( (created, running), condition=driverM)
stop = Transition( (running, created), condition=driverM)
class Car:
sport = Boolean()
color = String(specificReadPermission=True, specificWritePermission=True)
description = String(format=String.TEXT)
class RallyCar(Car):
root = True
workflow = RallyCarWorkflow
test = Integer()
class StandardRadio(Radio):
test1 = Integer()
c = Config()
c.languages = ('en', 'fr')

View file

@ -1,10 +0,0 @@
from appy.gen import *
class Meeting:
place = String(editDefault=True)
date = Date()
myObservations = String(format=String.XHTML, optional=True)
annex = File(optional=True)
leader = String(validator=['andyStein', 'joelLambillotte'])
root = True
pod = ['Meeting']

Binary file not shown.

View file

@ -1,10 +0,0 @@
This folder contains some example Appy applications used for testing purposes.
Directly in the folder, you have the application "AppyMeeting" which consists in
a single Python file (module AppyMeeting.py) and a POD template for producing
documents (Meeting.odt). You also have simple applications Zzz.py and
ZopeComponent.py that are one-file applications.
In sub-folder AppyCar, you have a more complex application that is structured
as a Python package (a hierarchy of folders).

View file

@ -1,130 +0,0 @@
from appy.gen import *
class BunchOfGeek:
description = String(format=String.TEXT)
class ZopeComponentTool(Tool):
someUsefulConfigurationOption = String()
def onInstall(self):
self.someUsefulConfigurationOption = 'My app is configured now!'
install = Action(action=onInstall)
class ZopeComponentWorkflow:
# Specific permissions
wf = WritePermission('ZopeComponent.funeralDate')
rb = ReadPermission('ZopeComponent.responsibleBunch')
# Roles
zManager = 'ZManager'
zLeader = 'ZLeader'
managerM = (zManager, 'Manager')
leaderM = (zLeader, 'Manager')
everybody = (zManager, zLeader, 'Manager')
# States
created = State({r:leaderM, w:('Owner', 'Manager'), d:leaderM, wf:'Owner',
rb:everybody}, initial=True)
validated = State({r:everybody, w:everybody, d:None, wf:everybody,
rb:everybody})
underDevelopment = State({r:everybody, w:leaderM, d:None, wf:leaderM,
rb:everybody})
whereIsTheClient = State({r:everybody, w:managerM, d:None, wf:managerM,
rb:everybody})
# Transitions
def funeralOk(self, obj): return obj.funeralDate
validate = Transition( (created, validated),
condition=managerM + (funeralOk,))
def updateDescription(self, obj):
obj.description = 'Description edited by my manager was silly.'
startDevelopment = Transition( (validated, underDevelopment),
condition=leaderM, action=updateDescription)
cancelDevelopment = Transition( (underDevelopment, whereIsTheClient),
condition=managerM)
cancel = Transition( ( (whereIsTheClient, underDevelopment),
(underDevelopment, validated),
(validated, created)), condition='Manager')
class ZopeComponent:
root = True
workflow = ZopeComponentWorkflow
def showDate(self):
return True
def validateDescription(self, value):
res = True
if value.find('simple') != -1:
res = self.translate('zope_3_is_not_simple')
return res
description = String(editDefault=True)
technicalDescription = String(format=String.XHTML,
validator=validateDescription)
#status = String(validator=['underDevelopement', 'stillSomeWorkToPerform',
# 'weAreAlmostFinished', 'alphaReleaseIsBugged', 'whereIsTheClient'],
# optional=True, editDefault=True)
funeralDate = Date(optional=True, specificWritePermission=True)
responsibleBunch = Ref(BunchOfGeek, multiplicity=(1,1), add=False,
link=True, back=Ref(attribute='components'),
specificReadPermission=True)
class CobolComponentWorkflow(ZopeComponentWorkflow):
p = ZopeComponentWorkflow # Shortcut to workflow parent
# An additional state
finished = State(p.whereIsTheClient.permissions)
# Override validate: condition on funeralDate has no sense here
validate = Transition(p.validate.states, condition=p.managerM)
# Override cancelDevelopment: go to finished instead
cancelDevelopment = Transition( (p.underDevelopment, finished),
condition=p.managerM)
# Update cancel accordingly
cancel = Transition( ((finished, p.underDevelopment),) +p.cancel.states[1:],
condition=p.cancel.condition)
class CobolComponent:
root = True
workflow = CobolComponentWorkflow
description = String()
class Person:
abstract = True
pod = True
title = String(show=False)
n = 'name_3'
firstName = String(group=n, width=15)
middleInitial = String(group=n, width=3)
name = String(multiplicity=(1,1), group=n, width=30)
contractDetails = String(format=String.XHTML)
cia = {'page': 'contactInformation', 'group': 'address_2'}
street = String(**cia)
number = Integer(**cia)
country = String(**cia)
zipCode = Integer(**cia)
cio = {'page': 'contactInformation', 'group': 'numbers_2', 'width': 20}
phoneNumber = String(**cio)
faxNumber = String(**cio)
mobilePhone = String(**cio)
workPhoneNumber = String(**cio)
workFaxNumber = String(**cio)
workMobilePhone = String(**cio)
def onEdit(self, created):
self.title = self.firstName + ' ' + self.name
class Worker(Person):
root = True
productivity = Integer()
class Parasite(Person):
root = True
pod = ['SordidGossips', 'MoreGossips']
hairColor = String(group='hairyCharacteristics')
sordidGossips = String(format = String.XHTML, page='Gossips')
parasiteIndex = String(validator=['low', 'medium', 'high',
'unquantifiable'],
page='contactInformation', group='numbers')
details = String(page='contactInformation', group='numbers',
master=parasiteIndex, masterValue='unquantifiable')
avoidAnyPhysicalContact = Boolean(page='contactInformation')
def validate(self, new, errors):
if (new.hairColor == 'flashy') and (new.firstName == 'Gerard'):
errors.hairColor = True
errors.firstName = "Flashy Gerards are disgusting."
c = Config()
c.languages = ('en', 'fr')
c.defaultCreators += ['ZLeader']

View file

@ -1,67 +0,0 @@
from appy.gen import *
class Zzz:
root = True
def show_f2(self): return True
def validate_i2(self, value):
if (value != None) and (value < 10):
return 'Value must be higher or equal to 10.'
return True
title=String(multiplicity=(0,1), show=False)
i1 = Integer(show=False)
i2 = Integer(validator = validate_i2)
f1 = Float(show=show_f2, page='other')
f2 = Float(multiplicity=(1,1))
class SeveralStrings:
root=True
anEmail = String(validator=String.EMAIL)
anUrl = String(validator=String.URL)
anAlphanumericValue = String(validator=String.ALPHANUMERIC)
aSingleSelectedValue = String(validator=['valueA', 'valueB', 'valueC'])
aSingleMandatorySelectedValue = String(
validator=['valueX', 'valueY', 'valueZ'], multiplicity=(1,1))
aMultipleSelectedValue = String(
validator=['valueS', 'valueT', 'valueU', 'valueV'],
multiplicity=(1,None), searchable=True)
aBooleanValue = Boolean(default=True)
dateWithHour = Date()
dateWithoutHour = Date(format=Date.WITHOUT_HOUR)
anAttachedFile = File()
anAttachedImage = File(isImage=True)
class Product:
root = True
description = String(format=String.TEXT)
stock = Integer()
def needOrder(self): return self.stock < 3
def orderProduct(self): self.stock = 3
order = Action(action=orderProduct, show=needOrder)
class Order:
description = String(format=String.TEXT)
number = Float(show=False)
# Reference field
def getReference(self): return 'OR-%f' % self.number
reference = Computed(method=getReference)
def filterProducts(self, allProducts):
return [f for f in allProducts if f.description.find('Descr') != -1]
products = Ref(Product, add=False, link=True, multiplicity=(1,None),
back=Ref(attribute='orders'), showHeaders=True,
shownInfo=('description','title', 'order'),
select=filterProducts)
def onEdit(self, created):
if created:
import random
self.number = random.random()
class Client:
root = True
folder = True
title = String(show=False)
firstName = String()
name = String()
orders = Ref(Order, add=True, link=False, multiplicity=(0,None),
back=Ref(attribute='client'), showHeaders=True,
shownInfo=('reference', 'description', 'products'), wide=True)
def onEdit(self, created):
self.title = self.firstName + ' ' + self.name

View file

@ -26,9 +26,10 @@
<div class="portletSep" tal:define="nb repeat/rootClass/number"
tal:condition="python: (nb == 1 and contextObj) or (nb != 1)"></div>
<div class="portletContent">
<tal:comment replace="nothing">Section title, with action icons</tal:comment>
<a tal:attributes="href python: '%s?className=%s' % (queryUrl, rootClass);
<div class="portletContent" tal:define="searchInfo python: tool.getSearches(rootClass)">
<tal:comment replace="nothing">Section title (link triggers the default search), with action icons</tal:comment>
<a tal:define="queryParam python: searchInfo['default'] and ('&search=%s' % searchInfo['default']['name']) or ''"
tal:attributes="href python: '%s?className=%s%s' % (queryUrl, rootClass, queryParam);
class python:test(not currentSearch and (currentClass==rootClass), 'portletCurrent', '')"
tal:content="structure python: _(rootClass + '_plural')">
</a>
@ -56,7 +57,7 @@
</a>
</span>
<tal:comment replace="nothing">Searches for this content type.</tal:comment>
<tal:searchOrGroup repeat="searchOrGroup python: tool.getSearches(rootClass)">
<tal:searchOrGroup repeat="searchOrGroup searchInfo/searches">
<tal:group condition="searchOrGroup/isGroup">
<tal:expanded define="group searchOrGroup;
expanded python: request.get(group['labelId'], 'collapsed') == 'expanded'">
@ -83,13 +84,13 @@
</tal:group>
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
class="portletAppyItem portletSearch">
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
title search/descr;
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
tal:content="structure search/label"></a>
</dt>
</tal:searchOrGroup>
</div>
</tal:section>
</metal:portlet>

View file

@ -23,10 +23,14 @@
<label tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:from define="fromName python: '%s*float' % widgetName">
<label tal:attributes="for fromName" tal:content="python: _('search_from')"></label>
<input type="text" tal:attributes="name fromName; maxlength maxChars" size="4"/>
<input type="text" size="4"
tal:attributes="name fromName; maxlength maxChars;
value python: widget['defaultForSearch'][0]"/>
</tal:from>
<tal:to define="toName python: '%s_to' % name">
<label tal:attributes="for toName" tal:content="python: _('search_to')"></label>
<input type="text" tal:attributes="name toName; maxlength maxChars" size="4"/>
<input type="text" size="4"
tal:attributes="name toName; maxlength maxChars;
value python: widget['defaultForSearch'][1]"/>
</tal:to><br/>
</metal:search>

View file

@ -23,10 +23,14 @@
<label tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:from define="fromName python: '%s*int' % widgetName">
<label tal:attributes="for fromName" tal:content="python: _('search_from')"></label>
<input type="text" tal:attributes="name fromName; maxlength maxChars" size="4"/>
<input type="text" size="4"
tal:attributes="name fromName; maxlength maxChars;
value python: widget['defaultForSearch'][0]"/>
</tal:from>
<tal:to define="toName python: '%s_to' % name">
<label tal:attributes="for toName" tal:content="python: _('search_to')"></label>
<input type="text" tal:attributes="name toName; maxlength maxChars" size="4"/>
<input type="text" size="4"
tal:attributes="name toName; maxlength maxChars;
value python: widget['defaultForSearch'][1]"/>
</tal:to><br/>
</metal:search>

View file

@ -89,7 +89,8 @@
<input type="text" tal:define="maxChars python: test(widget['maxChars'], widget['maxChars'], '')"
tal:attributes="name python: '%s*string-%s' % (widgetName, widget['transform']);
maxlength maxChars;
style python: 'text-transform:%s' % widget['transform']"/>
style python: 'text-transform:%s' % widget['transform'];
value widget/defaultForSearch"/>
</tal:simpleSearch>
<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>
@ -105,9 +106,11 @@
<label tal:attributes="for andName" tal:content="python: _('search_and')"></label><br/>
</tal:operator>
<tal:comment replace="nothing">The list of values</tal:comment>
<select tal:attributes="name widgetName; size widget/height" multiple="multiple">
<select tal:define="preSelected widget/defaultForSearch"
tal:attributes="name widgetName; size widget/height" multiple="multiple">
<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];
selected python: v[0] in preSelected"
tal:content="python: tool.truncateValue(v[1], widget)">
</option>
</select>

View file

@ -83,16 +83,9 @@ class ToolWrapper(AbstractWrapper):
'''Some names of Tool attributes are not easy to guess. For example,
the attribute that stores the names of the columns to display in
query results for class A that is in package x.y is
"tool.resultColumnsForx_y_A". Other example: the attribute that
stores the editable default value of field "f1" of class x.y.A is
"tool.defaultValueForx_y_A_f1". This method generates the attribute
"tool.resultColumnsForx_y_A". This method generates the attribute
name based on p_attributeType, a p_klass from the application, and a
p_attrName (given only if needed, for example if p_attributeType is
"defaultValue"). p_attributeType may be:
"defaultValue"
Stores the editable default value for a given p_attrName of a
given p_klass.
p_attrName (given only if needed). p_attributeType may be:
"podTemplate"
Stores the pod template for p_attrName.
@ -117,10 +110,6 @@ class ToolWrapper(AbstractWrapper):
Determines, among all indexed fields for p_klass, which one will
really be used in the search screen.
"optionalFields"
Stores the list of optional attributes that are in use in the
tool for the given p_klass.
"showWorkflow"
Stores the boolean field indicating if we must show workflow-
related information for p_klass or not.