Improved SAP interface and added historization of fields.
This commit is contained in:
		
							parent
							
								
									b888f8149b
								
							
						
					
					
						commit
						d320a369c9
					
				
					 13 changed files with 362 additions and 182 deletions
				
			
		| 
						 | 
				
			
			@ -47,7 +47,7 @@ class Type:
 | 
			
		|||
    def __init__(self, validator, multiplicity, index, default, optional,
 | 
			
		||||
                 editDefault, show, page, group, move, indexed, searchable,
 | 
			
		||||
                 specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                 height, master, masterValue, focus):
 | 
			
		||||
                 height, master, masterValue, focus, historized):
 | 
			
		||||
        # The validator restricts which values may be defined. It can be an
 | 
			
		||||
        # interval (1,None), a list of string values ['choice1', 'choice2'],
 | 
			
		||||
        # a regular expression, a custom function, a Selection instance, etc.
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +107,9 @@ class Type:
 | 
			
		|||
        # If a field must retain attention in a particular way, set focus=True.
 | 
			
		||||
        # It will be rendered in a special way.
 | 
			
		||||
        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.type = self.__class__.__name__
 | 
			
		||||
        self.pythonType = None # The True corresponding Python type
 | 
			
		||||
| 
						 | 
				
			
			@ -128,11 +131,11 @@ class Integer(Type):
 | 
			
		|||
                 page='main', group=None, move=0, indexed=False,
 | 
			
		||||
                 searchable=False, specificReadPermission=False,
 | 
			
		||||
                 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,
 | 
			
		||||
                      editDefault, show, page, group, move, indexed, searchable,
 | 
			
		||||
                      specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                      height, master, masterValue, focus)
 | 
			
		||||
                      height, master, masterValue, focus, historized)
 | 
			
		||||
        self.pythonType = long
 | 
			
		||||
 | 
			
		||||
class Float(Type):
 | 
			
		||||
| 
						 | 
				
			
			@ -141,11 +144,11 @@ class Float(Type):
 | 
			
		|||
                 page='main', group=None, move=0, indexed=False,
 | 
			
		||||
                 searchable=False, specificReadPermission=False,
 | 
			
		||||
                 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,
 | 
			
		||||
                      editDefault, show, page, group, move, indexed, False,
 | 
			
		||||
                      specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                      height, master, masterValue, focus)
 | 
			
		||||
                      height, master, masterValue, focus, historized)
 | 
			
		||||
        self.pythonType = float
 | 
			
		||||
 | 
			
		||||
class String(Type):
 | 
			
		||||
| 
						 | 
				
			
			@ -249,11 +252,11 @@ class String(Type):
 | 
			
		|||
                 show=True, page='main', group=None, move=0, indexed=False,
 | 
			
		||||
                 searchable=False, specificReadPermission=False,
 | 
			
		||||
                 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,
 | 
			
		||||
                      editDefault, show, page, group, move, indexed, searchable,
 | 
			
		||||
                      specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                      height, master, masterValue, focus)
 | 
			
		||||
                      height, master, masterValue, focus, historized)
 | 
			
		||||
        self.format = format
 | 
			
		||||
        self.isSelect = self.isSelection()
 | 
			
		||||
    def isSelection(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -276,11 +279,11 @@ class Boolean(Type):
 | 
			
		|||
                 page='main', group=None, move=0, indexed=False,
 | 
			
		||||
                 searchable=False, specificReadPermission=False,
 | 
			
		||||
                 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,
 | 
			
		||||
                      editDefault, show, page, group, move, indexed, searchable,
 | 
			
		||||
                      specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                      height, master, masterValue, focus)
 | 
			
		||||
                      height, master, masterValue, focus, historized)
 | 
			
		||||
        self.pythonType = bool
 | 
			
		||||
 | 
			
		||||
class Date(Type):
 | 
			
		||||
| 
						 | 
				
			
			@ -294,11 +297,11 @@ class Date(Type):
 | 
			
		|||
                 group=None, move=0, indexed=False, searchable=False,
 | 
			
		||||
                 specificReadPermission=False, specificWritePermission=False,
 | 
			
		||||
                 width=None, height=None, master=None, masterValue=None,
 | 
			
		||||
                 focus=False):
 | 
			
		||||
                 focus=False, historized=False):
 | 
			
		||||
        Type.__init__(self, validator, multiplicity, index, default, optional,
 | 
			
		||||
                      editDefault, show, page, group, move, indexed, searchable,
 | 
			
		||||
                      specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                      height, master, masterValue, focus)
 | 
			
		||||
                      height, master, masterValue, focus, historized)
 | 
			
		||||
        self.format = format
 | 
			
		||||
        self.startYear = startYear
 | 
			
		||||
        self.endYear = endYear
 | 
			
		||||
| 
						 | 
				
			
			@ -309,11 +312,12 @@ class File(Type):
 | 
			
		|||
                 page='main', group=None, move=0, indexed=False,
 | 
			
		||||
                 searchable=False, specificReadPermission=False,
 | 
			
		||||
                 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,
 | 
			
		||||
                      editDefault, show, page, group, move, indexed, False,
 | 
			
		||||
                      specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                      height, master, masterValue, focus)
 | 
			
		||||
                      height, master, masterValue, focus, historized)
 | 
			
		||||
        self.isImage = isImage
 | 
			
		||||
 | 
			
		||||
class Ref(Type):
 | 
			
		||||
| 
						 | 
				
			
			@ -325,11 +329,11 @@ class Ref(Type):
 | 
			
		|||
                 maxPerPage=30, move=0, indexed=False, searchable=False,
 | 
			
		||||
                 specificReadPermission=False, specificWritePermission=False,
 | 
			
		||||
                 width=None, height=None, master=None, masterValue=None,
 | 
			
		||||
                 focus=False):
 | 
			
		||||
                 focus=False, historized=False):
 | 
			
		||||
        Type.__init__(self, validator, multiplicity, index, default, optional,
 | 
			
		||||
                      editDefault, show, page, group, move, indexed, False,
 | 
			
		||||
                      specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                      height, master, masterValue, focus)
 | 
			
		||||
                      height, master, masterValue, focus, historized)
 | 
			
		||||
        self.klass = klass
 | 
			
		||||
        self.attribute = attribute
 | 
			
		||||
        self.add = add # May the user add new objects through this ref ?
 | 
			
		||||
| 
						 | 
				
			
			@ -356,11 +360,11 @@ class Computed(Type):
 | 
			
		|||
                 searchable=False, specificReadPermission=False,
 | 
			
		||||
                 specificWritePermission=False, width=None, height=None,
 | 
			
		||||
                 method=None, plainText=True, master=None, masterValue=None,
 | 
			
		||||
                 focus=False):
 | 
			
		||||
                 focus=False, historized=False):
 | 
			
		||||
        Type.__init__(self, None, multiplicity, index, default, optional,
 | 
			
		||||
                      False, show, page, group, move, indexed, False,
 | 
			
		||||
                      specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                      height, master, masterValue, focus)
 | 
			
		||||
                      height, master, masterValue, focus, historized)
 | 
			
		||||
        self.method = method # The method used for computing the field value
 | 
			
		||||
        self.plainText = plainText # Does field computation produce pain text
 | 
			
		||||
        # or XHTML?
 | 
			
		||||
| 
						 | 
				
			
			@ -376,11 +380,11 @@ class Action(Type):
 | 
			
		|||
                 searchable=False, specificReadPermission=False,
 | 
			
		||||
                 specificWritePermission=False, width=None, height=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,
 | 
			
		||||
                      False, show, page, group, move, indexed, False,
 | 
			
		||||
                      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.result = result # 'computation' means that the action will simply
 | 
			
		||||
        # 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,
 | 
			
		||||
                 searchable=False, specificReadPermission=False,
 | 
			
		||||
                 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,
 | 
			
		||||
                      False, show, page, group, move, indexed, False,
 | 
			
		||||
                      specificReadPermission, specificWritePermission, width,
 | 
			
		||||
                      height, master, masterValue, focus)
 | 
			
		||||
                      height, master, masterValue, focus, historized)
 | 
			
		||||
 | 
			
		||||
# Workflow-specific types ------------------------------------------------------
 | 
			
		||||
class State:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -91,6 +91,9 @@ class Generator(AbstractGenerator):
 | 
			
		|||
        poMsg = msg(app, '', app); poMsg.produceNiceDefault()
 | 
			
		||||
        self.labels += [poMsg,
 | 
			
		||||
            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('root_type', '', msg.ROOT_TYPE),
 | 
			
		||||
            msg('workflow_comment', '', msg.WORKFLOW_COMMENT),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,7 +79,14 @@ def afterTest(test):
 | 
			
		|||
    exec 'from Products.%s import numberOfExecutedTests' % appName
 | 
			
		||||
    if cov and (numberOfExecutedTests == totalNumberOfTests):
 | 
			
		||||
        cov.stop()
 | 
			
		||||
        # Dumps the coverage report
 | 
			
		||||
        appModules = test.getNonEmptySubModules(appName)
 | 
			
		||||
        # Dumps the coverage report
 | 
			
		||||
        # HTML version
 | 
			
		||||
        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:
 | 
			
		||||
            obj = self.portal_factory.doCreate(self, self.id) # portal_factory
 | 
			
		||||
            # creates the final object from the temp object.
 | 
			
		||||
        if created and (obj._appy_meta_type == 'tool'):
 | 
			
		||||
            # We are in the special case where the tool itself is being created.
 | 
			
		||||
            # In this case, we do not process form data.
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            obj.processForm()
 | 
			
		||||
 | 
			
		||||
        # Get the current language and put it in the request
 | 
			
		||||
        #if rq.form.has_key('current_lang'):
 | 
			
		||||
        #    rq.form['language'] = rq.form.get('current_lang')
 | 
			
		||||
        previousData = None
 | 
			
		||||
        if not created: previousData = self.rememberPreviousData()
 | 
			
		||||
        # We do not process form data (=real update on the object) if the tool
 | 
			
		||||
        # itself is being created.
 | 
			
		||||
        if obj._appy_meta_type != 'tool': obj.processForm()
 | 
			
		||||
        if previousData:
 | 
			
		||||
            # Keep in history potential changes on historized fields
 | 
			
		||||
            self.historizeData(previousData)
 | 
			
		||||
 | 
			
		||||
        # Manage references
 | 
			
		||||
        obj._appy_manageRefs(created)
 | 
			
		||||
| 
						 | 
				
			
			@ -145,15 +143,79 @@ class AbstractMixin:
 | 
			
		|||
        self.plone_utils.addPortalMessage(msg)
 | 
			
		||||
        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):
 | 
			
		||||
        '''Brings the user to some p_url after an action has been executed.'''
 | 
			
		||||
        return self.REQUEST.RESPONSE.redirect(url)
 | 
			
		||||
 | 
			
		||||
    def getAppyAttribute(self, name):
 | 
			
		||||
        '''Returns method or attribute value corresponding to p_name.'''
 | 
			
		||||
        return eval('self.%s' % name)
 | 
			
		||||
    def getAppyValue(self, name, appyType=None, useParamValue=False,value=None):
 | 
			
		||||
        '''Returns the value of field (or method) p_name for this object
 | 
			
		||||
           (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
 | 
			
		||||
           get the Appy type corresponding to a backward field, set p_forward
 | 
			
		||||
           to False and specify the corresponding Archetypes relationship in
 | 
			
		||||
| 
						 | 
				
			
			@ -166,22 +228,27 @@ class AbstractMixin:
 | 
			
		|||
                try:
 | 
			
		||||
                    # If I get the attr on self instead of baseClass, I get the
 | 
			
		||||
                    # property field that is redefined at the wrapper level.
 | 
			
		||||
                    appyType = getattr(baseClass, fieldName)
 | 
			
		||||
                    res = self._appy_getTypeAsDict(fieldName, appyType, baseClass)
 | 
			
		||||
                    res = appyType = getattr(baseClass, fieldName)
 | 
			
		||||
                    if asDict:
 | 
			
		||||
                        res = self._appy_getTypeAsDict(
 | 
			
		||||
                            fieldName, appyType, baseClass)
 | 
			
		||||
                except AttributeError:
 | 
			
		||||
                    # Check for another parent
 | 
			
		||||
                    if self.wrapperClass.__bases__[0].__bases__:
 | 
			
		||||
                        baseClass = self.wrapperClass.__bases__[0].__bases__[-1]
 | 
			
		||||
                        try:
 | 
			
		||||
                            appyType = getattr(baseClass, fieldName)
 | 
			
		||||
                            res = self._appy_getTypeAsDict(fieldName, appyType,
 | 
			
		||||
                                                           baseClass)
 | 
			
		||||
                            res = appyType = getattr(baseClass, fieldName)
 | 
			
		||||
                            if asDict:
 | 
			
		||||
                                res = self._appy_getTypeAsDict(
 | 
			
		||||
                                    fieldName, appyType, baseClass)
 | 
			
		||||
                        except AttributeError:
 | 
			
		||||
                            pass
 | 
			
		||||
        else:
 | 
			
		||||
            referers = self.getProductConfig().referers
 | 
			
		||||
            for appyType, rel in referers[self.__class__.__name__]:
 | 
			
		||||
                if rel == fieldName:
 | 
			
		||||
                    res = appyType
 | 
			
		||||
                    if asDict:
 | 
			
		||||
                        res = appyType.__dict__
 | 
			
		||||
                        res['backd'] = appyType.back.__dict__
 | 
			
		||||
        return res
 | 
			
		||||
| 
						 | 
				
			
			@ -357,8 +424,7 @@ class AbstractMixin:
 | 
			
		|||
        groups = {} # The already encountered groups
 | 
			
		||||
        for fieldDescr in self._appy_getOrderedFields(isEdit):
 | 
			
		||||
            # Select only widgets shown on current page
 | 
			
		||||
            if fieldDescr.page != page:
 | 
			
		||||
                continue
 | 
			
		||||
            if fieldDescr.page != page: continue
 | 
			
		||||
            # Do not take into account hidden fields and fields that can't be
 | 
			
		||||
            # edited through the edit view
 | 
			
		||||
            if not self.showField(fieldDescr, isEdit): continue
 | 
			
		||||
| 
						 | 
				
			
			@ -561,6 +627,27 @@ class AbstractMixin:
 | 
			
		|||
                res = '%s_%s' % (wf.id, 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):
 | 
			
		||||
        '''Computes on p_self the value of the Computed field corresponding to
 | 
			
		||||
           p_appyType.'''
 | 
			
		||||
| 
						 | 
				
			
			@ -1080,7 +1167,7 @@ class AbstractMixin:
 | 
			
		|||
        params = ''
 | 
			
		||||
        rq = self.REQUEST
 | 
			
		||||
        for k, v in kwargs.iteritems(): params += '&%s=%s' % (k, v)
 | 
			
		||||
        params = params[1:]
 | 
			
		||||
        if params: params = params[1:]
 | 
			
		||||
        if t == 'showRef':
 | 
			
		||||
            chunk = '/skyn/ajax?objectUid=%s&page=ref&' \
 | 
			
		||||
                'macro=showReferenceContent&' % self.UID()
 | 
			
		||||
| 
						 | 
				
			
			@ -1088,6 +1175,11 @@ class AbstractMixin:
 | 
			
		|||
            if rq.has_key(startKey) and not kwargs.has_key(startKey):
 | 
			
		||||
                params += '&%s=%s' % (startKey, rq[startKey])
 | 
			
		||||
            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'
 | 
			
		||||
            return baseUrl + '/skyn/view' + params
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@
 | 
			
		|||
                  dummy2 python:response.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
 | 
			
		||||
                  dummy3 python:response.setHeader('CacheControl', 'no-cache')">
 | 
			
		||||
  <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>
 | 
			
		||||
  <metal:callMacro use-macro="python: context.get(page).macros.get(macro)"/>
 | 
			
		||||
</tal:ajax>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,4 +12,4 @@ else:
 | 
			
		|||
        from Products.CMFCore.utils import getToolByName
 | 
			
		||||
        portal = getToolByName(obj, 'portal_url').getPortalObject()
 | 
			
		||||
        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>
 | 
			
		||||
 | 
			
		||||
<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="v" tal:content="python: v.strftime('%d/%m/') + str(v.year())"></span>
 | 
			
		||||
  <span tal:condition="python: v and (appyType['format'] == 0)"
 | 
			
		||||
        tal:content="python: v.strftime('%H:%M')"></span>
 | 
			
		||||
  <span tal:replace="v"></span>
 | 
			
		||||
</metal:showDate>
 | 
			
		||||
 | 
			
		||||
<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'];
 | 
			
		||||
                              maxMult python: appyType['multiplicity'][1];
 | 
			
		||||
                              severalValues python: (maxMult == None) or (maxMult > 1)">
 | 
			
		||||
| 
						 | 
				
			
			@ -132,30 +130,12 @@
 | 
			
		|||
    <span tal:condition="showLabel" tal:content="label" class="appyLabel"
 | 
			
		||||
          tal:attributes="class python: 'appyLabel ' + contextObj.getCssClasses(appyType, asSlave=False);
 | 
			
		||||
                          id python: v"></span>
 | 
			
		||||
    <tal:severalValues condition="python: v and severalValues">
 | 
			
		||||
    <ul class="appyList">
 | 
			
		||||
      <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 class="appyList" tal:condition="python: v and severalValues">
 | 
			
		||||
      <li class="appyBullet" tal:repeat="sv v"><i tal:content="structure sv"></i></li>
 | 
			
		||||
    </ul>
 | 
			
		||||
    </tal:severalValues>
 | 
			
		||||
    <tal:singleValue condition="python: v and not severalValues">
 | 
			
		||||
      <tal:select condition="appyType/isSelect">
 | 
			
		||||
        <span tal:replace="python: tool.translate('%s_%s_list_%s' % (contextObj.meta_type, field.getName(), v))"/>
 | 
			
		||||
      </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>
 | 
			
		||||
      <span tal:condition="python: fmt != 3" tal:replace="structure v"/>
 | 
			
		||||
      <span tal:condition="python: fmt == 3">********</span>
 | 
			
		||||
    </tal:singleValue>
 | 
			
		||||
  </tal:simpleString>
 | 
			
		||||
  <tal:formattedString condition="python: fmt not in (0, 3)">
 | 
			
		||||
| 
						 | 
				
			
			@ -275,7 +255,6 @@
 | 
			
		|||
 | 
			
		||||
<metal:fields define-macro="listFields"
 | 
			
		||||
     tal:repeat="widgetDescr python: contextObj.getAppyFields(isEdit, pageName)">
 | 
			
		||||
 | 
			
		||||
    <tal:displayArchetypesField condition="python: widgetDescr['widgetType'] == 'field'">
 | 
			
		||||
      <tal:atField condition="python: widgetDescr['page'] == pageName">
 | 
			
		||||
        <metal:field use-macro="here/skyn/macros/macros/showArchetypesField" />
 | 
			
		||||
| 
						 | 
				
			
			@ -293,63 +272,65 @@
 | 
			
		|||
    </tal:displayGroup>
 | 
			
		||||
</metal:fields>
 | 
			
		||||
 | 
			
		||||
<span metal:define-macro="byline"
 | 
			
		||||
     tal:condition="python: site_properties.allowAnonymousViewAbout or not isAnon"
 | 
			
		||||
     tal:define="creator here/Creator;" class="documentByLine">
 | 
			
		||||
  <tal:name tal:condition="creator"
 | 
			
		||||
            tal: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: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>
 | 
			
		||||
<metal:history define-macro="history"
 | 
			
		||||
      tal:define="startNumber request/startNumber|python:0;
 | 
			
		||||
                  startNumber python: int(startNumber);
 | 
			
		||||
                  historyInfo python: contextObj.getHistory(startNumber);
 | 
			
		||||
                  objs        historyInfo/events;
 | 
			
		||||
                  batchSize   historyInfo/batchSize;
 | 
			
		||||
                  totalNumber historyInfo/totalNumber;
 | 
			
		||||
                  ajaxHookId  python:'appyHistory';
 | 
			
		||||
                  baseUrl     python: contextObj.getUrl('showHistory', startNumber='**v**');
 | 
			
		||||
                  tool        contextObj/getTool">
 | 
			
		||||
 | 
			
		||||
<span metal:define-macro="workflowHistory" class="reviewHistory"
 | 
			
		||||
      tal:define="history contextObj/getWorkflowHistory" tal:condition="history">
 | 
			
		||||
  <dl id="history" class="collapsible inline collapsedOnLoad">
 | 
			
		||||
    <dt class="collapsibleHeader" i18n:translate="label_history" i18n:domain="plone">History</dt>
 | 
			
		||||
    <dd class="collapsibleContent">
 | 
			
		||||
    <table width="100%" class="listing nosort" i18n:attributes="summary summary_review_history"
 | 
			
		||||
           tal:define="review_history python:contextObj.portal_workflow.getInfoFor(contextObj, 'review_history', []);
 | 
			
		||||
                       review_history python:[review for review in review_history if review.get('action','')]"
 | 
			
		||||
           tal:condition="review_history">
 | 
			
		||||
  <tal:comment replace="nothing">Table containing the history</tal:comment>
 | 
			
		||||
  <tal:history condition="objs">
 | 
			
		||||
  <metal:nav use-macro="here/skyn/macros/macros/appyNavigate"/>
 | 
			
		||||
  <table width="100%" class="listing nosort">
 | 
			
		||||
    <tr i18n:domain="plone">
 | 
			
		||||
      <th i18n:translate="listingheader_action"/>
 | 
			
		||||
      <th i18n:translate="listingheader_performed_by"/>
 | 
			
		||||
      <th i18n:translate="listingheader_date_and_time"/>
 | 
			
		||||
      <th i18n:translate="listingheader_comment"/>
 | 
			
		||||
    </tr>
 | 
			
		||||
        <metal:block tal:define="review_history python: portal.reverseList(review_history);"
 | 
			
		||||
                     tal:repeat="items review_history">
 | 
			
		||||
        <tr tal:define="odd repeat/items/odd;
 | 
			
		||||
                        rhComments items/comments|nothing;
 | 
			
		||||
                        state items/review_state|nothing"
 | 
			
		||||
            tal:condition="python: items['action'] and (rhComments != '_invisible_')"
 | 
			
		||||
            tal:attributes="class python:test(odd, 'even', 'odd')">
 | 
			
		||||
            
 | 
			
		||||
            <td tal:content="python: tool.translate(contextObj.getWorkflowLabel(items['action']))"
 | 
			
		||||
    <tal:event repeat="event objs">
 | 
			
		||||
    <tr tal:define="odd repeat/event/odd;
 | 
			
		||||
                    rhComments event/comments|nothing;
 | 
			
		||||
                    state event/review_state|nothing;
 | 
			
		||||
                    isDataChange python: event['action'] == '_datachange_'"
 | 
			
		||||
        tal:attributes="class python:test(odd, 'even', 'odd')" valign="top">
 | 
			
		||||
      <td tal:condition="isDataChange" tal:content="python: tool.translate('data_change')"></td>
 | 
			
		||||
      <td tal:condition="not: isDataChange"
 | 
			
		||||
          tal:content="python: tool.translate(contextObj.getWorkflowLabel(event['action']))"
 | 
			
		||||
          tal:attributes="class string:state-${state}"/>
 | 
			
		||||
            <td tal:define="actorid python:items.get('actor');
 | 
			
		||||
      <td tal:define="actorid python:event.get('actor');
 | 
			
		||||
                      actor python:contextObj.portal_membership.getMemberInfo(actorid);
 | 
			
		||||
                      fullname actor/fullname|nothing;
 | 
			
		||||
                      username actor/username|nothing"
 | 
			
		||||
          tal:content="python:fullname or username or actorid"/>
 | 
			
		||||
            <td tal:content="python:toLocalizedTime(items['time'],long_format=True)"/>
 | 
			
		||||
            <td><tal:comment condition="rhComments" tal:content="structure rhComments"/>
 | 
			
		||||
      <td tal:content="python:contextObj.toLocalizedTime(event['time'],long_format=True)"/>
 | 
			
		||||
      <td tal:condition="not: isDataChange"><tal:comment condition="rhComments" tal:content="structure rhComments"/>
 | 
			
		||||
        <tal:noComment condition="not: rhComments" i18n:translate="no_comments" i18n:domain="plone"/></td>
 | 
			
		||||
      <td tal:condition="isDataChange">
 | 
			
		||||
        <tal:comment replace="nothing">
 | 
			
		||||
          Display the previous values of the fields whose value were modified in this change.</tal:comment>
 | 
			
		||||
        <table class="appyChanges" width="100%">
 | 
			
		||||
          <tr>
 | 
			
		||||
            <th tal:content="python: tool.translate('modified_field')"></th>
 | 
			
		||||
            <th tal:content="python: tool.translate('previous_value')"></th>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr tal:repeat="change event/changes/items">
 | 
			
		||||
            <td tal:content="python: tool.translate(change[1][1])"></td>
 | 
			
		||||
            <td tal:define="appyType python:contextObj.getAppyType(change[0])"
 | 
			
		||||
                tal:content="python: contextObj.getAppyValue(change[0], appyType, True, change[1][0])"></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </metal:block>
 | 
			
		||||
        </table>
 | 
			
		||||
    </dd>
 | 
			
		||||
  </dl>
 | 
			
		||||
</span>
 | 
			
		||||
      </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    </tal:event>
 | 
			
		||||
  </table>
 | 
			
		||||
  </tal:history>
 | 
			
		||||
</metal:history>
 | 
			
		||||
 | 
			
		||||
<div metal:define-macro="pagePrologue">
 | 
			
		||||
  <tal:comment replace="nothing">Global elements used in every page.</tal:comment>
 | 
			
		||||
| 
						 | 
				
			
			@ -468,6 +449,32 @@
 | 
			
		|||
       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>
 | 
			
		||||
  <tal:comment replace="nothing">Global form for deleting an object</tal:comment>
 | 
			
		||||
| 
						 | 
				
			
			@ -479,7 +486,10 @@
 | 
			
		|||
 | 
			
		||||
<div metal:define-macro="showPageHeader"
 | 
			
		||||
     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: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"/>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
          <metal:byLine use-macro="here/skyn/macros/macros/byline"/>
 | 
			
		||||
          <tal:showWorkflow condition="showWorkflow">
 | 
			
		||||
            <metal:workflowHistory use-macro="here/skyn/macros/macros/workflowHistory"/>
 | 
			
		||||
          </tal:showWorkflow>
 | 
			
		||||
        <td class="documentByLine">
 | 
			
		||||
          <tal:comment replace="nothing">Creator and last modification date</tal:comment>
 | 
			
		||||
            <tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
 | 
			
		||||
            <tal:accessHistory condition="hasHistory">
 | 
			
		||||
            <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 valign="top"><metal:pod use-macro="here/skyn/macros/macros/listPodTemplates"/>
 | 
			
		||||
        </td>
 | 
			
		||||
      </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>
 | 
			
		||||
      <tr tal:condition="python: showWorkflow and contextObj.getWorkflowLabel()">
 | 
			
		||||
| 
						 | 
				
			
			@ -819,35 +861,6 @@
 | 
			
		|||
   tal:define="queryUrl python: '%s/skyn/query' % appFolder.absolute_url();
 | 
			
		||||
               currentSearch request/search|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>
 | 
			
		||||
  <dt class="portletHeader">
 | 
			
		||||
    <tal:comment replace="nothing">If there is only one flavour, clicking on the portlet
 | 
			
		||||
| 
						 | 
				
			
			@ -907,7 +920,7 @@
 | 
			
		|||
          <img align="left" style="cursor:pointer"
 | 
			
		||||
               tal:attributes="id python: '%s_img' % group['labelId'];
 | 
			
		||||
                               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"/>
 | 
			
		||||
        </dt>
 | 
			
		||||
        <tal:comment replace="nothing">Group searches</tal:comment>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,10 +27,10 @@
 | 
			
		|||
                  phase request/phase|phaseInfo/name;
 | 
			
		||||
                  pageName python: contextObj.getAppyPage(isEdit, phaseInfo);
 | 
			
		||||
                  showWorkflow python: flavour.getAttr('showWorkflowFor' + contextObj.meta_type)">
 | 
			
		||||
      <div metal:use-macro="here/skyn/macros/macros/pagePrologue"/>
 | 
			
		||||
      <div metal:use-macro="here/skyn/macros/macros/showPageHeader"/>
 | 
			
		||||
      <div metal:use-macro="here/skyn/macros/macros/listFields" />
 | 
			
		||||
      <div metal:use-macro="here/skyn/macros/macros/showPageFooter"/>
 | 
			
		||||
      <metal:prologue use-macro="here/skyn/macros/macros/pagePrologue"/>
 | 
			
		||||
      <metal:header   use-macro="here/skyn/macros/macros/showPageHeader"/>
 | 
			
		||||
      <metal:fields   use-macro="here/skyn/macros/macros/listFields" />
 | 
			
		||||
      <metal:footer   use-macro="here/skyn/macros/macros/showPageFooter"/>
 | 
			
		||||
    </metal:fill>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
         tal:define="tool python: context.<!toolInstanceName!>"
 | 
			
		||||
         tal:condition="tool/showPortlet">
 | 
			
		||||
      <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;
 | 
			
		||||
                      appName string:<!applicationName!>;
 | 
			
		||||
                      appFolder tool/getAppFolder;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,6 +72,31 @@
 | 
			
		|||
  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. */
 | 
			
		||||
.stepDone {
 | 
			
		||||
  background-color: #cde2a7;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
   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.utils import sequenceTypes
 | 
			
		||||
from appy.shared.utils import getOsTempFolder
 | 
			
		||||
| 
						 | 
				
			
			@ -153,7 +153,8 @@ class AbstractWrapper:
 | 
			
		|||
            objId = kwargs['id']
 | 
			
		||||
            del kwargs['id']
 | 
			
		||||
        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
 | 
			
		||||
        externalData = None
 | 
			
		||||
        if kwargs.has_key('_data'):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,6 +59,9 @@ class PoMessage:
 | 
			
		|||
    IMPORT_DONE = 'Import terminated successfully.'
 | 
			
		||||
    WORKFLOW_COMMENT = 'Optional comment'
 | 
			
		||||
    WORKFLOW_STATE = 'state'
 | 
			
		||||
    DATA_CHANGE = 'Data change'
 | 
			
		||||
    MODIFIED_FIELD = 'Modified field'
 | 
			
		||||
    PREVIOUS_VALUE = 'Previous value'
 | 
			
		||||
    PHASE = 'phase'
 | 
			
		||||
    ROOT_TYPE = 'type'
 | 
			
		||||
    CHOOSE_A_VALUE = ' - '
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,12 @@ SAP_FUNCTION_ERROR = 'Error while calling function "%s". %s'
 | 
			
		|||
SAP_DISCONNECT_ERROR = 'Error while disconnecting from SAP. %s'
 | 
			
		||||
SAP_TABLE_PARAM_ERROR = 'Param "%s" does not correspond to a valid table ' \
 | 
			
		||||
    '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_INFO_ERROR = 'Error while asking information about function ' \
 | 
			
		||||
    '"%s". %s'
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +58,32 @@ class Sap:
 | 
			
		|||
            connNoPasswd = params[:params.index('PASSWD')] + 'PASSWD=********'
 | 
			
		||||
            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):
 | 
			
		||||
        '''Calls a function on the SAP server.'''
 | 
			
		||||
        try:
 | 
			
		||||
| 
						 | 
				
			
			@ -60,8 +92,8 @@ class Sap:
 | 
			
		|||
            for name, value in params.iteritems():
 | 
			
		||||
                if type(value) == dict:
 | 
			
		||||
                    # The param corresponds to a SAP/C "struct"
 | 
			
		||||
                    v = self.sap.get_structure(name)()
 | 
			
		||||
                    v.from_dict(value)
 | 
			
		||||
                    v = self.createStructure(
 | 
			
		||||
                        self.sap.get_structure(name),value, name)
 | 
			
		||||
                elif type(value) in sequenceTypes:
 | 
			
		||||
                    # The param must be a SAP/C "table" (a list of structs)
 | 
			
		||||
                    # Retrieve the name of the struct type related to this
 | 
			
		||||
| 
						 | 
				
			
			@ -78,8 +110,7 @@ class Sap:
 | 
			
		|||
                            SAP_TABLE_PARAM_ERROR % (name, functionName))
 | 
			
		||||
                    v = self.sap.get_table(tableTypeName)
 | 
			
		||||
                    for dValue in value:
 | 
			
		||||
                        v.append_from_dict(dValue)
 | 
			
		||||
                    #v = v.handle
 | 
			
		||||
                        v.append(self.createStructure(v.struc, dValue, name))
 | 
			
		||||
                else:
 | 
			
		||||
                    v = value
 | 
			
		||||
                function[name] = v
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue