[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:
parent
1505264887
commit
7240561f7f
301
gen/__init__.py
301
gen/__init__.py
|
@ -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.'''
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
Binary file not shown.
|
@ -1,6 +0,0 @@
|
|||
from appy.gen import *
|
||||
|
||||
class Engine:
|
||||
engineType = String()
|
||||
description = String(format=String.XHTML)
|
||||
pod = True
|
|
@ -1,5 +0,0 @@
|
|||
from appy.gen import *
|
||||
|
||||
class Radio:
|
||||
abstract = True
|
||||
name = String()
|
|
@ -1,6 +0,0 @@
|
|||
from appy.gen import *
|
||||
|
||||
class Wheel:
|
||||
title = String(multiplicity=(1,1))
|
||||
description = String(format=String.XHTML)
|
||||
|
|
@ -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')
|
|
@ -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.
Binary file not shown.
|
@ -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).
|
||||
|
Binary file not shown.
|
@ -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']
|
|
@ -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
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -23,10 +23,14 @@
|
|||
<label tal:content="python: _(widget['labelId'])"></label><br>
|
||||
<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>
|
||||
|
|
|
@ -23,10 +23,14 @@
|
|||
<label tal:content="python: _(widget['labelId'])"></label><br>
|
||||
<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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue