Improved SAP interface and added historization of fields.
This commit is contained in:
parent
b888f8149b
commit
d320a369c9
|
@ -47,7 +47,7 @@ class Type:
|
||||||
def __init__(self, validator, multiplicity, index, default, optional,
|
def __init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, indexed, searchable,
|
editDefault, show, page, group, move, indexed, searchable,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus):
|
height, master, masterValue, focus, historized):
|
||||||
# The validator restricts which values may be defined. It can be an
|
# The validator restricts which values may be defined. It can be an
|
||||||
# interval (1,None), a list of string values ['choice1', 'choice2'],
|
# interval (1,None), a list of string values ['choice1', 'choice2'],
|
||||||
# a regular expression, a custom function, a Selection instance, etc.
|
# a regular expression, a custom function, a Selection instance, etc.
|
||||||
|
@ -107,6 +107,9 @@ class Type:
|
||||||
# If a field must retain attention in a particular way, set focus=True.
|
# If a field must retain attention in a particular way, set focus=True.
|
||||||
# It will be rendered in a special way.
|
# It will be rendered in a special way.
|
||||||
self.focus = focus
|
self.focus = focus
|
||||||
|
# If we must keep track of changes performed on a field, "historized"
|
||||||
|
# must be set to True.
|
||||||
|
self.historized = historized
|
||||||
self.id = id(self)
|
self.id = id(self)
|
||||||
self.type = self.__class__.__name__
|
self.type = self.__class__.__name__
|
||||||
self.pythonType = None # The True corresponding Python type
|
self.pythonType = None # The True corresponding Python type
|
||||||
|
@ -128,11 +131,11 @@ class Integer(Type):
|
||||||
page='main', group=None, move=0, indexed=False,
|
page='main', group=None, move=0, indexed=False,
|
||||||
searchable=False, specificReadPermission=False,
|
searchable=False, specificReadPermission=False,
|
||||||
specificWritePermission=False, width=None, height=None,
|
specificWritePermission=False, width=None, height=None,
|
||||||
master=None, masterValue=None, focus=False):
|
master=None, masterValue=None, focus=False, historized=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, indexed, searchable,
|
editDefault, show, page, group, move, indexed, searchable,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
self.pythonType = long
|
self.pythonType = long
|
||||||
|
|
||||||
class Float(Type):
|
class Float(Type):
|
||||||
|
@ -141,11 +144,11 @@ class Float(Type):
|
||||||
page='main', group=None, move=0, indexed=False,
|
page='main', group=None, move=0, indexed=False,
|
||||||
searchable=False, specificReadPermission=False,
|
searchable=False, specificReadPermission=False,
|
||||||
specificWritePermission=False, width=None, height=None,
|
specificWritePermission=False, width=None, height=None,
|
||||||
master=None, masterValue=None, focus=False):
|
master=None, masterValue=None, focus=False, historized=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, indexed, False,
|
editDefault, show, page, group, move, indexed, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
self.pythonType = float
|
self.pythonType = float
|
||||||
|
|
||||||
class String(Type):
|
class String(Type):
|
||||||
|
@ -249,11 +252,11 @@ class String(Type):
|
||||||
show=True, page='main', group=None, move=0, indexed=False,
|
show=True, page='main', group=None, move=0, indexed=False,
|
||||||
searchable=False, specificReadPermission=False,
|
searchable=False, specificReadPermission=False,
|
||||||
specificWritePermission=False, width=None, height=None,
|
specificWritePermission=False, width=None, height=None,
|
||||||
master=None, masterValue=None, focus=False):
|
master=None, masterValue=None, focus=False, historized=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, indexed, searchable,
|
editDefault, show, page, group, move, indexed, searchable,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
self.format = format
|
self.format = format
|
||||||
self.isSelect = self.isSelection()
|
self.isSelect = self.isSelection()
|
||||||
def isSelection(self):
|
def isSelection(self):
|
||||||
|
@ -276,11 +279,11 @@ class Boolean(Type):
|
||||||
page='main', group=None, move=0, indexed=False,
|
page='main', group=None, move=0, indexed=False,
|
||||||
searchable=False, specificReadPermission=False,
|
searchable=False, specificReadPermission=False,
|
||||||
specificWritePermission=False, width=None, height=None,
|
specificWritePermission=False, width=None, height=None,
|
||||||
master=None, masterValue=None, focus=False):
|
master=None, masterValue=None, focus=False, historized=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, indexed, searchable,
|
editDefault, show, page, group, move, indexed, searchable,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
self.pythonType = bool
|
self.pythonType = bool
|
||||||
|
|
||||||
class Date(Type):
|
class Date(Type):
|
||||||
|
@ -294,11 +297,11 @@ class Date(Type):
|
||||||
group=None, move=0, indexed=False, searchable=False,
|
group=None, move=0, indexed=False, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None,
|
width=None, height=None, master=None, masterValue=None,
|
||||||
focus=False):
|
focus=False, historized=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, indexed, searchable,
|
editDefault, show, page, group, move, indexed, searchable,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
self.format = format
|
self.format = format
|
||||||
self.startYear = startYear
|
self.startYear = startYear
|
||||||
self.endYear = endYear
|
self.endYear = endYear
|
||||||
|
@ -309,11 +312,12 @@ class File(Type):
|
||||||
page='main', group=None, move=0, indexed=False,
|
page='main', group=None, move=0, indexed=False,
|
||||||
searchable=False, specificReadPermission=False,
|
searchable=False, specificReadPermission=False,
|
||||||
specificWritePermission=False, width=None, height=None,
|
specificWritePermission=False, width=None, height=None,
|
||||||
master=None, masterValue=None, focus=False, isImage=False):
|
master=None, masterValue=None, focus=False, historized=False,
|
||||||
|
isImage=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, indexed, False,
|
editDefault, show, page, group, move, indexed, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
self.isImage = isImage
|
self.isImage = isImage
|
||||||
|
|
||||||
class Ref(Type):
|
class Ref(Type):
|
||||||
|
@ -325,11 +329,11 @@ class Ref(Type):
|
||||||
maxPerPage=30, move=0, indexed=False, searchable=False,
|
maxPerPage=30, move=0, indexed=False, searchable=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
specificReadPermission=False, specificWritePermission=False,
|
||||||
width=None, height=None, master=None, masterValue=None,
|
width=None, height=None, master=None, masterValue=None,
|
||||||
focus=False):
|
focus=False, historized=False):
|
||||||
Type.__init__(self, validator, multiplicity, index, default, optional,
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
||||||
editDefault, show, page, group, move, indexed, False,
|
editDefault, show, page, group, move, indexed, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
self.klass = klass
|
self.klass = klass
|
||||||
self.attribute = attribute
|
self.attribute = attribute
|
||||||
self.add = add # May the user add new objects through this ref ?
|
self.add = add # May the user add new objects through this ref ?
|
||||||
|
@ -356,11 +360,11 @@ class Computed(Type):
|
||||||
searchable=False, specificReadPermission=False,
|
searchable=False, specificReadPermission=False,
|
||||||
specificWritePermission=False, width=None, height=None,
|
specificWritePermission=False, width=None, height=None,
|
||||||
method=None, plainText=True, master=None, masterValue=None,
|
method=None, plainText=True, master=None, masterValue=None,
|
||||||
focus=False):
|
focus=False, historized=False):
|
||||||
Type.__init__(self, None, multiplicity, index, default, optional,
|
Type.__init__(self, None, multiplicity, index, default, optional,
|
||||||
False, show, page, group, move, indexed, False,
|
False, show, page, group, move, indexed, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
self.method = method # The method used for computing the field value
|
self.method = method # The method used for computing the field value
|
||||||
self.plainText = plainText # Does field computation produce pain text
|
self.plainText = plainText # Does field computation produce pain text
|
||||||
# or XHTML?
|
# or XHTML?
|
||||||
|
@ -376,11 +380,11 @@ class Action(Type):
|
||||||
searchable=False, specificReadPermission=False,
|
searchable=False, specificReadPermission=False,
|
||||||
specificWritePermission=False, width=None, height=None,
|
specificWritePermission=False, width=None, height=None,
|
||||||
action=None, result='computation', master=None,
|
action=None, result='computation', master=None,
|
||||||
masterValue=None, focus=False):
|
masterValue=None, focus=False, historized=False):
|
||||||
Type.__init__(self, None, (0,1), index, default, optional,
|
Type.__init__(self, None, (0,1), index, default, optional,
|
||||||
False, show, page, group, move, indexed, False,
|
False, show, page, group, move, indexed, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
self.action = action # Can be a single method or a list/tuple of methods
|
self.action = action # Can be a single method or a list/tuple of methods
|
||||||
self.result = result # 'computation' means that the action will simply
|
self.result = result # 'computation' means that the action will simply
|
||||||
# compute things and redirect the user to the same page, with some
|
# compute things and redirect the user to the same page, with some
|
||||||
|
@ -425,11 +429,11 @@ class Info(Type):
|
||||||
page='main', group=None, move=0, indexed=False,
|
page='main', group=None, move=0, indexed=False,
|
||||||
searchable=False, specificReadPermission=False,
|
searchable=False, specificReadPermission=False,
|
||||||
specificWritePermission=False, width=None, height=None,
|
specificWritePermission=False, width=None, height=None,
|
||||||
master=None, masterValue=None, focus=False):
|
master=None, masterValue=None, focus=False, historized=False):
|
||||||
Type.__init__(self, None, (0,1), index, default, optional,
|
Type.__init__(self, None, (0,1), index, default, optional,
|
||||||
False, show, page, group, move, indexed, False,
|
False, show, page, group, move, indexed, False,
|
||||||
specificReadPermission, specificWritePermission, width,
|
specificReadPermission, specificWritePermission, width,
|
||||||
height, master, masterValue, focus)
|
height, master, masterValue, focus, historized)
|
||||||
|
|
||||||
# Workflow-specific types ------------------------------------------------------
|
# Workflow-specific types ------------------------------------------------------
|
||||||
class State:
|
class State:
|
||||||
|
|
|
@ -91,6 +91,9 @@ class Generator(AbstractGenerator):
|
||||||
poMsg = msg(app, '', app); poMsg.produceNiceDefault()
|
poMsg = msg(app, '', app); poMsg.produceNiceDefault()
|
||||||
self.labels += [poMsg,
|
self.labels += [poMsg,
|
||||||
msg('workflow_state', '', msg.WORKFLOW_STATE),
|
msg('workflow_state', '', msg.WORKFLOW_STATE),
|
||||||
|
msg('data_change', '', msg.DATA_CHANGE),
|
||||||
|
msg('modified_field', '', msg.MODIFIED_FIELD),
|
||||||
|
msg('previous_value', '', msg.PREVIOUS_VALUE),
|
||||||
msg('phase', '', msg.PHASE),
|
msg('phase', '', msg.PHASE),
|
||||||
msg('root_type', '', msg.ROOT_TYPE),
|
msg('root_type', '', msg.ROOT_TYPE),
|
||||||
msg('workflow_comment', '', msg.WORKFLOW_COMMENT),
|
msg('workflow_comment', '', msg.WORKFLOW_COMMENT),
|
||||||
|
|
|
@ -79,7 +79,14 @@ def afterTest(test):
|
||||||
exec 'from Products.%s import numberOfExecutedTests' % appName
|
exec 'from Products.%s import numberOfExecutedTests' % appName
|
||||||
if cov and (numberOfExecutedTests == totalNumberOfTests):
|
if cov and (numberOfExecutedTests == totalNumberOfTests):
|
||||||
cov.stop()
|
cov.stop()
|
||||||
# Dumps the coverage report
|
|
||||||
appModules = test.getNonEmptySubModules(appName)
|
appModules = test.getNonEmptySubModules(appName)
|
||||||
|
# Dumps the coverage report
|
||||||
|
# HTML version
|
||||||
cov.html_report(directory=covFolder, morfs=appModules)
|
cov.html_report(directory=covFolder, morfs=appModules)
|
||||||
|
# Summary in a text file
|
||||||
|
f = file('%s/summary.txt' % covFolder, 'w')
|
||||||
|
cov.report(file=f, morfs=appModules)
|
||||||
|
f.close()
|
||||||
|
# Annotated modules
|
||||||
|
cov.annotate(directory=covFolder, morfs=appModules)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -31,16 +31,14 @@ class AbstractMixin:
|
||||||
if created:
|
if created:
|
||||||
obj = self.portal_factory.doCreate(self, self.id) # portal_factory
|
obj = self.portal_factory.doCreate(self, self.id) # portal_factory
|
||||||
# creates the final object from the temp object.
|
# creates the final object from the temp object.
|
||||||
if created and (obj._appy_meta_type == 'tool'):
|
previousData = None
|
||||||
# We are in the special case where the tool itself is being created.
|
if not created: previousData = self.rememberPreviousData()
|
||||||
# In this case, we do not process form data.
|
# We do not process form data (=real update on the object) if the tool
|
||||||
pass
|
# itself is being created.
|
||||||
else:
|
if obj._appy_meta_type != 'tool': obj.processForm()
|
||||||
obj.processForm()
|
if previousData:
|
||||||
|
# Keep in history potential changes on historized fields
|
||||||
# Get the current language and put it in the request
|
self.historizeData(previousData)
|
||||||
#if rq.form.has_key('current_lang'):
|
|
||||||
# rq.form['language'] = rq.form.get('current_lang')
|
|
||||||
|
|
||||||
# Manage references
|
# Manage references
|
||||||
obj._appy_manageRefs(created)
|
obj._appy_manageRefs(created)
|
||||||
|
@ -145,15 +143,79 @@ class AbstractMixin:
|
||||||
self.plone_utils.addPortalMessage(msg)
|
self.plone_utils.addPortalMessage(msg)
|
||||||
self.goto(rq['HTTP_REFERER'])
|
self.goto(rq['HTTP_REFERER'])
|
||||||
|
|
||||||
|
def rememberPreviousData(self):
|
||||||
|
'''This method is called before updating an object and remembers, for
|
||||||
|
every historized field, the previous value. Result is a dict
|
||||||
|
~{s_fieldName: previousFieldValue}~'''
|
||||||
|
res = {}
|
||||||
|
for atField in self.Schema().filterFields(isMetadata=0):
|
||||||
|
fieldName = atField.getName()
|
||||||
|
appyType = self.getAppyType(fieldName, asDict=False)
|
||||||
|
if appyType and appyType.historized:
|
||||||
|
res[fieldName] = (getattr(self, fieldName),
|
||||||
|
atField.widget.label_msgid)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def historizeData(self, previousData):
|
||||||
|
'''Records in the object history potential changes on historized fields.
|
||||||
|
p_previousData contains the values, before an update, of the
|
||||||
|
historized fields, while p_self already contains the (potentially)
|
||||||
|
modified values.'''
|
||||||
|
# Remove from previousData all values that were not changed
|
||||||
|
for fieldName in previousData.keys():
|
||||||
|
if getattr(self, fieldName) == previousData[fieldName][0]:
|
||||||
|
del previousData[fieldName]
|
||||||
|
if previousData:
|
||||||
|
# Create the event to add in the history
|
||||||
|
DateTime = self.getProductConfig().DateTime
|
||||||
|
state = self.portal_workflow.getInfoFor(self, 'review_state')
|
||||||
|
user = self.portal_membership.getAuthenticatedMember()
|
||||||
|
event = {'action': '_datachange_', 'changes': previousData,
|
||||||
|
'review_state': state, 'actor': user.id,
|
||||||
|
'time': DateTime(), 'comments': ''}
|
||||||
|
# Add the event to the history
|
||||||
|
histKey = self.workflow_history.keys()[0]
|
||||||
|
self.workflow_history[histKey] += (event,)
|
||||||
|
|
||||||
def goto(self, url):
|
def goto(self, url):
|
||||||
'''Brings the user to some p_url after an action has been executed.'''
|
'''Brings the user to some p_url after an action has been executed.'''
|
||||||
return self.REQUEST.RESPONSE.redirect(url)
|
return self.REQUEST.RESPONSE.redirect(url)
|
||||||
|
|
||||||
def getAppyAttribute(self, name):
|
def getAppyValue(self, name, appyType=None, useParamValue=False,value=None):
|
||||||
'''Returns method or attribute value corresponding to p_name.'''
|
'''Returns the value of field (or method) p_name for this object
|
||||||
return eval('self.%s' % name)
|
(p_self). If p_appyType (the corresponding Appy type) is provided,
|
||||||
|
it gives additional information about the way to render the value.
|
||||||
|
If p_useParamValue is True, the method uses p_value instead of the
|
||||||
|
real field value (useful for rendering a value from the object
|
||||||
|
history, for example).'''
|
||||||
|
# Which value will we use ?
|
||||||
|
if useParamValue: v = value
|
||||||
|
else: v = eval('self.%s' % name)
|
||||||
|
if not appyType: return v
|
||||||
|
if (v == None) or (v == ''): return v
|
||||||
|
vType = appyType['type']
|
||||||
|
if vType == 'Date':
|
||||||
|
res = v.strftime('%d/%m/') + str(v.year())
|
||||||
|
if appyType['format'] == 0:
|
||||||
|
res += ' %s' % v.strftime('%H:%M')
|
||||||
|
return res
|
||||||
|
elif vType == 'String':
|
||||||
|
if not v: return v
|
||||||
|
if appyType['isSelect']:
|
||||||
|
maxMult = appyType['multiplicity'][1]
|
||||||
|
t = self.translate
|
||||||
|
if (maxMult == None) or (maxMult > 1):
|
||||||
|
return [t('%s_%s_list_%s' % (self.meta_type, name, e)) \
|
||||||
|
for e in v]
|
||||||
|
else:
|
||||||
|
return t('%s_%s_list_%s' % (self.meta_type, name, v))
|
||||||
|
return v
|
||||||
|
elif vType == 'Boolean':
|
||||||
|
if v: return self.translate('yes', domain='plone')
|
||||||
|
else: return self.translate('no', domain='plone')
|
||||||
|
return v
|
||||||
|
|
||||||
def getAppyType(self, fieldName, forward=True):
|
def getAppyType(self, fieldName, forward=True, asDict=True):
|
||||||
'''Returns the Appy type corresponding to p_fieldName. If you want to
|
'''Returns the Appy type corresponding to p_fieldName. If you want to
|
||||||
get the Appy type corresponding to a backward field, set p_forward
|
get the Appy type corresponding to a backward field, set p_forward
|
||||||
to False and specify the corresponding Archetypes relationship in
|
to False and specify the corresponding Archetypes relationship in
|
||||||
|
@ -166,24 +228,29 @@ class AbstractMixin:
|
||||||
try:
|
try:
|
||||||
# If I get the attr on self instead of baseClass, I get the
|
# If I get the attr on self instead of baseClass, I get the
|
||||||
# property field that is redefined at the wrapper level.
|
# property field that is redefined at the wrapper level.
|
||||||
appyType = getattr(baseClass, fieldName)
|
res = appyType = getattr(baseClass, fieldName)
|
||||||
res = self._appy_getTypeAsDict(fieldName, appyType, baseClass)
|
if asDict:
|
||||||
|
res = self._appy_getTypeAsDict(
|
||||||
|
fieldName, appyType, baseClass)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Check for another parent
|
# Check for another parent
|
||||||
if self.wrapperClass.__bases__[0].__bases__:
|
if self.wrapperClass.__bases__[0].__bases__:
|
||||||
baseClass = self.wrapperClass.__bases__[0].__bases__[-1]
|
baseClass = self.wrapperClass.__bases__[0].__bases__[-1]
|
||||||
try:
|
try:
|
||||||
appyType = getattr(baseClass, fieldName)
|
res = appyType = getattr(baseClass, fieldName)
|
||||||
res = self._appy_getTypeAsDict(fieldName, appyType,
|
if asDict:
|
||||||
baseClass)
|
res = self._appy_getTypeAsDict(
|
||||||
|
fieldName, appyType, baseClass)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
referers = self.getProductConfig().referers
|
referers = self.getProductConfig().referers
|
||||||
for appyType, rel in referers[self.__class__.__name__]:
|
for appyType, rel in referers[self.__class__.__name__]:
|
||||||
if rel == fieldName:
|
if rel == fieldName:
|
||||||
res = appyType.__dict__
|
res = appyType
|
||||||
res['backd'] = appyType.back.__dict__
|
if asDict:
|
||||||
|
res = appyType.__dict__
|
||||||
|
res['backd'] = appyType.back.__dict__
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _appy_getRefs(self, fieldName, ploneObjects=False,
|
def _appy_getRefs(self, fieldName, ploneObjects=False,
|
||||||
|
@ -357,8 +424,7 @@ class AbstractMixin:
|
||||||
groups = {} # The already encountered groups
|
groups = {} # The already encountered groups
|
||||||
for fieldDescr in self._appy_getOrderedFields(isEdit):
|
for fieldDescr in self._appy_getOrderedFields(isEdit):
|
||||||
# Select only widgets shown on current page
|
# Select only widgets shown on current page
|
||||||
if fieldDescr.page != page:
|
if fieldDescr.page != page: continue
|
||||||
continue
|
|
||||||
# Do not take into account hidden fields and fields that can't be
|
# Do not take into account hidden fields and fields that can't be
|
||||||
# edited through the edit view
|
# edited through the edit view
|
||||||
if not self.showField(fieldDescr, isEdit): continue
|
if not self.showField(fieldDescr, isEdit): continue
|
||||||
|
@ -561,6 +627,27 @@ class AbstractMixin:
|
||||||
res = '%s_%s' % (wf.id, res)
|
res = '%s_%s' % (wf.id, res)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def hasHistory(self):
|
||||||
|
'''Has this object an history?'''
|
||||||
|
if hasattr(self.aq_base, 'workflow_history') and self.workflow_history:
|
||||||
|
key = self.workflow_history.keys()[0]
|
||||||
|
for event in self.workflow_history[key]:
|
||||||
|
if event['action'] and (event['comments'] != '_invisible_'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getHistory(self, startNumber=0, reverse=True, includeInvisible=False):
|
||||||
|
'''Returns the history for this object, sorted in reverse order (most
|
||||||
|
recent change first) if p_reverse is True.'''
|
||||||
|
batchSize = 3
|
||||||
|
key = self.workflow_history.keys()[0]
|
||||||
|
history = list(self.workflow_history[key][1:])
|
||||||
|
if not includeInvisible:
|
||||||
|
history = [e for e in history if e['comments'] != '_invisible_']
|
||||||
|
if reverse: history.reverse()
|
||||||
|
return {'events': history[startNumber:startNumber+batchSize],
|
||||||
|
'totalNumber': len(history), 'batchSize':batchSize}
|
||||||
|
|
||||||
def getComputedValue(self, appyType):
|
def getComputedValue(self, appyType):
|
||||||
'''Computes on p_self the value of the Computed field corresponding to
|
'''Computes on p_self the value of the Computed field corresponding to
|
||||||
p_appyType.'''
|
p_appyType.'''
|
||||||
|
@ -1080,7 +1167,7 @@ class AbstractMixin:
|
||||||
params = ''
|
params = ''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
for k, v in kwargs.iteritems(): params += '&%s=%s' % (k, v)
|
for k, v in kwargs.iteritems(): params += '&%s=%s' % (k, v)
|
||||||
params = params[1:]
|
if params: params = params[1:]
|
||||||
if t == 'showRef':
|
if t == 'showRef':
|
||||||
chunk = '/skyn/ajax?objectUid=%s&page=ref&' \
|
chunk = '/skyn/ajax?objectUid=%s&page=ref&' \
|
||||||
'macro=showReferenceContent&' % self.UID()
|
'macro=showReferenceContent&' % self.UID()
|
||||||
|
@ -1088,6 +1175,11 @@ class AbstractMixin:
|
||||||
if rq.has_key(startKey) and not kwargs.has_key(startKey):
|
if rq.has_key(startKey) and not kwargs.has_key(startKey):
|
||||||
params += '&%s=%s' % (startKey, rq[startKey])
|
params += '&%s=%s' % (startKey, rq[startKey])
|
||||||
return baseUrl + chunk + params
|
return baseUrl + chunk + params
|
||||||
|
elif t == 'showHistory':
|
||||||
|
chunk = '/skyn/ajax?objectUid=%s&page=macros¯o=history' % \
|
||||||
|
self.UID()
|
||||||
|
if params: params = '&' + params
|
||||||
|
return baseUrl + chunk + params
|
||||||
else: # We consider t=='view'
|
else: # We consider t=='view'
|
||||||
return baseUrl + '/skyn/view' + params
|
return baseUrl + '/skyn/view' + params
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
dummy2 python:response.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
|
dummy2 python:response.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
|
||||||
dummy3 python:response.setHeader('CacheControl', 'no-cache')">
|
dummy3 python:response.setHeader('CacheControl', 'no-cache')">
|
||||||
<tal:executeAction condition="action">
|
<tal:executeAction condition="action">
|
||||||
<tal:do define="dummy python: contextObj.getAppyAttribute('on'+action)()" omit-tag=""/>
|
<tal:do define="dummy python: contextObj.getAppyValue('on'+action)()" omit-tag=""/>
|
||||||
</tal:executeAction>
|
</tal:executeAction>
|
||||||
<metal:callMacro use-macro="python: context.get(page).macros.get(macro)"/>
|
<metal:callMacro use-macro="python: context.get(page).macros.get(macro)"/>
|
||||||
</tal:ajax>
|
</tal:ajax>
|
||||||
|
|
|
@ -12,4 +12,4 @@ else:
|
||||||
from Products.CMFCore.utils import getToolByName
|
from Products.CMFCore.utils import getToolByName
|
||||||
portal = getToolByName(obj, 'portal_url').getPortalObject()
|
portal = getToolByName(obj, 'portal_url').getPortalObject()
|
||||||
obj = portal.get('portal_%s' % obj.id.lower()) # The tool
|
obj = portal.get('portal_%s' % obj.id.lower()) # The tool
|
||||||
return obj.getAppyAttribute('on'+action)()
|
return obj.getAppyValue('on'+action)()
|
||||||
|
|
|
@ -116,15 +116,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<metal:showDate define-macro="showDateField"
|
<metal:showDate define-macro="showDateField"
|
||||||
tal:define="v python: field.getAccessor(contextObj)()">
|
tal:define="v python: contextObj.getAppyValue(field.getName(), appyType)">
|
||||||
<span tal:condition="showLabel" tal:content="label" class="appyLabel"></span>
|
<span tal:condition="showLabel" tal:content="label" class="appyLabel"></span>
|
||||||
<span tal:condition="v" tal:content="python: v.strftime('%d/%m/') + str(v.year())"></span>
|
<span tal:replace="v"></span>
|
||||||
<span tal:condition="python: v and (appyType['format'] == 0)"
|
|
||||||
tal:content="python: v.strftime('%H:%M')"></span>
|
|
||||||
</metal:showDate>
|
</metal:showDate>
|
||||||
|
|
||||||
<metal:showString define-macro="showStringField"
|
<metal:showString define-macro="showStringField"
|
||||||
tal:define="v python: field.getAccessor(contextObj)();
|
tal:define="v python: contextObj.getAppyValue(field.getName(), appyType);
|
||||||
fmt python: appyType['format'];
|
fmt python: appyType['format'];
|
||||||
maxMult python: appyType['multiplicity'][1];
|
maxMult python: appyType['multiplicity'][1];
|
||||||
severalValues python: (maxMult == None) or (maxMult > 1)">
|
severalValues python: (maxMult == None) or (maxMult > 1)">
|
||||||
|
@ -132,30 +130,12 @@
|
||||||
<span tal:condition="showLabel" tal:content="label" class="appyLabel"
|
<span tal:condition="showLabel" tal:content="label" class="appyLabel"
|
||||||
tal:attributes="class python: 'appyLabel ' + contextObj.getCssClasses(appyType, asSlave=False);
|
tal:attributes="class python: 'appyLabel ' + contextObj.getCssClasses(appyType, asSlave=False);
|
||||||
id python: v"></span>
|
id python: v"></span>
|
||||||
<tal:severalValues condition="python: v and severalValues">
|
<ul class="appyList" tal:condition="python: v and severalValues">
|
||||||
<ul class="appyList">
|
<li class="appyBullet" tal:repeat="sv v"><i tal:content="structure sv"></i></li>
|
||||||
<tal:items repeat="sv v">
|
|
||||||
<tal:select condition="appyType/isSelect">
|
|
||||||
<li class="appyBullet">
|
|
||||||
<i tal:content="python: tool.translate('%s_%s_list_%s' % (contextObj.meta_type, field.getName(), sv))"></i>
|
|
||||||
</li>
|
|
||||||
</tal:select>
|
|
||||||
<tal:string condition="not: appyType/isSelect">
|
|
||||||
<li class="appyBullet"><i tal:content="sv"></i></li>
|
|
||||||
</tal:string>
|
|
||||||
</tal:items>
|
|
||||||
</ul>
|
</ul>
|
||||||
</tal:severalValues>
|
|
||||||
<tal:singleValue condition="python: v and not severalValues">
|
<tal:singleValue condition="python: v and not severalValues">
|
||||||
<tal:select condition="appyType/isSelect">
|
<span tal:condition="python: fmt != 3" tal:replace="structure v"/>
|
||||||
<span tal:replace="python: tool.translate('%s_%s_list_%s' % (contextObj.meta_type, field.getName(), v))"/>
|
<span tal:condition="python: fmt == 3">********</span>
|
||||||
</tal:select>
|
|
||||||
<tal:noSelect condition="python: not appyType['isSelect'] and (fmt != 3)">
|
|
||||||
<span tal:replace="structure v"/>
|
|
||||||
</tal:noSelect>
|
|
||||||
<tal:password condition="python: not appyType['isSelect'] and (fmt == 3)">
|
|
||||||
********
|
|
||||||
</tal:password>
|
|
||||||
</tal:singleValue>
|
</tal:singleValue>
|
||||||
</tal:simpleString>
|
</tal:simpleString>
|
||||||
<tal:formattedString condition="python: fmt not in (0, 3)">
|
<tal:formattedString condition="python: fmt not in (0, 3)">
|
||||||
|
@ -275,7 +255,6 @@
|
||||||
|
|
||||||
<metal:fields define-macro="listFields"
|
<metal:fields define-macro="listFields"
|
||||||
tal:repeat="widgetDescr python: contextObj.getAppyFields(isEdit, pageName)">
|
tal:repeat="widgetDescr python: contextObj.getAppyFields(isEdit, pageName)">
|
||||||
|
|
||||||
<tal:displayArchetypesField condition="python: widgetDescr['widgetType'] == 'field'">
|
<tal:displayArchetypesField condition="python: widgetDescr['widgetType'] == 'field'">
|
||||||
<tal:atField condition="python: widgetDescr['page'] == pageName">
|
<tal:atField condition="python: widgetDescr['page'] == pageName">
|
||||||
<metal:field use-macro="here/skyn/macros/macros/showArchetypesField" />
|
<metal:field use-macro="here/skyn/macros/macros/showArchetypesField" />
|
||||||
|
@ -293,63 +272,65 @@
|
||||||
</tal:displayGroup>
|
</tal:displayGroup>
|
||||||
</metal:fields>
|
</metal:fields>
|
||||||
|
|
||||||
<span metal:define-macro="byline"
|
<metal:history define-macro="history"
|
||||||
tal:condition="python: site_properties.allowAnonymousViewAbout or not isAnon"
|
tal:define="startNumber request/startNumber|python:0;
|
||||||
tal:define="creator here/Creator;" class="documentByLine">
|
startNumber python: int(startNumber);
|
||||||
<tal:name tal:condition="creator"
|
historyInfo python: contextObj.getHistory(startNumber);
|
||||||
tal:define="author python:contextObj.portal_membership.getMemberInfo(creator)">
|
objs historyInfo/events;
|
||||||
<span class="documentAuthor" i18n:domain="plone" i18n:translate="label_by_author">
|
batchSize historyInfo/batchSize;
|
||||||
by <a tal:attributes="href string:${portal_url}/author/${creator}"
|
totalNumber historyInfo/totalNumber;
|
||||||
tal:content="python:author and author['fullname'] or creator"
|
ajaxHookId python:'appyHistory';
|
||||||
tal:omit-tag="not:author" i18n:name="author"/>
|
baseUrl python: contextObj.getUrl('showHistory', startNumber='**v**');
|
||||||
—
|
tool contextObj/getTool">
|
||||||
</span>
|
|
||||||
</tal:name>
|
|
||||||
<span class="documentModified">
|
|
||||||
<span i18n:translate="box_last_modified" i18n:domain="plone"/>
|
|
||||||
<span tal:replace="python:toLocalizedTime(here.ModificationDate(),long_format=1)"/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span metal:define-macro="workflowHistory" class="reviewHistory"
|
<tal:comment replace="nothing">Table containing the history</tal:comment>
|
||||||
tal:define="history contextObj/getWorkflowHistory" tal:condition="history">
|
<tal:history condition="objs">
|
||||||
<dl id="history" class="collapsible inline collapsedOnLoad">
|
<metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
|
||||||
<dt class="collapsibleHeader" i18n:translate="label_history" i18n:domain="plone">History</dt>
|
<table width="100%" class="listing nosort">
|
||||||
<dd class="collapsibleContent">
|
<tr i18n:domain="plone">
|
||||||
<table width="100%" class="listing nosort" i18n:attributes="summary summary_review_history"
|
<th i18n:translate="listingheader_action"/>
|
||||||
tal:define="review_history python:contextObj.portal_workflow.getInfoFor(contextObj, 'review_history', []);
|
<th i18n:translate="listingheader_performed_by"/>
|
||||||
review_history python:[review for review in review_history if review.get('action','')]"
|
<th i18n:translate="listingheader_date_and_time"/>
|
||||||
tal:condition="review_history">
|
<th i18n:translate="listingheader_comment"/>
|
||||||
<tr i18n:domain="plone">
|
</tr>
|
||||||
<th i18n:translate="listingheader_action"/>
|
<tal:event repeat="event objs">
|
||||||
<th i18n:translate="listingheader_performed_by"/>
|
<tr tal:define="odd repeat/event/odd;
|
||||||
<th i18n:translate="listingheader_date_and_time"/>
|
rhComments event/comments|nothing;
|
||||||
<th i18n:translate="listingheader_comment"/>
|
state event/review_state|nothing;
|
||||||
</tr>
|
isDataChange python: event['action'] == '_datachange_'"
|
||||||
<metal:block tal:define="review_history python: portal.reverseList(review_history);"
|
tal:attributes="class python:test(odd, 'even', 'odd')" valign="top">
|
||||||
tal:repeat="items review_history">
|
<td tal:condition="isDataChange" tal:content="python: tool.translate('data_change')"></td>
|
||||||
<tr tal:define="odd repeat/items/odd;
|
<td tal:condition="not: isDataChange"
|
||||||
rhComments items/comments|nothing;
|
tal:content="python: tool.translate(contextObj.getWorkflowLabel(event['action']))"
|
||||||
state items/review_state|nothing"
|
tal:attributes="class string:state-${state}"/>
|
||||||
tal:condition="python: items['action'] and (rhComments != '_invisible_')"
|
<td tal:define="actorid python:event.get('actor');
|
||||||
tal:attributes="class python:test(odd, 'even', 'odd')">
|
actor python:contextObj.portal_membership.getMemberInfo(actorid);
|
||||||
|
fullname actor/fullname|nothing;
|
||||||
<td tal:content="python: tool.translate(contextObj.getWorkflowLabel(items['action']))"
|
username actor/username|nothing"
|
||||||
tal:attributes="class string:state-${state}"/>
|
tal:content="python:fullname or username or actorid"/>
|
||||||
<td tal:define="actorid python:items.get('actor');
|
<td tal:content="python:contextObj.toLocalizedTime(event['time'],long_format=True)"/>
|
||||||
actor python:contextObj.portal_membership.getMemberInfo(actorid);
|
<td tal:condition="not: isDataChange"><tal:comment condition="rhComments" tal:content="structure rhComments"/>
|
||||||
fullname actor/fullname|nothing;
|
<tal:noComment condition="not: rhComments" i18n:translate="no_comments" i18n:domain="plone"/></td>
|
||||||
username actor/username|nothing"
|
<td tal:condition="isDataChange">
|
||||||
tal:content="python:fullname or username or actorid"/>
|
<tal:comment replace="nothing">
|
||||||
<td tal:content="python:toLocalizedTime(items['time'],long_format=True)"/>
|
Display the previous values of the fields whose value were modified in this change.</tal:comment>
|
||||||
<td><tal:comment condition="rhComments" tal:content="structure rhComments"/>
|
<table class="appyChanges" width="100%">
|
||||||
<tal:noComment condition="not: rhComments" i18n:translate="no_comments" i18n:domain="plone"/></td>
|
<tr>
|
||||||
</tr>
|
<th tal:content="python: tool.translate('modified_field')"></th>
|
||||||
</metal:block>
|
<th tal:content="python: tool.translate('previous_value')"></th>
|
||||||
</table>
|
</tr>
|
||||||
</dd>
|
<tr tal:repeat="change event/changes/items">
|
||||||
</dl>
|
<td tal:content="python: tool.translate(change[1][1])"></td>
|
||||||
</span>
|
<td tal:define="appyType python:contextObj.getAppyType(change[0])"
|
||||||
|
tal:content="python: contextObj.getAppyValue(change[0], appyType, True, change[1][0])"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tal:event>
|
||||||
|
</table>
|
||||||
|
</tal:history>
|
||||||
|
</metal:history>
|
||||||
|
|
||||||
<div metal:define-macro="pagePrologue">
|
<div metal:define-macro="pagePrologue">
|
||||||
<tal:comment replace="nothing">Global elements used in every page.</tal:comment>
|
<tal:comment replace="nothing">Global elements used in every page.</tal:comment>
|
||||||
|
@ -468,6 +449,32 @@
|
||||||
f.submit();
|
f.submit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function toggleCookie(cookieId) {
|
||||||
|
// What is the state of this boolean (expanded/collapsed) cookie?
|
||||||
|
var state = readCookie(cookieId);
|
||||||
|
if ((state != 'collapsed') && (state != 'expanded')) {
|
||||||
|
// No cookie yet, create it.
|
||||||
|
createCookie(cookieId, 'collapsed');
|
||||||
|
state = 'collapsed';
|
||||||
|
}
|
||||||
|
var hook = document.getElementById(cookieId); // The hook is the part of
|
||||||
|
// the HTML document that needs to be shown or hidden.
|
||||||
|
var displayValue = 'none';
|
||||||
|
var newState = 'collapsed';
|
||||||
|
var imgSrc = 'skyn/expand.gif';
|
||||||
|
if (state == 'collapsed') {
|
||||||
|
// Show the HTML zone
|
||||||
|
displayValue = 'block';
|
||||||
|
imgSrc = 'skyn/collapse.gif';
|
||||||
|
newState = 'expanded';
|
||||||
|
}
|
||||||
|
// Update the corresponding HTML element
|
||||||
|
hook.style.display = displayValue;
|
||||||
|
var img = document.getElementById(cookieId + '_img');
|
||||||
|
img.src = imgSrc;
|
||||||
|
// Inverse the cookie value
|
||||||
|
createCookie(cookieId, newState);
|
||||||
|
}
|
||||||
-->
|
-->
|
||||||
</script>
|
</script>
|
||||||
<tal:comment replace="nothing">Global form for deleting an object</tal:comment>
|
<tal:comment replace="nothing">Global form for deleting an object</tal:comment>
|
||||||
|
@ -479,7 +486,10 @@
|
||||||
|
|
||||||
<div metal:define-macro="showPageHeader"
|
<div metal:define-macro="showPageHeader"
|
||||||
tal:define="appyPages python: contextObj.getAppyPages(phase);
|
tal:define="appyPages python: contextObj.getAppyPages(phase);
|
||||||
showCommonInfo python: not isEdit"
|
showCommonInfo python: not isEdit;
|
||||||
|
hasHistory contextObj/hasHistory;
|
||||||
|
historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded';
|
||||||
|
creator contextObj/Creator"
|
||||||
tal:condition="not: contextObj/isTemporary">
|
tal:condition="not: contextObj/isTemporary">
|
||||||
|
|
||||||
<tal:comment replace="nothing">Information that is common to all tabs (object title, state, etc)</tal:comment>
|
<tal:comment replace="nothing">Information that is common to all tabs (object title, state, etc)</tal:comment>
|
||||||
|
@ -509,15 +519,47 @@
|
||||||
<td colspan="2" class="discreet" tal:content="descrLabel"/>
|
<td colspan="2" class="discreet" tal:content="descrLabel"/>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td class="documentByLine">
|
||||||
<metal:byLine use-macro="here/skyn/macros/macros/byline"/>
|
<tal:comment replace="nothing">Creator and last modification date</tal:comment>
|
||||||
<tal:showWorkflow condition="showWorkflow">
|
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
|
||||||
<metal:workflowHistory use-macro="here/skyn/macros/macros/workflowHistory"/>
|
<tal:accessHistory condition="hasHistory">
|
||||||
</tal:showWorkflow>
|
<img align="left" style="cursor:pointer" onClick="javascript:toggleCookie('appyHistory')"
|
||||||
|
tal:attributes="src python:test(historyExpanded, 'skyn/collapse.gif', 'skyn/expand.gif');"
|
||||||
|
id="appyHistory_img"/>
|
||||||
|
<span i18n:translate="label_history" i18n:domain="plone" class="appyHistory"></span>
|
||||||
|
</tal:accessHistory>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Show document creator</tal:comment>
|
||||||
|
<tal:creator condition="creator"
|
||||||
|
define="author python:contextObj.portal_membership.getMemberInfo(creator)">
|
||||||
|
<span class="documentAuthor" i18n:domain="plone" i18n:translate="label_by_author">
|
||||||
|
by <a tal:attributes="href string:${portal_url}/author/${creator}"
|
||||||
|
tal:content="python:author and author['fullname'] or creator"
|
||||||
|
tal:omit-tag="not:author" i18n:name="author"/>
|
||||||
|
—
|
||||||
|
</span>
|
||||||
|
</tal:creator>
|
||||||
|
<tal:comment replace="nothing">Show last modification date</tal:comment>
|
||||||
|
<span i18n:translate="box_last_modified" i18n:domain="plone"></span>
|
||||||
|
<span tal:replace="python:contextObj.toLocalizedTime(contextObj.ModificationDate(),long_format=1)"></span>
|
||||||
</td>
|
</td>
|
||||||
<td valign="top"><metal:pod use-macro="here/skyn/macros/macros/listPodTemplates"/>
|
<td valign="top"><metal:pod use-macro="here/skyn/macros/macros/listPodTemplates"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tal:comment replace="nothing">Object history</tal:comment>
|
||||||
|
<tr tal:condition="hasHistory">
|
||||||
|
<td colspan="2">
|
||||||
|
<span id="appyHistory"
|
||||||
|
tal:attributes="style python:test(historyExpanded, 'display:block', 'display:none')">
|
||||||
|
<div tal:define="ajaxHookId python: contextObj.UID() + '_history';
|
||||||
|
ajaxUrl python: contextObj.getUrl('showHistory')"
|
||||||
|
tal:attributes="id ajaxHookId">
|
||||||
|
<script language="javascript" tal:content="python: 'askAjaxChunk(\'%s\',\'%s\')' % (ajaxHookId, ajaxUrl)">
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Workflow-related information and actions</tal:comment>
|
<tal:comment replace="nothing">Workflow-related information and actions</tal:comment>
|
||||||
<tr tal:condition="python: showWorkflow and contextObj.getWorkflowLabel()">
|
<tr tal:condition="python: showWorkflow and contextObj.getWorkflowLabel()">
|
||||||
|
@ -819,35 +861,6 @@
|
||||||
tal:define="queryUrl python: '%s/skyn/query' % appFolder.absolute_url();
|
tal:define="queryUrl python: '%s/skyn/query' % appFolder.absolute_url();
|
||||||
currentSearch request/search|nothing;
|
currentSearch request/search|nothing;
|
||||||
currentType request/type_name|nothing;">
|
currentType request/type_name|nothing;">
|
||||||
<script language="javascript">
|
|
||||||
<!--
|
|
||||||
function toggleSearchGroup(groupId) {
|
|
||||||
// What is the state of this toggle?
|
|
||||||
var state = readCookie(groupId);
|
|
||||||
if ((state != 'collapsed') && (state != 'expanded')) {
|
|
||||||
// No cookie yet, create it.
|
|
||||||
createCookie(groupId, 'collapsed');
|
|
||||||
state = 'collapsed';
|
|
||||||
}
|
|
||||||
var group = document.getElementById(groupId);
|
|
||||||
var displayValue = 'none';
|
|
||||||
var newState = 'collapsed';
|
|
||||||
var imgSrc = 'skyn/expand.gif';
|
|
||||||
if (state == 'collapsed') {
|
|
||||||
// Expand the group
|
|
||||||
displayValue = 'block';
|
|
||||||
imgSrc = 'skyn/collapse.gif';
|
|
||||||
newState = 'expanded';
|
|
||||||
}
|
|
||||||
// Update group visibility and img
|
|
||||||
group.style.display = displayValue;
|
|
||||||
var img = document.getElementById(groupId + '_img');
|
|
||||||
img.src = imgSrc;
|
|
||||||
// Inverse the cookie value
|
|
||||||
createCookie(groupId, newState);
|
|
||||||
}
|
|
||||||
-->
|
|
||||||
</script>
|
|
||||||
<tal:comment replace="nothing">Portlet title, with link to tool.</tal:comment>
|
<tal:comment replace="nothing">Portlet title, with link to tool.</tal:comment>
|
||||||
<dt class="portletHeader">
|
<dt class="portletHeader">
|
||||||
<tal:comment replace="nothing">If there is only one flavour, clicking on the portlet
|
<tal:comment replace="nothing">If there is only one flavour, clicking on the portlet
|
||||||
|
@ -907,12 +920,12 @@
|
||||||
<img align="left" style="cursor:pointer"
|
<img align="left" style="cursor:pointer"
|
||||||
tal:attributes="id python: '%s_img' % group['labelId'];
|
tal:attributes="id python: '%s_img' % group['labelId'];
|
||||||
src python:test(expanded, 'skyn/collapse.gif', 'skyn/expand.gif');
|
src python:test(expanded, 'skyn/collapse.gif', 'skyn/expand.gif');
|
||||||
onClick python:'javascript:toggleSearchGroup(\'%s\')' % group['labelId']"/>
|
onClick python:'javascript:toggleCookie(\'%s\')' % group['labelId']"/>
|
||||||
<span tal:replace="group/label"/>
|
<span tal:replace="group/label"/>
|
||||||
</dt>
|
</dt>
|
||||||
<tal:comment replace="nothing">Group searches</tal:comment>
|
<tal:comment replace="nothing">Group searches</tal:comment>
|
||||||
<span tal:attributes="id group/labelId;
|
<span tal:attributes="id group/labelId;
|
||||||
style python:test(expanded, 'display:block', 'display:none')">
|
style python:test(expanded, 'display:block', 'display:none')">
|
||||||
<dt class="portletAppyItem portletSearch portletGroupItem" tal:repeat="search group/searches">
|
<dt class="portletAppyItem portletSearch portletGroupItem" tal:repeat="search group/searches">
|
||||||
<a tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s&search=%s' % (queryUrl, rootClass, flavourNumber, search['name']);
|
<a tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s&search=%s' % (queryUrl, rootClass, flavourNumber, search['name']);
|
||||||
title search/descr;
|
title search/descr;
|
||||||
|
|
|
@ -27,10 +27,10 @@
|
||||||
phase request/phase|phaseInfo/name;
|
phase request/phase|phaseInfo/name;
|
||||||
pageName python: contextObj.getAppyPage(isEdit, phaseInfo);
|
pageName python: contextObj.getAppyPage(isEdit, phaseInfo);
|
||||||
showWorkflow python: flavour.getAttr('showWorkflowFor' + contextObj.meta_type)">
|
showWorkflow python: flavour.getAttr('showWorkflowFor' + contextObj.meta_type)">
|
||||||
<div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
|
<metal:prologue use-macro="here/skyn/macros/macros/pagePrologue"/>
|
||||||
<div metal:use-macro="here/skyn/macros/macros/showPageHeader"/>
|
<metal:header use-macro="here/skyn/macros/macros/showPageHeader"/>
|
||||||
<div metal:use-macro="here/skyn/macros/macros/listFields" />
|
<metal:fields use-macro="here/skyn/macros/macros/listFields" />
|
||||||
<div metal:use-macro="here/skyn/macros/macros/showPageFooter"/>
|
<metal:footer use-macro="here/skyn/macros/macros/showPageFooter"/>
|
||||||
</metal:fill>
|
</metal:fill>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
tal:define="tool python: context.<!toolInstanceName!>"
|
tal:define="tool python: context.<!toolInstanceName!>"
|
||||||
tal:condition="tool/showPortlet">
|
tal:condition="tool/showPortlet">
|
||||||
<metal:block metal:use-macro="here/global_defines/macros/defines" />
|
<metal:block metal:use-macro="here/global_defines/macros/defines" />
|
||||||
|
<metal:prologue use-macro="here/skyn/macros/macros/pagePrologue"/>
|
||||||
<dl tal:define="rootClasses tool/getRootClasses;
|
<dl tal:define="rootClasses tool/getRootClasses;
|
||||||
appName string:<!applicationName!>;
|
appName string:<!applicationName!>;
|
||||||
appFolder tool/getAppFolder;
|
appFolder tool/getAppFolder;
|
||||||
|
|
|
@ -72,6 +72,31 @@
|
||||||
padding: 0.1em 1em 0.1em 1.3em;
|
padding: 0.1em 1em 0.1em 1.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.appyChanges th {
|
||||||
|
font-style: italic;
|
||||||
|
background-color: transparent;
|
||||||
|
border-bottom: 1px dashed #8CACBB;
|
||||||
|
border-top: 0 none transparent;
|
||||||
|
border-left: 0 none transparent;
|
||||||
|
border-right: 0 none transparent;
|
||||||
|
padding: 0.1em 0.1em 0.1em 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appyChanges td {
|
||||||
|
padding: 0.1em 0.1em 0.1em 0.1em !important;
|
||||||
|
border-right: 0 none transparent !important;
|
||||||
|
border-top: 0 none transparent;
|
||||||
|
border-left: 0 none transparent;
|
||||||
|
border-right: 0 none transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appyHistory {
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-weight: bold;
|
||||||
|
color: black;
|
||||||
|
font-size: 105%;
|
||||||
|
}
|
||||||
|
|
||||||
/* stepxx classes are used for displaying status of a phase or state. */
|
/* stepxx classes are used for displaying status of a phase or state. */
|
||||||
.stepDone {
|
.stepDone {
|
||||||
background-color: #cde2a7;
|
background-color: #cde2a7;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
developer the real classes used by the underlying web framework.'''
|
developer the real classes used by the underlying web framework.'''
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import time, os.path, mimetypes, unicodedata
|
import time, os.path, mimetypes, unicodedata, random
|
||||||
from appy.gen import Search
|
from appy.gen import Search
|
||||||
from appy.gen.utils import sequenceTypes
|
from appy.gen.utils import sequenceTypes
|
||||||
from appy.shared.utils import getOsTempFolder
|
from appy.shared.utils import getOsTempFolder
|
||||||
|
@ -153,7 +153,8 @@ class AbstractWrapper:
|
||||||
objId = kwargs['id']
|
objId = kwargs['id']
|
||||||
del kwargs['id']
|
del kwargs['id']
|
||||||
else:
|
else:
|
||||||
objId = '%s.%f' % (idPrefix, time.time())
|
objId = '%s.%f.%s' % (idPrefix, time.time(),
|
||||||
|
str(random.random()).split('.')[1])
|
||||||
# Determine if object must be created from external data
|
# Determine if object must be created from external data
|
||||||
externalData = None
|
externalData = None
|
||||||
if kwargs.has_key('_data'):
|
if kwargs.has_key('_data'):
|
||||||
|
|
|
@ -59,6 +59,9 @@ class PoMessage:
|
||||||
IMPORT_DONE = 'Import terminated successfully.'
|
IMPORT_DONE = 'Import terminated successfully.'
|
||||||
WORKFLOW_COMMENT = 'Optional comment'
|
WORKFLOW_COMMENT = 'Optional comment'
|
||||||
WORKFLOW_STATE = 'state'
|
WORKFLOW_STATE = 'state'
|
||||||
|
DATA_CHANGE = 'Data change'
|
||||||
|
MODIFIED_FIELD = 'Modified field'
|
||||||
|
PREVIOUS_VALUE = 'Previous value'
|
||||||
PHASE = 'phase'
|
PHASE = 'phase'
|
||||||
ROOT_TYPE = 'type'
|
ROOT_TYPE = 'type'
|
||||||
CHOOSE_A_VALUE = ' - '
|
CHOOSE_A_VALUE = ' - '
|
||||||
|
|
|
@ -8,15 +8,21 @@ from appy.gen.utils import sequenceTypes
|
||||||
|
|
||||||
class SapError(Exception): pass
|
class SapError(Exception): pass
|
||||||
SAP_MODULE_ERROR = 'Module pysap was not found (you can get it at ' \
|
SAP_MODULE_ERROR = 'Module pysap was not found (you can get it at ' \
|
||||||
'http://pysaprfc.sourceforge.net)'
|
'http://pysaprfc.sourceforge.net)'
|
||||||
SAP_CONNECT_ERROR = 'Error while connecting to SAP (conn_string: %s). %s'
|
SAP_CONNECT_ERROR = 'Error while connecting to SAP (conn_string: %s). %s'
|
||||||
SAP_FUNCTION_ERROR = 'Error while calling function "%s". %s'
|
SAP_FUNCTION_ERROR = 'Error while calling function "%s". %s'
|
||||||
SAP_DISCONNECT_ERROR = 'Error while disconnecting from SAP. %s'
|
SAP_DISCONNECT_ERROR = 'Error while disconnecting from SAP. %s'
|
||||||
SAP_TABLE_PARAM_ERROR = 'Param "%s" does not correspond to a valid table ' \
|
SAP_TABLE_PARAM_ERROR = 'Param "%s" does not correspond to a valid table ' \
|
||||||
'parameter for function "%s".'
|
'parameter for function "%s".'
|
||||||
|
SAP_STRUCT_ELEM_NOT_FOUND = 'Structure used by parameter "%s" does not define '\
|
||||||
|
'an attribute named "%s."'
|
||||||
|
SAP_STRING_REQUIRED = 'Type mismatch for attribute "%s" used in parameter ' \
|
||||||
|
'"%s": a string value is expected (SAP type is %s).'
|
||||||
|
SAP_STRING_OVERFLOW = 'A string value for attribute "%s" used in parameter ' \
|
||||||
|
'"%s" is too long (SAP type is %s).'
|
||||||
SAP_FUNCTION_NOT_FOUND = 'Function "%s" does not exist.'
|
SAP_FUNCTION_NOT_FOUND = 'Function "%s" does not exist.'
|
||||||
SAP_FUNCTION_INFO_ERROR = 'Error while asking information about function ' \
|
SAP_FUNCTION_INFO_ERROR = 'Error while asking information about function ' \
|
||||||
'"%s". %s'
|
'"%s". %s'
|
||||||
SAP_GROUP_NOT_FOUND = 'Group of functions "%s" does not exist or is empty.'
|
SAP_GROUP_NOT_FOUND = 'Group of functions "%s" does not exist or is empty.'
|
||||||
|
|
||||||
# Is the pysap module present or not ?
|
# Is the pysap module present or not ?
|
||||||
|
@ -52,6 +58,32 @@ class Sap:
|
||||||
connNoPasswd = params[:params.index('PASSWD')] + 'PASSWD=********'
|
connNoPasswd = params[:params.index('PASSWD')] + 'PASSWD=********'
|
||||||
raise SapError(SAP_CONNECT_ERROR % (connNoPasswd, str(se)))
|
raise SapError(SAP_CONNECT_ERROR % (connNoPasswd, str(se)))
|
||||||
|
|
||||||
|
def createStructure(self, structDef, userData, paramName):
|
||||||
|
'''Create a struct corresponding to SAP/C structure definition
|
||||||
|
p_structDef and fills it with dict p_userData.'''
|
||||||
|
res = structDef()
|
||||||
|
for name, value in userData.iteritems():
|
||||||
|
if name not in structDef._sfield_names_:
|
||||||
|
raise SapError(SAP_STRUCT_ELEM_NOT_FOUND % (paramName, name))
|
||||||
|
sapType = structDef._sfield_sap_types_[name]
|
||||||
|
# Check if the value is valid according to the required type
|
||||||
|
if sapType[0] == 'C':
|
||||||
|
sType = '%s%d' % (sapType[0], sapType[1])
|
||||||
|
# "None" value is tolerated.
|
||||||
|
if value == None: value = ''
|
||||||
|
if not isinstance(value, basestring):
|
||||||
|
raise SapError(
|
||||||
|
SAP_STRING_REQUIRED % (name, paramName, sType))
|
||||||
|
if len(value) > sapType[1]:
|
||||||
|
raise SapError(
|
||||||
|
SAP_STRING_OVERFLOW % (name, paramName, sType))
|
||||||
|
# Left-fill the string with blanks.
|
||||||
|
v = value.ljust(sapType[1])
|
||||||
|
else:
|
||||||
|
v = value
|
||||||
|
res[name.lower()] = v
|
||||||
|
return res
|
||||||
|
|
||||||
def call(self, functionName, **params):
|
def call(self, functionName, **params):
|
||||||
'''Calls a function on the SAP server.'''
|
'''Calls a function on the SAP server.'''
|
||||||
try:
|
try:
|
||||||
|
@ -60,8 +92,8 @@ class Sap:
|
||||||
for name, value in params.iteritems():
|
for name, value in params.iteritems():
|
||||||
if type(value) == dict:
|
if type(value) == dict:
|
||||||
# The param corresponds to a SAP/C "struct"
|
# The param corresponds to a SAP/C "struct"
|
||||||
v = self.sap.get_structure(name)()
|
v = self.createStructure(
|
||||||
v.from_dict(value)
|
self.sap.get_structure(name),value, name)
|
||||||
elif type(value) in sequenceTypes:
|
elif type(value) in sequenceTypes:
|
||||||
# The param must be a SAP/C "table" (a list of structs)
|
# The param must be a SAP/C "table" (a list of structs)
|
||||||
# Retrieve the name of the struct type related to this
|
# Retrieve the name of the struct type related to this
|
||||||
|
@ -78,8 +110,7 @@ class Sap:
|
||||||
SAP_TABLE_PARAM_ERROR % (name, functionName))
|
SAP_TABLE_PARAM_ERROR % (name, functionName))
|
||||||
v = self.sap.get_table(tableTypeName)
|
v = self.sap.get_table(tableTypeName)
|
||||||
for dValue in value:
|
for dValue in value:
|
||||||
v.append_from_dict(dValue)
|
v.append(self.createStructure(v.struc, dValue, name))
|
||||||
#v = v.handle
|
|
||||||
else:
|
else:
|
||||||
v = value
|
v = value
|
||||||
function[name] = v
|
function[name] = v
|
||||||
|
|
Loading…
Reference in a new issue