Several bugfixes for 0.6 series (bugfix while defining pod fields in a custom tool, bugfix in the creation flag, import of objects was broken...) and minor improvements in the layouting system (automatic generation of 'cell' layouts was optimized).

This commit is contained in:
Gaetan Delannay 2010-11-10 15:15:00 +01:00
parent 3d87036f85
commit 7dc55f23c2
11 changed files with 230 additions and 207 deletions

View file

@ -675,7 +675,7 @@ class Type:
'''p_value is a real p_obj(ect) value from a field from this type. This
method returns a pretty, string-formatted version, for displaying
purposes. Needs to be overridden by some child classes.'''
if value in nullValues: return ''
if self.isEmptyValue(value): return ''
return value
def getRequestValue(self, request):
@ -689,7 +689,7 @@ class Type:
representation of the field value coming from the request.
This method computes the real (potentially converted or manipulated
in some other way) value as can be stored in the database.'''
if value in nullValues: return None
if self.isEmptyValue(value): return None
return value
def getMasterData(self):
@ -698,6 +698,10 @@ class Type:
if self.master: return (self.master, self.masterValue)
if self.group: return self.group.getMasterData()
def isEmptyValue(self, value, obj=None):
'''Returns True if the p_value must be considered as an empty value.'''
return value in nullValues
def validateValue(self, obj, value):
'''This method may be overridden by child classes and will be called at
the right moment by m_validate defined below for triggering
@ -711,7 +715,7 @@ class Type:
definition. If it is the case, None is returned. Else, a translated
error message is returned.'''
# Check that a value is given if required.
if value in nullValues:
if self.isEmptyValue(value, obj):
if self.required and self.isClientVisible(obj):
# If the field is required, but not visible according to
# master/slave relationships, we consider it not to be required.
@ -759,6 +763,28 @@ class Type:
p_self type definition on p_obj.'''
setattr(obj, self.name, value)
def clone(self, forTool=True):
'''Returns a clone of myself. If p_forTool is True, the clone will be
adapted to its life into the tool.'''
res = copy.copy(self)
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)
res.specificReadPermission = False
res.specificWritePermission = False
res.multiplicity = (0, self.multiplicity[1])
if type(res.validator) == types.FunctionType:
# We will not be able to call this function from the tool.
res.validator = None
return res
class Integer(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, show=True,
@ -781,7 +807,11 @@ class Integer(Type):
return obj.translate('bad_%s' % self.pythonType.__name__)
def getStorableValue(self, value):
if value not in nullValues: return self.pythonType(value)
if not self.isEmptyValue(value): return self.pythonType(value)
def getFormattedValue(self, obj, value):
if self.isEmptyValue(value): return ''
return str(value)
class Float(Type):
allowedDecimalSeps = (',', '.')
@ -814,7 +844,7 @@ class Float(Type):
self.pythonType = float
def getFormattedValue(self, obj, value):
if value in nullValues: return ''
if self.isEmptyValue(value): return ''
# Determine the field separator
sep = self.sep[0]
# Produce the rounded string representation
@ -847,7 +877,7 @@ class Float(Type):
return obj.translate('bad_%s' % self.pythonType.__name__)
def getStorableValue(self, value):
if value not in nullValues:
if not self.isEmptyValue(value):
for sep in self.sep: value = value.replace(sep, '.')
return self.pythonType(value)
@ -1021,7 +1051,7 @@ class String(Type):
return value
def getFormattedValue(self, obj, value):
if value in nullValues: return ''
if self.isEmptyValue(value): return ''
res = value
if self.isSelect:
if isinstance(self.validator, Selection):
@ -1148,7 +1178,7 @@ class Boolean(Type):
return res
def getStorableValue(self, value):
if value not in nullValues:
if not self.isEmptyValue(value):
exec 'res = %s' % value
return res
@ -1187,7 +1217,7 @@ class Date(Type):
'jscalendar/calendar-en.js')
def getFormattedValue(self, obj, value):
if value in nullValues: return ''
if self.isEmptyValue(value): return ''
res = value.strftime('%d/%m/') + str(value.year())
if self.format == Date.WITH_HOUR:
res += ' %s' % value.strftime('%H:%M')
@ -1212,7 +1242,7 @@ class Date(Type):
return value
def getStorableValue(self, value):
if value not in nullValues:
if not self.isEmptyValue(value):
import DateTime
return DateTime.DateTime(value)
@ -1246,13 +1276,21 @@ class File(Type):
def getDefaultLayouts(self): return {'view':'lf','edit':'lrv-f'}
def isEmptyValue(self, value, obj=None):
'''Must p_value be considered as empty?'''
if not obj: return Type.isEmptyValue(self, value)
if value is not None: return False
# If "nochange", the value must not be considered as empty
return obj.REQUEST.get('%s_delete' % self.name) != 'nochange'
imageExts = ('.jpg', '.jpeg', '.png', '.gif')
def validateValue(self, obj, value):
form = obj.REQUEST.form
action = '%s_delete' % self.name
if not value.filename and form.has_key(action) and not form[action]:
if (not value or not value.filename) and form.has_key(action) and \
not form[action]:
# If this key is present but empty, it means that the user selected
# "replace the file with a new one". So in this cas he must provide
# "replace the file with a new one". So in this case he must provide
# a new file to upload.
return obj.translate('file_required')
# Check that, if self.isImage, the uploaded file is really an image
@ -1345,7 +1383,7 @@ class Ref(Type):
historized, sync)
self.validable = self.link
def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'}
def getDefaultLayouts(self): return {'view': Table('l-f'), 'edit': 'lrv-f'}
def isShowable(self, obj, layoutType):
res = Type.isShowable(self, obj, layoutType)
@ -1470,6 +1508,18 @@ class Ref(Type):
refs = [obj.uid_catalog(UID=uid)[0].getObject() for uid in uids]
exec 'obj.set%s%s(refs)' % (self.name[0].upper(), self.name[1:])
def clone(self, forTool=True):
'''Produces a clone of myself.'''
res = Type.clone(self, forTool)
res.back = copy.copy(self.back)
if not forTool: return res
res.link = True
res.add = False
res.back.attribute += 'DefaultValue'
res.back.show = False
res.select = None # Not callable from tool.
return res
class Computed(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, show='view',
@ -1491,18 +1541,17 @@ class Computed(Type):
def getValue(self, obj):
'''Computes the value instead of getting it in the database.'''
if not self.method: return ''
if not self.method: return
obj = obj.appy()
try:
res = self.method(obj)
if not isinstance(res, basestring):
res = repr(res)
return self.method(obj)
except Exception, e:
obj.log(Traceback.get(), type='error')
res = str(e)
return res
return str(e)
def getFormattedValue(self, obj, value): return value
def getFormattedValue(self, obj, value):
if not isinstance(value, basestring): return str(value)
return value
class Action(Type):
'''An action is a workflow-independent Python method that can be triggered

View file

@ -40,7 +40,10 @@
# ------------------------------------------------------------------------------
rowDelimiters = {'-':'middle', '=':'top', '_':'bottom'}
rowDelms = ''.join(rowDelimiters.keys())
cellDelimiters = {'|': 'center', ';': 'left', '!': 'right'}
cellDelms = ''.join(cellDelimiters.keys())
macroDict = {
# Page-related elements
's': ('page', 'header'), 'w': ('page', 'widgets'),
@ -158,6 +161,10 @@ class Table(LayoutElement):
res = layout
for letter in Table.derivedRepls[derivedType]:
res = res.replace(letter, '')
# Strip the derived layout
res = res.lstrip(rowDelms); res = res.lstrip(cellDelms)
if derivedType == 'cell':
res = res.rstrip(rowDelms); res = res.rstrip(cellDelms)
return res
def addCssClasses(self, css_class):

View file

@ -6,13 +6,12 @@
# ------------------------------------------------------------------------------
import types, copy
from model import ModelClass, Tool, toolFieldPrefixes
from model import ModelClass, toolFieldPrefixes
from utils import stringify
import appy.gen
import appy.gen.descriptors
from appy.gen.po import PoMessage
from appy.gen import Date, String, State, Transition, Type, Search, \
Selection, Import, Role
from appy.gen import *
from appy.gen.utils import produceNiceMessage, getClassName
TABS = 4 # Number of blanks in a Python indentation.
@ -131,7 +130,7 @@ class FieldDescriptor:
self.generator.labels.append(msg)
self.classDescr.labelsToPropagate.append(msg)
# Add the POD-related fields on the Tool
Tool._appy_addPodRelatedFields(self)
self.generator.tool.addPodRelatedFields(self)
notToValidateFields = ('Info', 'Computed', 'Action', 'Pod')
def walkAppyType(self):
@ -140,10 +139,10 @@ class FieldDescriptor:
# Manage things common to all Appy types
# - optional ?
if self.appyType.optional:
Tool._appy_addOptionalField(self)
self.generator.tool.addOptionalField(self)
# - edit default value ?
if self.appyType.editDefault:
Tool._appy_addDefaultField(self)
self.generator.tool.addDefaultField(self)
# - put an index on this field?
if self.appyType.indexed and \
(self.fieldName not in ('title', 'description')):
@ -381,11 +380,13 @@ class ToolClassDescriptor(ClassDescriptor):
self.modelClass = self.klass
self.predefined = True
self.customized = False
def getParents(self, allClasses=()):
res = ['Tool']
if self.customized:
res.append('%s.%s' % (self.klass.__module__, self.klass.__name__))
return res
def update(self, klass, attributes):
'''This method is called by the generator when he finds a custom tool
definition. We must then add the custom tool elements in this default
@ -393,11 +394,133 @@ class ToolClassDescriptor(ClassDescriptor):
self.orderedAttributes += attributes
self.klass = klass
self.customized = True
def isFolder(self, klass=None): return True
def isRoot(self): return False
def generateSchema(self):
ClassDescriptor.generateSchema(self, configClass=True)
def addField(self, fieldName, fieldType, classDescr):
'''Adds a new field to the Tool.'''
exec "self.modelClass.%s = fieldType" % fieldName
self.modelClass._appy_attributes.append(fieldName)
self.orderedAttributes.append(fieldName)
self.modelClass._appy_classes[fieldName] = classDescr.name
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, fieldDescr.classDescr)
fieldType.validator.append(fieldDescr.fieldName)
fieldType.page.name = 'data'
fieldType.group = 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, fieldDescr.classDescr)
fieldType.page.name = 'data'
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
def addPodRelatedFields(self, fieldDescr):
'''Adds the fields needed in the Tool for configuring a Pod field.'''
className = fieldDescr.classDescr.name
# On what page and group to display those fields ?
pg = {'page': 'documentGeneration',
'group': Group(fieldDescr.classDescr.klass.__name__, ['50%']*2)}
# Add the field that will store the pod template.
fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = File(**pg)
self.addField(fieldName, fieldType, fieldDescr.classDescr)
# Add the field that will store the output format(s)
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = String(validator=('odt', 'pdf', 'doc', 'rtf'),
multiplicity=(1,None), default=('odt',), **pg)
self.addField(fieldName, fieldType, fieldDescr.classDescr)
def addQueryResultColumns(self, classDescr):
'''Adds, for class p_classDescr, the attribute in the tool that allows
to select what default columns will be shown on query results.'''
className = classDescr.name
fieldName = 'resultColumnsFor%s' % className
fieldType = String(multiplicity=(0,None), validator=Selection(
'_appy_getAllFields*%s' % className), page='userInterface',
group=classDescr.klass.__name__)
self.addField(fieldName, fieldType, classDescr)
def addSearchRelatedFields(self, classDescr):
'''Adds, for class p_classDescr, attributes related to the search
functionality for class p_classDescr.'''
className = classDescr.name
# Field that defines if advanced search is enabled for class
# p_classDescr or not.
fieldName = 'enableAdvancedSearchFor%s' % className
fieldType = Boolean(default=True, page='userInterface',
group=classDescr.klass.__name__)
self.addField(fieldName, fieldType, classDescr)
# Field that defines how many columns are shown on the custom search
# screen.
fieldName = 'numberOfSearchColumnsFor%s' % className
fieldType = Integer(default=3, page='userInterface',
group=classDescr.klass.__name__)
self.addField(fieldName, fieldType, classDescr)
# Field that allows to select, among all indexed fields, what fields
# must really be used in the search screen.
fieldName = 'searchFieldsFor%s' % className
defaultValue = [a[0] for a in classDescr.getOrderedAppyAttributes(
condition='attrValue.indexed')]
fieldType = String(multiplicity=(0,None), validator=Selection(
'_appy_getSearchableFields*%s' % className), default=defaultValue,
page='userInterface', group=classDescr.klass.__name__)
self.addField(fieldName, fieldType, classDescr)
def addImportRelatedFields(self, classDescr):
'''Adds, for class p_classDescr, attributes related to the import
functionality for class p_classDescr.'''
className = classDescr.name
# Field that defines the path of the files to import.
fieldName = 'importPathFor%s' % className
defValue = classDescr.getCreateMean('Import').path
fieldType = String(page='data', multiplicity=(1,1), default=defValue,
group=classDescr.klass.__name__)
self.addField(fieldName, fieldType, classDescr)
def addWorkflowFields(self, classDescr):
'''Adds, for a given p_classDescr, the workflow-related fields.'''
className = classDescr.name
groupName = classDescr.klass.__name__
# Adds a field allowing to show/hide completely any workflow-related
# information for a given class.
defaultValue = False
if classDescr.isRoot() or issubclass(classDescr.klass, ModelClass):
defaultValue = True
fieldName = 'showWorkflowFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface',
group=groupName)
self.addField(fieldName, fieldType, classDescr)
# Adds the boolean field for showing or not the field "enter comments".
fieldName = 'showWorkflowCommentFieldFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface',
group=groupName)
self.addField(fieldName, fieldType, classDescr)
# Adds the boolean field for showing all states in current state or not.
# If this boolean is True but the current phase counts only one state,
# we will not show the state at all: the fact of knowing in what phase
# we are is sufficient. If this boolean is False, we simply show the
# current state.
defaultValue = False
if len(classDescr.getPhases()) > 1:
defaultValue = True
fieldName = 'showAllStatesInPhaseFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface',
group=groupName)
self.addField(fieldName, fieldType, classDescr)
class UserClassDescriptor(ClassDescriptor):
'''Represents an Archetypes-compliant class that corresponds to the User
for the generated application.'''

View file

@ -8,8 +8,9 @@ from appy.gen import *
from appy.gen.po import PoMessage, PoFile, PoParser
from appy.gen.generator import Generator as AbstractGenerator
from appy.gen.utils import getClassName
from descriptors import ClassDescriptor, WorkflowDescriptor, \
ToolClassDescriptor, UserClassDescriptor
from model import ModelClass, User, Tool
from descriptors import *
# Common methods that need to be defined on every Archetype class --------------
COMMON_METHODS = '''
@ -352,8 +353,8 @@ class Generator(AbstractGenerator):
wfInit += 'wf._states.append("%s")\n' % stateName
wfInit += 'workflowInstances[%s] = wf\n' % className
repls['workflowInstancesInit'] = wfInit
# Compute the list of ordered attributes (foward and backward, inherited
# included) for every Appy class.
# Compute the list of ordered attributes (forward and backward,
# inherited included) for every Appy class.
attributes = []
attributesDict = []
for classDescr in self.getClasses(include='all'):
@ -622,21 +623,16 @@ class Generator(AbstractGenerator):
for childDescr in classDescr.getChildren():
childFieldName = fieldName % childDescr.name
fieldType.group = childDescr.klass.__name__
Tool._appy_addField(childFieldName, fieldType, childDescr)
self.tool.addField(childFieldName, fieldType, childDescr)
if classDescr.isRoot():
# We must be able to configure query results from the tool.
Tool._appy_addQueryResultColumns(classDescr)
self.tool.addQueryResultColumns(classDescr)
# Add the search-related fields.
Tool._appy_addSearchRelatedFields(classDescr)
self.tool.addSearchRelatedFields(classDescr)
importMean = classDescr.getCreateMean('Import')
if importMean:
Tool._appy_addImportRelatedFields(classDescr)
Tool._appy_addWorkflowFields(self.user)
# Complete self.tool.orderedAttributes from the attributes that we
# just added to the Tool model class.
for fieldName in Tool._appy_attributes:
if fieldName not in self.tool.orderedAttributes:
self.tool.orderedAttributes.append(fieldName)
self.tool.addImportRelatedFields(classDescr)
self.tool.addWorkflowFields(self.user)
self.tool.generateSchema()
# Generate the Tool class
@ -663,7 +659,7 @@ class Generator(AbstractGenerator):
k = classDescr.klass
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
if not classDescr.isAbstract():
Tool._appy_addWorkflowFields(classDescr)
self.tool.addWorkflowFields(classDescr)
# Determine base archetypes schema and class
baseClass = 'BaseContent'
baseSchema = 'BaseSchema'

View file

@ -180,7 +180,8 @@ class ToolMixin(BaseMixin):
sortMethod = importParams['sort']
if sortMethod: sortMethod = sortMethod.__get__('')
elems = []
importPath = getattr(self, 'importPathFor%s' % contentType)
importType = self.getAppyType('importPathFor%s' % contentType)
importPath = importType.getValue(self)
for elem in os.listdir(importPath):
elemFullPath = os.path.join(importPath, elem)
elemInfo = onElement(elemFullPath)

View file

@ -919,9 +919,14 @@ class BaseMixin:
'''Translates a given p_label into p_domain with p_mapping.'''
cfg = self.getProductConfig()
if not domain: domain = cfg.PROJECTNAME
return self.Control_Panel.TranslationService.utranslate(
domain, label, mapping, self, default=default,
target_language=language)
try:
res = self.Control_Panel.TranslationService.utranslate(
domain, label, mapping, self, default=default,
target_language=language)
except AttributeError:
# When run in test mode, Zope does not create the TranslationService
res = label
return res
def getPageLayout(self, layoutType):
'''Returns the layout corresponding to p_layoutType for p_self.'''

View file

@ -6,7 +6,7 @@
Plone (content types, catalogs, workflows, etc.)'''
# ------------------------------------------------------------------------------
import copy, types
import types
from appy.gen import *
# ------------------------------------------------------------------------------
@ -24,13 +24,6 @@ class ModelClass:
'layouts', 'required', 'filterable', 'validable', 'backd',
'isBack', 'sync', 'pageName')
@classmethod
def _appy_addField(klass, fieldName, fieldType, classDescr):
exec "klass.%s = fieldType" % fieldName
klass._appy_attributes.append(fieldName)
if hasattr(klass, '_appy_classes'):
klass._appy_classes[fieldName] = classDescr.name
@classmethod
def _appy_getTypeBody(klass, appyType):
'''This method returns the code declaration for p_appyType.'''
@ -137,155 +130,4 @@ class Tool(ModelClass):
exec 'del klass.%s' % k
klass._appy_attributes = list(defaultToolFields)
klass._appy_classes = {}
@classmethod
def _appy_copyField(klass, appyType):
'''From a given p_appyType, produce a type definition suitable for
storing the default value for this field.'''
res = copy.copy(appyType)
# 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.group = copy.copy(appyType.group)
res.page = copy.copy(appyType.page)
# Set default layouts for all Tool fields
res.layouts = res.formatLayouts(None)
res.specificReadPermission = False
res.specificWritePermission = False
res.multiplicity = (0, appyType.multiplicity[1])
if type(res.validator) == types.FunctionType:
# We will not be able to call this function from the tool.
res.validator = None
if isinstance(appyType, Ref):
res.link = True
res.add = False
res.back = copy.copy(appyType.back)
res.back.attribute += 'DefaultValue'
res.back.show = False
res.select = None # Not callable from tool.
return res
@classmethod
def _appy_addOptionalField(klass, fieldDescr):
className = fieldDescr.classDescr.name
fieldName = 'optionalFieldsFor%s' % className
fieldType = getattr(klass, fieldName, None)
if not fieldType:
fieldType = String(multiplicity=(0,None))
fieldType.validator = []
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
fieldType.validator.append(fieldDescr.fieldName)
fieldType.page.name = 'data'
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
@classmethod
def _appy_addDefaultField(klass, fieldDescr):
className = fieldDescr.classDescr.name
fieldName = 'defaultValueFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = klass._appy_copyField(fieldDescr.appyType)
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
fieldType.page.name = 'data'
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
@classmethod
def _appy_addPodRelatedFields(klass, fieldDescr):
'''Adds the fields needed in the Tool for configuring a Pod field.'''
className = fieldDescr.classDescr.name
# On what page and group to display those fields ?
pg = {'page': 'documentGeneration',
'group': '%s_2' % fieldDescr.classDescr.klass.__name__}
# Add the field that will store the pod template.
fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = File(**pg)
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
# Add the field that will store the output format(s)
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = String(validator=('odt', 'pdf', 'doc', 'rtf'),
multiplicity=(1,None), default=('odt',), **pg)
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
@classmethod
def _appy_addQueryResultColumns(klass, classDescr):
'''Adds, for class p_classDescr, the attribute in the tool that
allows to select what default columns will be shown on query
results.'''
className = classDescr.name
fieldName = 'resultColumnsFor%s' % className
fieldType = String(multiplicity=(0,None), validator=Selection(
'_appy_getAllFields*%s' % className), page='userInterface',
group=classDescr.klass.__name__)
klass._appy_addField(fieldName, fieldType, classDescr)
@classmethod
def _appy_addSearchRelatedFields(klass, classDescr):
'''Adds, for class p_classDescr, attributes related to the search
functionality for class p_classDescr.'''
className = classDescr.name
# Field that defines if advanced search is enabled for class
# p_classDescr or not.
fieldName = 'enableAdvancedSearchFor%s' % className
fieldType = Boolean(default=True, page='userInterface',
group=classDescr.klass.__name__)
klass._appy_addField(fieldName, fieldType, classDescr)
# Field that defines how many columns are shown on the custom search
# screen.
fieldName = 'numberOfSearchColumnsFor%s' % className
fieldType = Integer(default=3, page='userInterface',
group=classDescr.klass.__name__)
klass._appy_addField(fieldName, fieldType, classDescr)
# Field that allows to select, among all indexed fields, what fields
# must really be used in the search screen.
fieldName = 'searchFieldsFor%s' % className
defaultValue = [a[0] for a in classDescr.getOrderedAppyAttributes(
condition='attrValue.indexed')]
fieldType = String(multiplicity=(0,None), validator=Selection(
'_appy_getSearchableFields*%s' % className), default=defaultValue,
page='userInterface', group=classDescr.klass.__name__)
klass._appy_addField(fieldName, fieldType, classDescr)
@classmethod
def _appy_addImportRelatedFields(klass, classDescr):
'''Adds, for class p_classDescr, attributes related to the import
functionality for class p_classDescr.'''
className = classDescr.name
# Field that defines the path of the files to import.
fieldName = 'importPathFor%s' % className
defValue = classDescr.getCreateMean('Import').path
fieldType = String(page='data', multiplicity=(1,1), default=defValue,
group=classDescr.klass.__name__)
klass._appy_addField(fieldName, fieldType, classDescr)
@classmethod
def _appy_addWorkflowFields(klass, classDescr):
'''Adds, for a given p_classDescr, the workflow-related fields.'''
className = classDescr.name
groupName = classDescr.klass.__name__
# Adds a field allowing to show/hide completely any workflow-related
# information for a given class.
defaultValue = False
if classDescr.isRoot() or issubclass(classDescr.klass, ModelClass):
defaultValue = True
fieldName = 'showWorkflowFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface',
group=groupName)
klass._appy_addField(fieldName, fieldType, classDescr)
# Adds the boolean field for showing or not the field "enter comments".
fieldName = 'showWorkflowCommentFieldFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface',
group=groupName)
klass._appy_addField(fieldName, fieldType, classDescr)
# Adds the boolean field for showing all states in current state or not.
# If this boolean is True but the current phase counts only one state,
# we will not show the state at all: the fact of knowing in what phase
# we are is sufficient. If this boolean is False, we simply show the
# current state.
defaultValue = False
if len(classDescr.getPhases()) > 1:
defaultValue = True
fieldName = 'showAllStatesInPhaseFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface',
group=groupName)
klass._appy_addField(fieldName, fieldType, classDescr)
# ------------------------------------------------------------------------------

View file

@ -52,7 +52,7 @@
<input type="hidden" name="action" value="Update"/>
<input type="hidden" name="page" tal:attributes="value page"/>
<input type="hidden" name="nav" tal:attributes="value request/nav|nothing"/>
<input type="hidden" name="is_new" tal:attributes="value contextObj/checkCreationFlag"/>
<input type="hidden" name="is_new" tal:attributes="value contextObj/isTemporary"/>
<metal:show use-macro="here/skyn/page/macros/show"/>
</form>
<metal:footer use-macro="here/skyn/page/macros/footer"/>

View file

@ -387,7 +387,8 @@
layoutType We must know if we must render the widgets in a "view",
"edit" or "cell" layout
</tal:comment>
<table metal:define-macro="widgets" cellpadding="0" cellspacing="0">
<table metal:define-macro="widgets" cellpadding="0" cellspacing="0"
tal:attributes="width layout/width">
<tr tal:repeat="widget python: contextObj.getGroupedAppyTypes(layoutType, page)">
<td tal:condition="python: widget['type'] == 'group'">
<metal:call use-macro="portal/skyn/widgets/show/macros/group"/>

View file

@ -100,7 +100,7 @@
<tal:comment replace="nothing">The list of values</tal:comment>
<select tal:attributes="name widgetName" multiple="multiple" size="5">
<option tal:repeat="v python:tool.getPossibleValues(name, withTranslations=True, withBlankValue=False, className=contentType)"
tal:attributes="value python:v[0]" tal:content="python: v[1]">
tal:attributes="value python:v[0]" tal:content="python: tool.truncateValue(v[1], widget)">
</option>
</select>
</tal:selectSearch><br/>

View file

@ -234,7 +234,6 @@ th {
font-variant: small-caps;
font-weight: bold;
font-style: normal;
margin: 0.3em 0 0.3em 0;
}
.portletSep { border-top: 1px dashed #8cacbb; }
.portletGroupItem { padding-left: 0.8em; font-style: italic; }