[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: class Search:
'''Used for specifying a search for a given type.''' '''Used for specifying a search for a given type.'''
def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None, def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None,
**fields): default=False, **fields):
self.name = name self.name = name
self.group = group # Searches may be visually grouped in the portlet self.group = group # Searches may be visually grouped in the portlet
self.sortBy = sortBy self.sortBy = sortBy
self.sortOrder = sortOrder self.sortOrder = sortOrder
self.limit = limit 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 # In the dict below, keys are indexed field names and values are
# search values. # search values.
self.fields = fields self.fields = fields
@ -368,46 +371,32 @@ class Type:
jsFiles = {} jsFiles = {}
dLayouts = 'lrv-d-f' dLayouts = 'lrv-d-f'
def __init__(self, validator, multiplicity, index, default, optional, def __init__(self, validator, multiplicity, default, show, page, group,
editDefault, show, page, group, layouts, move, indexed, layouts, move, indexed, searchable, specificReadPermission,
searchable, specificReadPermission, specificWritePermission, specificWritePermission, width, height, maxChars, colspan,
width, height, maxChars, colspan, master, masterValue, focus, master, masterValue, focus, historized, sync, mapping, label,
historized, sync, mapping, label): defaultForSearch):
# 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.
self.validator = validator 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. # occurrences of values.
self.multiplicity = multiplicity 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) # Is the field required or not ? (derived from multiplicity)
self.required = self.multiplicity[0] > 0 self.required = self.multiplicity[0] > 0
# May the user configure a default value ? # Default value
self.editDefault = editDefault self.default = default
# Must the field be visible or not? # Must the field be visible or not?
self.show = show self.show = show
# When displaying/editing the whole object, on what page and phase must # When displaying/editing the whole object, on what page and phase must
# this field value appear? # this field value appear?
self.page = Page.get(page) self.page = Page.get(page)
self.pageName = self.page.name self.pageName = self.page.name
# Within self.page, in what group of fields must this field value # Within self.page, in what group of fields must this one appear?
# appear?
self.group = Group.get(group) self.group = Group.get(group)
# The following attribute allows to move a field back to a previous # 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 self.move = move
# If indexed is True, a database index will be set on the field for # If indexed is True, a database index will be set on the field for
# fast access. # fast access.
@ -448,8 +437,7 @@ class Type:
self.slaves = [] self.slaves = []
# The behaviour of this field may depend on another, "master" field # The behaviour of this field may depend on another, "master" field
self.master = master self.master = master
if master: if master: self.master.slaves.append(self)
self.master.slaves.append(self)
# When master has some value(s), there is impact on this field. # When master has some value(s), there is impact on this field.
self.masterValue = initMasterValue(masterValue) self.masterValue = initMasterValue(masterValue)
# If a field must retain attention in a particular way, set focus=True. # 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 # 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
# the search field corresponding to this field, this default value will
# be present.
self.defaultForSearch = defaultForSearch
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
@ -569,13 +561,6 @@ class Type:
def isShowable(self, obj, layoutType): def isShowable(self, obj, layoutType):
'''When displaying p_obj on a given p_layoutType, must we show this '''When displaying p_obj on a given p_layoutType, must we show this
field?''' 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 # Check if the user has the permission to view or edit the field
if layoutType == 'edit': perm = self.writePermission if layoutType == 'edit': perm = self.writePermission
else: perm = self.readPermission else: perm = self.readPermission
@ -759,9 +744,8 @@ class Type:
'''Gets, on_obj, the value conforming to self's type definition.''' '''Gets, on_obj, the value conforming to self's type definition.'''
value = getattr(obj.aq_base, self.name, None) value = getattr(obj.aq_base, self.name, None)
if self.isEmptyValue(value): if self.isEmptyValue(value):
# If there is no value, get the default value if any # If there is no value, get the default value if any: return
if not self.editDefault: # self.default, of self.default() if it is a method.
# Return self.default, of self.default() if it is a method
if callable(self.default): if callable(self.default):
try: try:
return self.callMethod(obj, self.default) return self.callMethod(obj, self.default)
@ -773,10 +757,6 @@ class Type:
return None return None
else: else:
return self.default 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 return value
def getFormattedValue(self, obj, value): def getFormattedValue(self, obj, value):
@ -932,10 +912,6 @@ class Type:
res.group = copy.copy(self.group) res.group = copy.copy(self.group)
res.page = copy.copy(self.page) res.page = copy.copy(self.page)
if not forTool: return res 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 res.show = True
# Set default layouts for all Tool fields # Set default layouts for all Tool fields
res.layouts = res.formatLayouts(None) res.layouts = res.formatLayouts(None)
@ -984,19 +960,18 @@ class Type:
return obj.goto(obj.absolute_url()) return obj.goto(obj.absolute_url())
class Integer(Type): class Integer(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None, def __init__(self, validator=None, multiplicity=(0,1), default=None,
default=None, optional=False, editDefault=False, show=True, show=True, page='main', group=None, layouts=None, move=0,
page='main', group=None, layouts=None, move=0, indexed=False, indexed=False, searchable=False, specificReadPermission=False,
searchable=False, specificReadPermission=False,
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,
Type.__init__(self, validator, multiplicity, index, default, optional, defaultForSearch=('','')):
editDefault, show, page, group, layouts, move, indexed, Type.__init__(self, validator, multiplicity, default, show, page, group,
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) label, defaultForSearch)
self.pythonType = long self.pythonType = long
def validateValue(self, obj, value): def validateValue(self, obj, value):
@ -1015,14 +990,14 @@ class Integer(Type):
class Float(Type): class Float(Type):
allowedDecimalSeps = (',', '.') allowedDecimalSeps = (',', '.')
allowedThousandsSeps = (' ', '') allowedThousandsSeps = (' ', '')
def __init__(self, validator=None, multiplicity=(0,1), index=None, def __init__(self, validator=None, multiplicity=(0,1), default=None,
default=None, optional=False, editDefault=False, show=True, show=True, page='main', group=None, layouts=None, move=0,
page='main', group=None, layouts=None, move=0, indexed=False, indexed=False, searchable=False, specificReadPermission=False,
searchable=False, specificReadPermission=False,
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,
precision=None, sep=(',', '.'), tsep=' '): defaultForSearch=('',''), precision=None, sep=(',', '.'),
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
# rounded. # rounded.
@ -1036,13 +1011,14 @@ class Float(Type):
# Check that the separator(s) are among allowed decimal separators # Check that the separator(s) are among allowed decimal separators
for sep in self.sep: for sep in self.sep:
if sep not in Float.allowedDecimalSeps: 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 self.tsep = tsep
Type.__init__(self, validator, multiplicity, index, default, optional, Type.__init__(self, validator, multiplicity, default, show, page, group,
editDefault, show, page, group, layouts, move, indexed, layouts, move, indexed, False, specificReadPermission,
False, specificReadPermission, specificWritePermission, specificWritePermission, width, height, maxChars, colspan,
width, height, maxChars, colspan, master, masterValue, master, masterValue, focus, historized, True, mapping,
focus, historized, True, mapping, label) label, defaultForSearch)
self.pythonType = float self.pythonType = float
def getFormattedValue(self, obj, value): def getFormattedValue(self, obj, value):
@ -1167,15 +1143,15 @@ class String(Type):
XHTML = 2 XHTML = 2
PASSWORD = 3 PASSWORD = 3
CAPTCHA = 4 CAPTCHA = 4
def __init__(self, validator=None, multiplicity=(0,1), index=None, def __init__(self, validator=None, multiplicity=(0,1), default=None,
default=None, optional=False, editDefault=False, format=LINE, format=LINE, show=True, page='main', group=None, layouts=None,
show=True, page='main', group=None, layouts=None, move=0, move=0, indexed=False, searchable=False,
indexed=False, searchable=False, specificReadPermission=False, specificReadPermission=False, specificWritePermission=False,
specificWritePermission=False, width=None, height=None, width=None, height=None, maxChars=None, colspan=1, master=None,
maxChars=None, colspan=1, master=None, masterValue=None, masterValue=None, focus=False, historized=False, mapping=None,
focus=False, historized=False, mapping=None, label=None, label=None, defaultForSearch='', transform='none',
transform='none', styles=('p','h1','h2','h3','h4'), styles=('p','h1','h2','h3','h4'), allowImageUpload=True,
allowImageUpload=True, richText=False): 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
@ -1197,13 +1173,15 @@ class String(Type):
# CSS property: "none" (default), "uppercase", "capitalize" or # CSS property: "none" (default), "uppercase", "capitalize" or
# "lowercase". # "lowercase".
self.transform = transform self.transform = transform
Type.__init__(self, validator, multiplicity, index, default, optional, Type.__init__(self, validator, multiplicity, default, show, page, group,
editDefault, show, page, group, layouts, move, indexed, layouts, move, indexed, searchable,specificReadPermission,
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) label, defaultForSearch)
self.isSelect = self.isSelection() 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 # 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
@ -1473,20 +1451,19 @@ class String(Type):
return self.getCaptchaChallenge({})['text'] return self.getCaptchaChallenge({})['text']
class Boolean(Type): class Boolean(Type):
'''Field for storing boolean values.'''
def __init__(self, validator=None, multiplicity=(0,1), index=None, def __init__(self, validator=None, multiplicity=(0,1), default=None,
default=None, optional=False, editDefault=False, show=True, show=True, page='main', group=None, layouts = None, move=0,
page='main', group=None, layouts = None, move=0, indexed=False, indexed=False, searchable=False, specificReadPermission=False,
searchable=False, specificReadPermission=False,
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,
Type.__init__(self, validator, multiplicity, index, default, optional, defaultForSearch=False):
editDefault, show, page, group, layouts, move, indexed, Type.__init__(self, validator, multiplicity, default, show, page, group,
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) label, defaultForSearch)
self.pythonType = bool self.pythonType = bool
dLayouts = {'view': 'lf', 'edit': Table('flrv;=d', width=None)} dLayouts = {'view': 'lf', 'edit': Table('flrv;=d', width=None)}
@ -1521,8 +1498,7 @@ class Date(Type):
WITHOUT_HOUR = 1 WITHOUT_HOUR = 1
dateParts = ('year', 'month', 'day') dateParts = ('year', 'month', 'day')
hourParts = ('hour', 'minute') hourParts = ('hour', 'minute')
def __init__(self, validator=None, multiplicity=(0,1), index=None, def __init__(self, validator=None, multiplicity=(0,1), default=None,
default=None, optional=False, editDefault=False,
format=WITH_HOUR, calendar=True, format=WITH_HOUR, calendar=True,
startYear=time.localtime()[0]-10, startYear=time.localtime()[0]-10,
endYear=time.localtime()[0]+10, reverseYears=False, endYear=time.localtime()[0]+10, reverseYears=False,
@ -1530,7 +1506,8 @@ class Date(Type):
indexed=False, searchable=False, specificReadPermission=False, indexed=False, searchable=False, specificReadPermission=False,
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):
self.format = format self.format = format
self.calendar = calendar self.calendar = calendar
self.startYear = startYear self.startYear = startYear
@ -1538,12 +1515,11 @@ class Date(Type):
# If reverseYears is True, in the selection box, available years, from # If reverseYears is True, in the selection box, available years, from
# self.startYear to self.endYear will be listed in reverse order. # self.startYear to self.endYear will be listed in reverse order.
self.reverseYears = reverseYears self.reverseYears = reverseYears
Type.__init__(self, validator, multiplicity, index, default, optional, Type.__init__(self, validator, multiplicity, default, show, page, group,
editDefault, show, page, group, layouts, move, indexed, layouts, move, indexed, searchable,specificReadPermission,
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) label, defaultForSearch)
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.
@ -1598,20 +1574,19 @@ class Date(Type):
return DateTime.DateTime(value) return DateTime.DateTime(value)
class File(Type): class File(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None, def __init__(self, validator=None, multiplicity=(0,1), default=None,
default=None, optional=False, editDefault=False, show=True, show=True, page='main', group=None, layouts=None, move=0,
page='main', group=None, layouts=None, move=0, indexed=False, indexed=False, searchable=False, specificReadPermission=False,
searchable=False, specificReadPermission=False,
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): isImage=False, defaultForSearch=''):
self.isImage = isImage self.isImage = isImage
Type.__init__(self, validator, multiplicity, index, default, optional, Type.__init__(self, validator, multiplicity, default, show, page, group,
editDefault, show, page, group, layouts, move, indexed, layouts, move, indexed, False, specificReadPermission,
False, specificReadPermission, specificWritePermission, specificWritePermission, width, height, None, colspan,
width, height, None, colspan, master, masterValue, focus, master, masterValue, focus, historized, True, mapping,
historized, True, mapping, label) label, defaultForSearch)
@staticmethod @staticmethod
def getFileObject(filePath, fileName=None, zope=False): def getFileObject(filePath, fileName=None, zope=False):
@ -1756,17 +1731,17 @@ class Ref(Type):
wdLayouts = {'view': Table('l-d-f', width='100%')} wdLayouts = {'view': Table('l-d-f', width='100%')}
def __init__(self, klass=None, attribute=None, validator=None, def __init__(self, klass=None, attribute=None, validator=None,
multiplicity=(0,1), index=None, default=None, optional=False, multiplicity=(0,1), default=None, add=False, addConfirm=False,
editDefault=False, add=False, addConfirm=False, delete=None, delete=None, noForm=False, link=True, unlink=None, back=None,
noForm=False, link=True, unlink=None, back=None, show=True, show=True, page='main', group=None, layouts=None,
page='main', group=None, layouts=None, showHeaders=False, showHeaders=False, shownInfo=(), select=None, maxPerPage=30,
shownInfo=(), select=None, maxPerPage=30, move=0, move=0, indexed=False, searchable=False,
indexed=False, searchable=False, specificReadPermission=False, specificReadPermission=False, specificWritePermission=False,
specificWritePermission=False, width=None, height=5, width=None, height=5, maxChars=None, colspan=1, master=None,
maxChars=None, colspan=1, master=None, masterValue=None, masterValue=None, focus=False, historized=False, mapping=None,
focus=False, historized=False, mapping=None, label=None, label=None, queryable=False, queryFields=None, queryNbCols=1,
queryable=False, queryFields=None, queryNbCols=1, navigable=False, searchSelect=None, changeOrder=True,
navigable=False, searchSelect=None, changeOrder=True): defaultForSearch=''):
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 ?
@ -1852,11 +1827,11 @@ class Ref(Type):
# If changeOrder is False, it even if the user has the right to modify # 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. # the field, it will not be possible to move objects or sort them.
self.changeOrder = changeOrder self.changeOrder = changeOrder
Type.__init__(self, validator, multiplicity, index, default, optional, Type.__init__(self, validator, multiplicity, default, show, page, group,
editDefault, show, page, group, layouts, move, indexed, layouts, move, indexed, False, specificReadPermission,
False, specificReadPermission, specificWritePermission, specificWritePermission, width, height, None, colspan,
width, height, None, colspan, master, masterValue, focus, master, masterValue, focus, historized, sync, mapping,
historized, sync, mapping, label) label, defaultForSearch)
self.validable = self.link self.validable = self.link
def getDefaultLayouts(self): def getDefaultLayouts(self):
@ -2125,14 +2100,14 @@ def autoref(klass, field):
setattr(klass, field.back.attribute, field.back) setattr(klass, field.back.attribute, field.back)
class Computed(Type): class Computed(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None, def __init__(self, validator=None, multiplicity=(0,1), default=None,
default=None, optional=False, editDefault=False, show='view', show='view', page='main', group=None, layouts=None, move=0,
page='main', group=None, layouts=None, move=0, indexed=False, indexed=False, searchable=False, specificReadPermission=False,
searchable=False, specificReadPermission=False,
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, context={}): sync=True, mapping=None, label=None, defaultForSearch='',
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
# Does field computation produce plain text or XHTML? # 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 # to the macro specified in self.method. If the dict contains key
# "someKey", it will be available to the macro as "options/someKey". # "someKey", it will be available to the macro as "options/someKey".
self.context = context self.context = context
Type.__init__(self, None, multiplicity, index, default, optional, Type.__init__(self, None, multiplicity, default, show, page, group,
False, show, page, group, layouts, move, indexed, False, layouts, move, indexed, False, specificReadPermission,
specificReadPermission, specificWritePermission, width, specificWritePermission, width, height, None, colspan,
height, None, colspan, master, masterValue, focus, master, masterValue, focus, historized, sync, mapping,
historized, sync, mapping, label) label, defaultForSearch)
self.validable = False self.validable = False
def callMacro(self, obj, macroPath): 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 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 procedure of a gen-application is implemented by an action on the custom
tool class. An action is rendered as a button.''' tool class. An action is rendered as a button.'''
def __init__(self, validator=None, multiplicity=(1,1), index=None, def __init__(self, validator=None, multiplicity=(1,1), default=None,
default=None, optional=False, editDefault=False, show=True, show=True, page='main', group=None, layouts=None, move=0,
page='main', group=None, layouts=None, move=0, indexed=False, indexed=False, searchable=False, specificReadPermission=False,
searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
maxChars=None, colspan=1, action=None, result='computation', maxChars=None, colspan=1, action=None, result='computation',
confirm=False, master=None, masterValue=None, focus=False, 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 # If following field "confirm" is True, a popup will ask the user if
# she is really sure about triggering this action. # she is really sure about triggering this action.
self.confirm = confirm self.confirm = confirm
Type.__init__(self, None, (0,1), index, default, optional, Type.__init__(self, None, (0,1), default, show, page, group, layouts,
False, show, page, group, layouts, move, indexed, False, move, indexed, False, specificReadPermission,
specificReadPermission, specificWritePermission, width, specificWritePermission, width, height, None, colspan,
height, None, colspan, master, masterValue, focus, master, masterValue, focus, historized, False, mapping,
historized, False, mapping, label) label, 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'}
@ -2258,18 +2232,17 @@ class Action(Type):
class Info(Type): class Info(Type):
'''An info is a field whose purpose is to present information '''An info is a field whose purpose is to present information
(text, html...) to the user.''' (text, html...) to the user.'''
def __init__(self, validator=None, multiplicity=(1,1), index=None, def __init__(self, validator=None, multiplicity=(1,1), default=None,
default=None, optional=False, editDefault=False, show='view', show='view', page='main', group=None, layouts=None, move=0,
page='main', group=None, layouts=None, move=0, indexed=False, indexed=False, searchable=False, specificReadPermission=False,
searchable=False, specificReadPermission=False,
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):
Type.__init__(self, None, (0,1), index, default, optional, Type.__init__(self, None, (0,1), default, show, page, group, layouts,
False, show, page, group, layouts, move, indexed, False, move, indexed, False, specificReadPermission,
specificReadPermission, specificWritePermission, width, specificWritePermission, width, height, None, colspan,
height, None, colspan, master, masterValue, focus, master, masterValue, focus, historized, False, mapping,
historized, False, mapping, label) label, None)
self.validable = False self.validable = False
class Pod(Type): class Pod(Type):
@ -2279,8 +2252,7 @@ class Pod(Type):
POD_ERROR = 'An error occurred while generating the document. Please ' \ POD_ERROR = 'An error occurred while generating the document. Please ' \
'contact the system administrator.' 'contact the system administrator.'
DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.' DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
def __init__(self, validator=None, index=None, default=None, def __init__(self, validator=None, default=None, show=('view', 'result'),
optional=False, editDefault=False, show=('view', 'result'),
page='main', group=None, layouts=None, move=0, indexed=False, page='main', group=None, layouts=None, move=0, indexed=False,
searchable=False, specificReadPermission=False, searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
@ -2303,12 +2275,11 @@ class Pod(Type):
self.stylesMapping = stylesMapping self.stylesMapping = stylesMapping
# Freeze format is by PDF by default # Freeze format is by PDF by default
self.freezeFormat = freezeFormat self.freezeFormat = freezeFormat
Type.__init__(self, None, (0,1), index, default, optional, Type.__init__(self, None, (0,1), default, show, page, group, layouts,
False, show, page, group, layouts, move, indexed, move, indexed, searchable, specificReadPermission,
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) label, None)
self.validable = False self.validable = False
def isFrozen(self, obj): def isFrozen(self, obj):
@ -2441,19 +2412,18 @@ class Pod(Type):
class List(Type): class List(Type):
'''A list.''' '''A list.'''
def __init__(self, fields, validator=None, multiplicity=(0,1), index=None, def __init__(self, fields, validator=None, multiplicity=(0,1), default=None,
default=None, optional=False, editDefault=False, show=True, show=True, page='main', group=None, layouts=None, move=0,
page='main', group=None, layouts=None, move=0, indexed=False, indexed=False, searchable=False, specificReadPermission=False,
searchable=False, specificReadPermission=False,
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,
subLayouts=Table('fv', width=None)): subLayouts=Table('fv', width=None)):
Type.__init__(self, validator, multiplicity, index, default, optional, Type.__init__(self, validator, multiplicity, default, show, page, group,
editDefault, show, page, group, layouts, move, indexed, layouts, move, indexed, False, specificReadPermission,
False, specificReadPermission, specificWritePermission, specificWritePermission, width, height, None, colspan,
width, height, None, colspan, master, masterValue, focus, master, masterValue, focus, historized, True, mapping,
historized, True, mapping, label) label, 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.
@ -2941,8 +2911,7 @@ class Selection:
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Model: pass class Model: pass
class Tool(Model): class Tool(Model):
'''If you want so define a custom tool class, she must inherit from this '''If you want to extend or modify the Tool class, subclass me.'''
class.'''
class User(Model): class User(Model):
'''If you want to extend or modify the User class, subclass me.''' '''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, otherCalendars=None, additionalInfo=None, startDate=None,
endDate=None, defaultDate=None, preCompute=None, endDate=None, defaultDate=None, preCompute=None,
applicableEvents=None): applicableEvents=None):
Type.__init__(self, validator, (0,1), None, default, False, False, Type.__init__(self, validator, (0,1), default, show, page, group,
show, page, group, layouts, move, False, False, layouts, move, False, False, specificReadPermission,
specificReadPermission, specificWritePermission, specificWritePermission, width, height, None, colspan,
width, height, None, colspan, master, masterValue, focus, master, masterValue, focus, False, True, mapping, label,
False, True, mapping, label) 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

@ -131,21 +131,14 @@ class ClassDescriptor(Descriptor):
parentWrapper = '%s_Wrapper' % k.name parentWrapper = '%s_Wrapper' % k.name
return (parentWrapper, parentClass) return (parentWrapper, parentClass)
def generateSchema(self, configClass=False): def generateSchema(self):
'''Generates i18n and other related stuff for this class. If this class '''Generates i18n and other related stuff for 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).'''
for attrName in self.orderedAttributes: for attrName in self.orderedAttributes:
try: try:
attrValue = getattr(self.klass, attrName) attrValue = getattr(self.klass, attrName)
except AttributeError: except AttributeError:
attrValue = getattr(self.modelClass, attrName) attrValue = getattr(self.modelClass, attrName)
if isinstance(attrValue, gen.Type): if not isinstance(attrValue, gen.Type): continue
if configClass:
attrValue = copy.copy(attrValue)
attrValue.optional = False
attrValue.editDefault = False
FieldDescriptor(attrName, attrValue, self).generate() FieldDescriptor(attrName, attrValue, self).generate()
def isAbstract(self): def isAbstract(self):
@ -392,13 +385,7 @@ class FieldDescriptor:
'''Walks into the Appy type definition and gathers data about the '''Walks into the Appy type definition and gathers data about the
i18n labels.''' i18n labels.'''
# Manage things common to all Appy types # Manage things common to all Appy types
# - optional ? # Put an index on this field?
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?
if self.appyType.indexed and (self.fieldName != 'title'): if self.appyType.indexed and (self.fieldName != 'title'):
self.classDescr.addIndexMethod(self) self.classDescr.addIndexMethod(self)
# i18n labels # i18n labels
@ -477,28 +464,6 @@ class ToolClassDescriptor(ClassDescriptor):
def isFolder(self, klass=None): return True def isFolder(self, klass=None): return True
def isRoot(self): return False 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): def addPodRelatedFields(self, fieldDescr):
'''Adds the fields needed in the Tool for configuring a Pod field.''' '''Adds the fields needed in the Tool for configuring a Pod field.'''
@ -607,8 +572,6 @@ class UserClassDescriptor(ClassDescriptor):
self.klass = klass self.klass = klass
self.customized = True self.customized = True
def isFolder(self, klass=None): return False def isFolder(self, klass=None): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
class GroupClassDescriptor(ClassDescriptor): class GroupClassDescriptor(ClassDescriptor):
'''Represents the class that corresponds to the Group for the generated '''Represents the class that corresponds to the Group for the generated
@ -634,8 +597,6 @@ class GroupClassDescriptor(ClassDescriptor):
self.klass = klass self.klass = klass
self.customized = True self.customized = True
def isFolder(self, klass=None): return False def isFolder(self, klass=None): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
class TranslationClassDescriptor(ClassDescriptor): class TranslationClassDescriptor(ClassDescriptor):
'''Represents the set of translation ids for a gen-application.''' '''Represents the set of translation ids for a gen-application.'''
@ -648,8 +609,6 @@ class TranslationClassDescriptor(ClassDescriptor):
def getParents(self, allClasses=()): return ('Translation',) def getParents(self, allClasses=()): return ('Translation',)
def isFolder(self, klass=None): return False def isFolder(self, klass=None): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
def addLabelField(self, messageId, page): def addLabelField(self, messageId, page):
'''Adds a Computed field that will display, in the source language, the '''Adds a Computed field that will display, in the source language, the
@ -714,6 +673,4 @@ class PageClassDescriptor(ClassDescriptor):
self.klass = klass self.klass = klass
self.customized = True self.customized = True
def isFolder(self, klass=None): return 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: if classDescr.name in self.referers:
for field in self.referers[classDescr.name]: for field in self.referers[classDescr.name]:
names.append(field.appyType.back.attribute) names.append(field.appyType.back.attribute)
# Add the 'state' attribute
names.append('state')
qNames = ['"%s"' % name for name in names] qNames = ['"%s"' % name for name in names]
attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames))) attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames)))
repls['attributes'] = ',\n '.join(attributes) repls['attributes'] = ',\n '.join(attributes)

View file

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

View file

@ -357,6 +357,11 @@ class ZopeInstaller:
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') title.init('title', None, 'appy')
setattr(wrapperClass, 'title', title) 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]] names = self.config.attributes[wrapperClass.__name__[:-8]]
wrapperClass.__fields__ = [getattr(wrapperClass, n) for n in names] wrapperClass.__fields__ = [getattr(wrapperClass, n) for n in names]
# Post-initialise every Appy type # Post-initialise every Appy type

View file

@ -1,8 +1,6 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, os.path, sys, re, time, random, types, base64, urllib import os, os.path, sys, re, time, random, types, base64, urllib
from appy.shared import mimeTypes from appy import Object
from appy.shared.utils import getOsTempFolder, sequenceTypes
from appy.shared.data import languages
import appy.gen import appy.gen
from appy.gen import Type, Search, Selection, String from appy.gen import Type, Search, Selection, String
from appy.gen.utils import SomeObjects, getClassName 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.wrappers import AbstractWrapper
from appy.gen.descriptors import ClassDescriptor from appy.gen.descriptors import ClassDescriptor
from appy.gen.mail import sendMail 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: try:
from AccessControl.ZopeSecurityPolicy import _noroles from AccessControl.ZopeSecurityPolicy import _noroles
except ImportError: except ImportError:
@ -681,11 +682,15 @@ class ToolMixin(BaseMixin):
return obj, fieldName return obj, fieldName
def getSearches(self, contentType): def getSearches(self, contentType):
'''Returns the list of searches that are defined for p_contentType. '''Returns an object with 2 attributes:
Every list item is a dict that contains info about a search or about * "searches" stores the searches that are defined for p_contentType;
a group of searches.''' * "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) appyClass = self.getAppyClass(contentType)
res = [] searches = []
default = None # Also retrieve the default one here.
visitedGroups = {} # Names of already visited search groups visitedGroups = {} # Names of already visited search groups
for search in ClassDescriptor.getSearches(appyClass): for search in ClassDescriptor.getSearches(appyClass):
# Determine first group label, we will need it. # Determine first group label, we will need it.
@ -699,7 +704,7 @@ class ToolMixin(BaseMixin):
'label': self.translate(groupLabel), 'label': self.translate(groupLabel),
'descr': self.translate('%s_descr' % groupLabel), 'descr': self.translate('%s_descr' % groupLabel),
} }
res.append(group) searches.append(group)
visitedGroups[search.group] = group visitedGroups[search.group] = group
# Add the search itself # Add the search itself
searchLabel = '%s_search_%s' % (contentType, search.name) searchLabel = '%s_search_%s' % (contentType, search.name)
@ -709,8 +714,10 @@ class ToolMixin(BaseMixin):
if search.group: if search.group:
visitedGroups[search.group]['searches'].append(dSearch) visitedGroups[search.group]['searches'].append(dSearch)
else: else:
res.append(dSearch) searches.append(dSearch)
return res if search.default:
default = dSearch
return Object(searches=searches, default=default).__dict__
def getQueryUrl(self, contentType, searchName, startNumber=None): def getQueryUrl(self, contentType, searchName, startNumber=None):
'''This method creates the URL that allows to perform a (non-Ajax) '''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()) return stateShow(workflow, self.appy())
else: return stateShow 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): def _appy_managePermissions(self):
'''When an object is created or updated, we must update "add" '''When an object is created or updated, we must update "add"
permissions accordingly: if the object is a folder, we must set on 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 --------------------------------------------------------------- # The Tool class ---------------------------------------------------------------
# Prefixes of the fields generated on the Tool. # Prefixes of the fields generated on the Tool.
toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns', toolFieldPrefixes = ('podTemplate', 'formats', 'resultColumns',
'enableAdvancedSearch', 'numberOfSearchColumns', 'enableAdvancedSearch', 'numberOfSearchColumns',
'searchFields', 'optionalFields', 'showWorkflow', 'searchFields', 'showWorkflow', 'showAllStatesInPhase')
'showAllStatesInPhase')
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom', defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
'appyVersion', 'dateFormat', 'hourFormat', 'users', 'appyVersion', 'dateFormat', 'hourFormat', 'users',
'connectedUsers', 'groups', 'translations', 'connectedUsers', 'groups', 'translations',

View file

@ -35,10 +35,11 @@ class Ogone(Type):
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
colspan=1, master=None, masterValue=None, focus=False, colspan=1, master=None, masterValue=None, focus=False,
mapping=None, label=None): mapping=None, label=None):
Type.__init__(self, None, (0,1), None, None, False, False, show, page, Type.__init__(self, None, (0,1), None, show, page, group, layouts, move,
group, layouts, move, 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)
# 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

@ -29,14 +29,12 @@ class PoMessage:
# The following messages (starting with MSG_) correspond to tool # The following messages (starting with MSG_) correspond to tool
# attributes added for every gen-class (warning: the message IDs correspond # attributes added for every gen-class (warning: the message IDs correspond
# to MSG_<attributePrefix>). # to MSG_<attributePrefix>).
MSG_defaultValue = "Default value for field '%s'"
MSG_podTemplate = "POD template for field '%s'" MSG_podTemplate = "POD template for field '%s'"
MSG_formats = "Output format(s) for field '%s'" MSG_formats = "Output format(s) for field '%s'"
MSG_resultColumns = "Columns to display while showing query results" MSG_resultColumns = "Columns to display while showing query results"
MSG_enableAdvancedSearch = "Enable advanced search" MSG_enableAdvancedSearch = "Enable advanced search"
MSG_numberOfSearchColumns = "Number of search columns" MSG_numberOfSearchColumns = "Number of search columns"
MSG_searchFields = "Search fields" MSG_searchFields = "Search fields"
MSG_optionalFields = 'Optional fields'
MSG_showWorkflow = 'Show workflow-related information' MSG_showWorkflow = 'Show workflow-related information'
MSG_showAllStatesInPhase = 'Show all states in phase' MSG_showAllStatesInPhase = 'Show all states in phase'
POD_ASKACTION = 'Trigger related action' 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" <div class="portletSep" tal:define="nb repeat/rootClass/number"
tal:condition="python: (nb == 1 and contextObj) or (nb != 1)"></div> tal:condition="python: (nb == 1 and contextObj) or (nb != 1)"></div>
<div class="portletContent"> <div class="portletContent" tal:define="searchInfo python: tool.getSearches(rootClass)">
<tal:comment replace="nothing">Section title, with action icons</tal:comment> <tal:comment replace="nothing">Section title (link triggers the default search), with action icons</tal:comment>
<a tal:attributes="href python: '%s?className=%s' % (queryUrl, rootClass); <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', '')" class python:test(not currentSearch and (currentClass==rootClass), 'portletCurrent', '')"
tal:content="structure python: _(rootClass + '_plural')"> tal:content="structure python: _(rootClass + '_plural')">
</a> </a>
@ -56,7 +57,7 @@
</a> </a>
</span> </span>
<tal:comment replace="nothing">Searches for this content type.</tal:comment> <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:group condition="searchOrGroup/isGroup">
<tal:expanded define="group searchOrGroup; <tal:expanded define="group searchOrGroup;
expanded python: request.get(group['labelId'], 'collapsed') == 'expanded'"> expanded python: request.get(group['labelId'], 'collapsed') == 'expanded'">
@ -83,13 +84,13 @@
</tal:group> </tal:group>
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup" <dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
class="portletAppyItem portletSearch"> class="portletAppyItem portletSearch">
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']); <a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
title search/descr; title search/descr;
class python: test(search['name'] == currentSearch, 'portletCurrent', '');" class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
tal:content="structure search/label"></a> tal:content="structure search/label"></a>
</dt> </dt>
</tal:searchOrGroup> </tal:searchOrGroup>
</div>
</tal:section> </tal:section>
</metal:portlet> </metal:portlet>

View file

@ -23,10 +23,14 @@
<label tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp; <label tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:from define="fromName python: '%s*float' % widgetName"> <tal:from define="fromName python: '%s*float' % widgetName">
<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" 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: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" 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/> </tal:to><br/>
</metal:search> </metal:search>

View file

@ -23,10 +23,14 @@
<label tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp; <label tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:from define="fromName python: '%s*int' % widgetName"> <tal:from define="fromName python: '%s*int' % widgetName">
<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" 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: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" 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/> </tal:to><br/>
</metal:search> </metal:search>

View file

@ -89,7 +89,8 @@
<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;
style python: 'text-transform:%s' % widget['transform']"/> style python: 'text-transform:%s' % widget['transform'];
value widget/defaultForSearch"/>
</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>
@ -105,9 +106,11 @@
<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: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)" <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)"> tal:content="python: tool.truncateValue(v[1], widget)">
</option> </option>
</select> </select>

View file

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