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:
parent
3d87036f85
commit
7dc55f23c2
|
@ -675,7 +675,7 @@ class Type:
|
||||||
'''p_value is a real p_obj(ect) value from a field from this type. This
|
'''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
|
method returns a pretty, string-formatted version, for displaying
|
||||||
purposes. Needs to be overridden by some child classes.'''
|
purposes. Needs to be overridden by some child classes.'''
|
||||||
if value in nullValues: return ''
|
if self.isEmptyValue(value): return ''
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def getRequestValue(self, request):
|
def getRequestValue(self, request):
|
||||||
|
@ -689,7 +689,7 @@ class Type:
|
||||||
representation of the field value coming from the request.
|
representation of the field value coming from the request.
|
||||||
This method computes the real (potentially converted or manipulated
|
This method computes the real (potentially converted or manipulated
|
||||||
in some other way) value as can be stored in the database.'''
|
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
|
return value
|
||||||
|
|
||||||
def getMasterData(self):
|
def getMasterData(self):
|
||||||
|
@ -698,6 +698,10 @@ class Type:
|
||||||
if self.master: return (self.master, self.masterValue)
|
if self.master: return (self.master, self.masterValue)
|
||||||
if self.group: return self.group.getMasterData()
|
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):
|
def validateValue(self, obj, value):
|
||||||
'''This method may be overridden by child classes and will be called at
|
'''This method may be overridden by child classes and will be called at
|
||||||
the right moment by m_validate defined below for triggering
|
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
|
definition. If it is the case, None is returned. Else, a translated
|
||||||
error message is returned.'''
|
error message is returned.'''
|
||||||
# Check that a value is given if required.
|
# 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 self.required and self.isClientVisible(obj):
|
||||||
# If the field is required, but not visible according to
|
# If the field is required, but not visible according to
|
||||||
# master/slave relationships, we consider it not to be required.
|
# master/slave relationships, we consider it not to be required.
|
||||||
|
@ -759,6 +763,28 @@ class Type:
|
||||||
p_self type definition on p_obj.'''
|
p_self type definition on p_obj.'''
|
||||||
setattr(obj, self.name, value)
|
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):
|
class Integer(Type):
|
||||||
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
||||||
default=None, optional=False, editDefault=False, show=True,
|
default=None, optional=False, editDefault=False, show=True,
|
||||||
|
@ -781,7 +807,11 @@ class Integer(Type):
|
||||||
return obj.translate('bad_%s' % self.pythonType.__name__)
|
return obj.translate('bad_%s' % self.pythonType.__name__)
|
||||||
|
|
||||||
def getStorableValue(self, value):
|
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):
|
class Float(Type):
|
||||||
allowedDecimalSeps = (',', '.')
|
allowedDecimalSeps = (',', '.')
|
||||||
|
@ -814,7 +844,7 @@ class Float(Type):
|
||||||
self.pythonType = float
|
self.pythonType = float
|
||||||
|
|
||||||
def getFormattedValue(self, obj, value):
|
def getFormattedValue(self, obj, value):
|
||||||
if value in nullValues: return ''
|
if self.isEmptyValue(value): return ''
|
||||||
# Determine the field separator
|
# Determine the field separator
|
||||||
sep = self.sep[0]
|
sep = self.sep[0]
|
||||||
# Produce the rounded string representation
|
# Produce the rounded string representation
|
||||||
|
@ -847,7 +877,7 @@ class Float(Type):
|
||||||
return obj.translate('bad_%s' % self.pythonType.__name__)
|
return obj.translate('bad_%s' % self.pythonType.__name__)
|
||||||
|
|
||||||
def getStorableValue(self, value):
|
def getStorableValue(self, value):
|
||||||
if value not in nullValues:
|
if not self.isEmptyValue(value):
|
||||||
for sep in self.sep: value = value.replace(sep, '.')
|
for sep in self.sep: value = value.replace(sep, '.')
|
||||||
return self.pythonType(value)
|
return self.pythonType(value)
|
||||||
|
|
||||||
|
@ -1021,7 +1051,7 @@ class String(Type):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def getFormattedValue(self, obj, value):
|
def getFormattedValue(self, obj, value):
|
||||||
if value in nullValues: return ''
|
if self.isEmptyValue(value): return ''
|
||||||
res = value
|
res = value
|
||||||
if self.isSelect:
|
if self.isSelect:
|
||||||
if isinstance(self.validator, Selection):
|
if isinstance(self.validator, Selection):
|
||||||
|
@ -1148,7 +1178,7 @@ class Boolean(Type):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getStorableValue(self, value):
|
def getStorableValue(self, value):
|
||||||
if value not in nullValues:
|
if not self.isEmptyValue(value):
|
||||||
exec 'res = %s' % value
|
exec 'res = %s' % value
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -1187,7 +1217,7 @@ class Date(Type):
|
||||||
'jscalendar/calendar-en.js')
|
'jscalendar/calendar-en.js')
|
||||||
|
|
||||||
def getFormattedValue(self, obj, value):
|
def getFormattedValue(self, obj, value):
|
||||||
if value in nullValues: return ''
|
if self.isEmptyValue(value): return ''
|
||||||
res = value.strftime('%d/%m/') + str(value.year())
|
res = value.strftime('%d/%m/') + str(value.year())
|
||||||
if self.format == Date.WITH_HOUR:
|
if self.format == Date.WITH_HOUR:
|
||||||
res += ' %s' % value.strftime('%H:%M')
|
res += ' %s' % value.strftime('%H:%M')
|
||||||
|
@ -1212,7 +1242,7 @@ class Date(Type):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def getStorableValue(self, value):
|
def getStorableValue(self, value):
|
||||||
if value not in nullValues:
|
if not self.isEmptyValue(value):
|
||||||
import DateTime
|
import DateTime
|
||||||
return DateTime.DateTime(value)
|
return DateTime.DateTime(value)
|
||||||
|
|
||||||
|
@ -1246,13 +1276,21 @@ class File(Type):
|
||||||
|
|
||||||
def getDefaultLayouts(self): return {'view':'lf','edit':'lrv-f'}
|
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')
|
imageExts = ('.jpg', '.jpeg', '.png', '.gif')
|
||||||
def validateValue(self, obj, value):
|
def validateValue(self, obj, value):
|
||||||
form = obj.REQUEST.form
|
form = obj.REQUEST.form
|
||||||
action = '%s_delete' % self.name
|
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
|
# 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.
|
# a new file to upload.
|
||||||
return obj.translate('file_required')
|
return obj.translate('file_required')
|
||||||
# Check that, if self.isImage, the uploaded file is really an image
|
# Check that, if self.isImage, the uploaded file is really an image
|
||||||
|
@ -1345,7 +1383,7 @@ class Ref(Type):
|
||||||
historized, sync)
|
historized, sync)
|
||||||
self.validable = self.link
|
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):
|
def isShowable(self, obj, layoutType):
|
||||||
res = Type.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]
|
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:])
|
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):
|
class Computed(Type):
|
||||||
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
||||||
default=None, optional=False, editDefault=False, show='view',
|
default=None, optional=False, editDefault=False, show='view',
|
||||||
|
@ -1491,18 +1541,17 @@ class Computed(Type):
|
||||||
|
|
||||||
def getValue(self, obj):
|
def getValue(self, obj):
|
||||||
'''Computes the value instead of getting it in the database.'''
|
'''Computes the value instead of getting it in the database.'''
|
||||||
if not self.method: return ''
|
if not self.method: return
|
||||||
obj = obj.appy()
|
obj = obj.appy()
|
||||||
try:
|
try:
|
||||||
res = self.method(obj)
|
return self.method(obj)
|
||||||
if not isinstance(res, basestring):
|
|
||||||
res = repr(res)
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
obj.log(Traceback.get(), type='error')
|
obj.log(Traceback.get(), type='error')
|
||||||
res = str(e)
|
return str(e)
|
||||||
return res
|
|
||||||
|
|
||||||
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):
|
class Action(Type):
|
||||||
'''An action is a workflow-independent Python method that can be triggered
|
'''An action is a workflow-independent Python method that can be triggered
|
||||||
|
|
|
@ -40,7 +40,10 @@
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
rowDelimiters = {'-':'middle', '=':'top', '_':'bottom'}
|
rowDelimiters = {'-':'middle', '=':'top', '_':'bottom'}
|
||||||
|
rowDelms = ''.join(rowDelimiters.keys())
|
||||||
cellDelimiters = {'|': 'center', ';': 'left', '!': 'right'}
|
cellDelimiters = {'|': 'center', ';': 'left', '!': 'right'}
|
||||||
|
cellDelms = ''.join(cellDelimiters.keys())
|
||||||
|
|
||||||
macroDict = {
|
macroDict = {
|
||||||
# Page-related elements
|
# Page-related elements
|
||||||
's': ('page', 'header'), 'w': ('page', 'widgets'),
|
's': ('page', 'header'), 'w': ('page', 'widgets'),
|
||||||
|
@ -158,6 +161,10 @@ class Table(LayoutElement):
|
||||||
res = layout
|
res = layout
|
||||||
for letter in Table.derivedRepls[derivedType]:
|
for letter in Table.derivedRepls[derivedType]:
|
||||||
res = res.replace(letter, '')
|
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
|
return res
|
||||||
|
|
||||||
def addCssClasses(self, css_class):
|
def addCssClasses(self, css_class):
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import types, copy
|
import types, copy
|
||||||
from model import ModelClass, Tool, toolFieldPrefixes
|
from model import ModelClass, toolFieldPrefixes
|
||||||
from utils import stringify
|
from utils import stringify
|
||||||
import appy.gen
|
import appy.gen
|
||||||
import appy.gen.descriptors
|
import appy.gen.descriptors
|
||||||
from appy.gen.po import PoMessage
|
from appy.gen.po import PoMessage
|
||||||
from appy.gen import Date, String, State, Transition, Type, Search, \
|
from appy.gen import *
|
||||||
Selection, Import, Role
|
|
||||||
from appy.gen.utils import produceNiceMessage, getClassName
|
from appy.gen.utils import produceNiceMessage, getClassName
|
||||||
TABS = 4 # Number of blanks in a Python indentation.
|
TABS = 4 # Number of blanks in a Python indentation.
|
||||||
|
|
||||||
|
@ -131,7 +130,7 @@ class FieldDescriptor:
|
||||||
self.generator.labels.append(msg)
|
self.generator.labels.append(msg)
|
||||||
self.classDescr.labelsToPropagate.append(msg)
|
self.classDescr.labelsToPropagate.append(msg)
|
||||||
# Add the POD-related fields on the Tool
|
# Add the POD-related fields on the Tool
|
||||||
Tool._appy_addPodRelatedFields(self)
|
self.generator.tool.addPodRelatedFields(self)
|
||||||
|
|
||||||
notToValidateFields = ('Info', 'Computed', 'Action', 'Pod')
|
notToValidateFields = ('Info', 'Computed', 'Action', 'Pod')
|
||||||
def walkAppyType(self):
|
def walkAppyType(self):
|
||||||
|
@ -140,10 +139,10 @@ class FieldDescriptor:
|
||||||
# Manage things common to all Appy types
|
# Manage things common to all Appy types
|
||||||
# - optional ?
|
# - optional ?
|
||||||
if self.appyType.optional:
|
if self.appyType.optional:
|
||||||
Tool._appy_addOptionalField(self)
|
self.generator.tool.addOptionalField(self)
|
||||||
# - edit default value ?
|
# - edit default value ?
|
||||||
if self.appyType.editDefault:
|
if self.appyType.editDefault:
|
||||||
Tool._appy_addDefaultField(self)
|
self.generator.tool.addDefaultField(self)
|
||||||
# - put an index on this field?
|
# - put an index on this field?
|
||||||
if self.appyType.indexed and \
|
if self.appyType.indexed and \
|
||||||
(self.fieldName not in ('title', 'description')):
|
(self.fieldName not in ('title', 'description')):
|
||||||
|
@ -381,11 +380,13 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
self.modelClass = self.klass
|
self.modelClass = self.klass
|
||||||
self.predefined = True
|
self.predefined = True
|
||||||
self.customized = False
|
self.customized = False
|
||||||
|
|
||||||
def getParents(self, allClasses=()):
|
def getParents(self, allClasses=()):
|
||||||
res = ['Tool']
|
res = ['Tool']
|
||||||
if self.customized:
|
if self.customized:
|
||||||
res.append('%s.%s' % (self.klass.__module__, self.klass.__name__))
|
res.append('%s.%s' % (self.klass.__module__, self.klass.__name__))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def update(self, klass, attributes):
|
def update(self, klass, attributes):
|
||||||
'''This method is called by the generator when he finds a custom tool
|
'''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
|
definition. We must then add the custom tool elements in this default
|
||||||
|
@ -393,11 +394,133 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
self.orderedAttributes += attributes
|
self.orderedAttributes += attributes
|
||||||
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 isRoot(self): return False
|
def isRoot(self): return False
|
||||||
def generateSchema(self):
|
def generateSchema(self):
|
||||||
ClassDescriptor.generateSchema(self, configClass=True)
|
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):
|
class UserClassDescriptor(ClassDescriptor):
|
||||||
'''Represents an Archetypes-compliant class that corresponds to the User
|
'''Represents an Archetypes-compliant class that corresponds to the User
|
||||||
for the generated application.'''
|
for the generated application.'''
|
||||||
|
|
|
@ -8,8 +8,9 @@ from appy.gen import *
|
||||||
from appy.gen.po import PoMessage, PoFile, PoParser
|
from appy.gen.po import PoMessage, PoFile, PoParser
|
||||||
from appy.gen.generator import Generator as AbstractGenerator
|
from appy.gen.generator import Generator as AbstractGenerator
|
||||||
from appy.gen.utils import getClassName
|
from appy.gen.utils import getClassName
|
||||||
|
from descriptors import ClassDescriptor, WorkflowDescriptor, \
|
||||||
|
ToolClassDescriptor, UserClassDescriptor
|
||||||
from model import ModelClass, User, Tool
|
from model import ModelClass, User, Tool
|
||||||
from descriptors import *
|
|
||||||
|
|
||||||
# Common methods that need to be defined on every Archetype class --------------
|
# Common methods that need to be defined on every Archetype class --------------
|
||||||
COMMON_METHODS = '''
|
COMMON_METHODS = '''
|
||||||
|
@ -352,8 +353,8 @@ class Generator(AbstractGenerator):
|
||||||
wfInit += 'wf._states.append("%s")\n' % stateName
|
wfInit += 'wf._states.append("%s")\n' % stateName
|
||||||
wfInit += 'workflowInstances[%s] = wf\n' % className
|
wfInit += 'workflowInstances[%s] = wf\n' % className
|
||||||
repls['workflowInstancesInit'] = wfInit
|
repls['workflowInstancesInit'] = wfInit
|
||||||
# Compute the list of ordered attributes (foward and backward, inherited
|
# Compute the list of ordered attributes (forward and backward,
|
||||||
# included) for every Appy class.
|
# inherited included) for every Appy class.
|
||||||
attributes = []
|
attributes = []
|
||||||
attributesDict = []
|
attributesDict = []
|
||||||
for classDescr in self.getClasses(include='all'):
|
for classDescr in self.getClasses(include='all'):
|
||||||
|
@ -622,21 +623,16 @@ class Generator(AbstractGenerator):
|
||||||
for childDescr in classDescr.getChildren():
|
for childDescr in classDescr.getChildren():
|
||||||
childFieldName = fieldName % childDescr.name
|
childFieldName = fieldName % childDescr.name
|
||||||
fieldType.group = childDescr.klass.__name__
|
fieldType.group = childDescr.klass.__name__
|
||||||
Tool._appy_addField(childFieldName, fieldType, childDescr)
|
self.tool.addField(childFieldName, fieldType, childDescr)
|
||||||
if classDescr.isRoot():
|
if classDescr.isRoot():
|
||||||
# We must be able to configure query results from the tool.
|
# We must be able to configure query results from the tool.
|
||||||
Tool._appy_addQueryResultColumns(classDescr)
|
self.tool.addQueryResultColumns(classDescr)
|
||||||
# Add the search-related fields.
|
# Add the search-related fields.
|
||||||
Tool._appy_addSearchRelatedFields(classDescr)
|
self.tool.addSearchRelatedFields(classDescr)
|
||||||
importMean = classDescr.getCreateMean('Import')
|
importMean = classDescr.getCreateMean('Import')
|
||||||
if importMean:
|
if importMean:
|
||||||
Tool._appy_addImportRelatedFields(classDescr)
|
self.tool.addImportRelatedFields(classDescr)
|
||||||
Tool._appy_addWorkflowFields(self.user)
|
self.tool.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.generateSchema()
|
self.tool.generateSchema()
|
||||||
|
|
||||||
# Generate the Tool class
|
# Generate the Tool class
|
||||||
|
@ -663,7 +659,7 @@ class Generator(AbstractGenerator):
|
||||||
k = classDescr.klass
|
k = classDescr.klass
|
||||||
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
|
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
|
||||||
if not classDescr.isAbstract():
|
if not classDescr.isAbstract():
|
||||||
Tool._appy_addWorkflowFields(classDescr)
|
self.tool.addWorkflowFields(classDescr)
|
||||||
# Determine base archetypes schema and class
|
# Determine base archetypes schema and class
|
||||||
baseClass = 'BaseContent'
|
baseClass = 'BaseContent'
|
||||||
baseSchema = 'BaseSchema'
|
baseSchema = 'BaseSchema'
|
||||||
|
|
|
@ -180,7 +180,8 @@ class ToolMixin(BaseMixin):
|
||||||
sortMethod = importParams['sort']
|
sortMethod = importParams['sort']
|
||||||
if sortMethod: sortMethod = sortMethod.__get__('')
|
if sortMethod: sortMethod = sortMethod.__get__('')
|
||||||
elems = []
|
elems = []
|
||||||
importPath = getattr(self, 'importPathFor%s' % contentType)
|
importType = self.getAppyType('importPathFor%s' % contentType)
|
||||||
|
importPath = importType.getValue(self)
|
||||||
for elem in os.listdir(importPath):
|
for elem in os.listdir(importPath):
|
||||||
elemFullPath = os.path.join(importPath, elem)
|
elemFullPath = os.path.join(importPath, elem)
|
||||||
elemInfo = onElement(elemFullPath)
|
elemInfo = onElement(elemFullPath)
|
||||||
|
|
|
@ -919,9 +919,14 @@ class BaseMixin:
|
||||||
'''Translates a given p_label into p_domain with p_mapping.'''
|
'''Translates a given p_label into p_domain with p_mapping.'''
|
||||||
cfg = self.getProductConfig()
|
cfg = self.getProductConfig()
|
||||||
if not domain: domain = cfg.PROJECTNAME
|
if not domain: domain = cfg.PROJECTNAME
|
||||||
return self.Control_Panel.TranslationService.utranslate(
|
try:
|
||||||
domain, label, mapping, self, default=default,
|
res = self.Control_Panel.TranslationService.utranslate(
|
||||||
target_language=language)
|
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):
|
def getPageLayout(self, layoutType):
|
||||||
'''Returns the layout corresponding to p_layoutType for p_self.'''
|
'''Returns the layout corresponding to p_layoutType for p_self.'''
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Plone (content types, catalogs, workflows, etc.)'''
|
Plone (content types, catalogs, workflows, etc.)'''
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import copy, types
|
import types
|
||||||
from appy.gen import *
|
from appy.gen import *
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@ -24,13 +24,6 @@ class ModelClass:
|
||||||
'layouts', 'required', 'filterable', 'validable', 'backd',
|
'layouts', 'required', 'filterable', 'validable', 'backd',
|
||||||
'isBack', 'sync', 'pageName')
|
'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
|
@classmethod
|
||||||
def _appy_getTypeBody(klass, appyType):
|
def _appy_getTypeBody(klass, appyType):
|
||||||
'''This method returns the code declaration for p_appyType.'''
|
'''This method returns the code declaration for p_appyType.'''
|
||||||
|
@ -137,155 +130,4 @@ class Tool(ModelClass):
|
||||||
exec 'del klass.%s' % k
|
exec 'del klass.%s' % k
|
||||||
klass._appy_attributes = list(defaultToolFields)
|
klass._appy_attributes = list(defaultToolFields)
|
||||||
klass._appy_classes = {}
|
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)
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
<input type="hidden" name="action" value="Update"/>
|
<input type="hidden" name="action" value="Update"/>
|
||||||
<input type="hidden" name="page" tal:attributes="value page"/>
|
<input type="hidden" name="page" tal:attributes="value page"/>
|
||||||
<input type="hidden" name="nav" tal:attributes="value request/nav|nothing"/>
|
<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"/>
|
<metal:show use-macro="here/skyn/page/macros/show"/>
|
||||||
</form>
|
</form>
|
||||||
<metal:footer use-macro="here/skyn/page/macros/footer"/>
|
<metal:footer use-macro="here/skyn/page/macros/footer"/>
|
||||||
|
|
|
@ -387,7 +387,8 @@
|
||||||
layoutType We must know if we must render the widgets in a "view",
|
layoutType We must know if we must render the widgets in a "view",
|
||||||
"edit" or "cell" layout
|
"edit" or "cell" layout
|
||||||
</tal:comment>
|
</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)">
|
<tr tal:repeat="widget python: contextObj.getGroupedAppyTypes(layoutType, page)">
|
||||||
<td tal:condition="python: widget['type'] == 'group'">
|
<td tal:condition="python: widget['type'] == 'group'">
|
||||||
<metal:call use-macro="portal/skyn/widgets/show/macros/group"/>
|
<metal:call use-macro="portal/skyn/widgets/show/macros/group"/>
|
||||||
|
|
|
@ -100,7 +100,7 @@
|
||||||
<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" multiple="multiple" size="5">
|
<select tal:attributes="name widgetName" multiple="multiple" size="5">
|
||||||
<option tal:repeat="v python:tool.getPossibleValues(name, withTranslations=True, withBlankValue=False, className=contentType)"
|
<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>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</tal:selectSearch><br/>
|
</tal:selectSearch><br/>
|
||||||
|
|
|
@ -234,7 +234,6 @@ th {
|
||||||
font-variant: small-caps;
|
font-variant: small-caps;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
margin: 0.3em 0 0.3em 0;
|
|
||||||
}
|
}
|
||||||
.portletSep { border-top: 1px dashed #8cacbb; }
|
.portletSep { border-top: 1px dashed #8cacbb; }
|
||||||
.portletGroupItem { padding-left: 0.8em; font-style: italic; }
|
.portletGroupItem { padding-left: 0.8em; font-style: italic; }
|
||||||
|
|
Loading…
Reference in a new issue