'''This package contains mixin classes that are mixed in with generated classes: - mixins/ClassMixin is mixed in with Standard Archetypes classes; - mixins/ToolMixin is mixed in with the generated application Tool class; - mixins/FlavourMixin is mixed in with the generated application Flavour class. The AbstractMixin defined hereafter is the base class of any mixin.''' # ------------------------------------------------------------------------------ import os, os.path, types, mimetypes import appy.gen from appy.gen import Type, String, Selection from appy.gen.utils import * from appy.gen.layout import Table, defaultPageLayouts from appy.gen.plone25.descriptors import ClassDescriptor from appy.gen.plone25.utils import updateRolesForPermission # ------------------------------------------------------------------------------ class AbstractMixin: '''Every Archetype class generated by appy.gen inherits from a mixin that inherits from this class. It contains basic functions allowing to minimize the amount of generated code.''' def createOrUpdate(self, created, values): '''This method creates (if p_created is True) or updates an object. p_values are manipulated versions of those from the HTTP request. In the case of an object creation (p_created is True), p_self is a temporary object created in the request by portal_factory, and this method creates the corresponding final object. In the case of an update, this method simply updates fields of p_self.''' rq = self.REQUEST obj = self if created: obj = self.portal_factory.doCreate(self, self.id) # portal_factory # creates the final object from the temp object. previousData = None if not created: previousData = self.rememberPreviousData() # Perform the change on the object, unless self is a tool being created. if (obj._appy_meta_type == 'Tool') and created: # We do not process form data (=real update on the object) if the # tool itself is being created. pass else: # Store in the database the new value coming from the form for appyType in self.getAppyTypes('edit', rq.get('page')): value = getattr(values, appyType.name, None) appyType.store(obj, value) if created: # Now we have a title for the object, so we derive a nice id obj._renameAfterCreation(check_auto_id=True) if previousData: # Keep in history potential changes on historized fields self.historizeData(previousData) # Manage references obj._appy_manageRefs(created) if obj.wrapperClass: # Get the wrapper first appyObject = obj.appy() # Call the custom "onEdit" if available if hasattr(appyObject, 'onEdit'): appyObject.onEdit(created) # Manage "add" permissions obj._appy_managePermissions() # Reindex object obj.reindexObject() return obj def delete(self): '''This methods is self's suicide.''' self.getParentNode().manage_delObjects([self.id]) def onCreate(self): '''This method is called when a user wants to create a root object in the application folder or an object through a reference field.''' rq = self.REQUEST if rq.get('initiator', None): # The object to create will be linked to an initiator object through # a ref field. rq.SESSION['initiator'] = rq.get('initiator') rq.SESSION['initiatorField'] = rq.get('field') rq.SESSION['initiatorTarget'] = rq.get('type_name') if self._appy_meta_type == 'Tool': if rq.get('initiator', None): # This is the creation of an object linked to the tool baseUrl = self.absolute_url() else: # This is the creation of a root object in the app folder baseUrl = self.getAppFolder().absolute_url() else: baseUrl = self.absolute_url() objId = self.generateUniqueId(rq.get('type_name')) urlBack = '%s/portal_factory/%s/%s/skyn/edit' % \ (baseUrl, rq.get('type_name'), objId) return self.goto(urlBack) def intraFieldValidation(self, errors, values): '''This method performs field-specific validation for every field from the page that is being created or edited. For every field whose validation generates an error, we add an entry in p_errors. For every field, we add in p_values an entry with the "ready-to-store" field value.''' rq = self.REQUEST for appyType in self.getAppyTypes('edit', rq.form.get('page')): if not appyType.validable: continue value = appyType.getRequestValue(rq) message = appyType.validate(self, value) if message: setattr(errors, appyType.name, message) else: setattr(values, appyType.name, appyType.getStorableValue(value)) def interFieldValidation(self, errors, values): '''This method is called when individual validation of all fields succeed (when editing or creating an object). Then, this method performs inter-field validation. This way, the user must first correct individual fields before being confronted to potential inter-field validation errors.''' obj = self.appy() if not hasattr(obj, 'validate'): return obj.validate(values, errors) # This custom "validate" method may have added fields in the given # p_errors object. Within this object, for every error message that is # not a string, we replace it with the standard validation error for the # corresponding field. for key, value in errors.__dict__.iteritems(): resValue = value if not isinstance(resValue, basestring): appyType = self.getAppyType(key) msgId = '%s_valid' % appyType.labelId resValue = self.translate(msgId) setattr(errors, key, resValue) def onUpdate(self): '''This method is executed when a user wants to update an object. The object may be a temporary object created by portal_factory in the request. In this case, the update consists in the creation of the "final" object in the database. If the object is not a temporary one, this method updates its fields in the database.''' rq = self.REQUEST errorMessage = self.translate( 'Please correct the indicated errors.', domain='plone') isNew = rq.get('is_new') == 'True' # Go back to the consult view if the user clicked on 'Cancel' if rq.get('buttonCancel.x', None): if isNew: # Go back to the Plone site (no better solution at present). urlBack = self.portal_url.getPortalObject().absolute_url() else: urlBack = self.absolute_url() self.plone_utils.addPortalMessage( self.translate('Changes canceled.', domain='plone')) return self.goto(urlBack, True) # Object for storing validation errors errors = AppyObject() # Object for storing the (converted) values from the request values = AppyObject() # Trigger field-specific validation self.intraFieldValidation(errors, values) if errors.__dict__: rq.set('errors', errors.__dict__) self.plone_utils.addPortalMessage(errorMessage) return self.skyn.edit(self) # Trigger inter-field validation self.interFieldValidation(errors, values) if errors.__dict__: rq.set('errors', errors.__dict__) self.plone_utils.addPortalMessage(errorMessage) return self.skyn.edit(self) # Create or update the object in the database obj = self.createOrUpdate(isNew, values) # Redirect the user to the appropriate page msg = obj.translate('Changes saved.', domain='plone') if rq.get('buttonOk.x', None): # Go to the consult view for this object obj.plone_utils.addPortalMessage(msg) return self.goto('%s/skyn/view' % obj.absolute_url(), True) if rq.get('buttonPrevious.x', None): # Go to the previous page (edit mode) for this object. # We recompute the list of phases and pages because things # may have changed since the object has been updated (ie, # additional pages may be shown or hidden now, so the next and # previous pages may have changed). currentPage = rq.get('page') phaseInfo = self.getAppyPhases(page=currentPage) previousPage = self.getPreviousPage(phaseInfo, currentPage) if previousPage: rq.set('page', previousPage) return obj.skyn.edit(obj) else: obj.plone_utils.addPortalMessage(msg) return self.goto('%s/skyn/view' % obj.absolute_url(), True) if rq.get('buttonNext.x', None): # Go to the next page (edit mode) for this object currentPage = rq.get('page') phaseInfo = self.getAppyPhases(page=currentPage) nextPage = self.getNextPage(phaseInfo, currentPage) if nextPage: rq.set('page', nextPage) return obj.skyn.edit(obj) else: obj.plone_utils.addPortalMessage(msg) return self.goto('%s/skyn/view' % obj.absolute_url(), True) return obj.skyn.edit(obj) def onDelete(self): rq = self.REQUEST msg = self.translate('delete_done') self.delete() self.plone_utils.addPortalMessage(msg) self.goto(rq['HTTP_REFERER'], True) 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 appyType in self.getAllAppyTypes(): if appyType.historized: res[appyType.name] = (getattr(self, appyType.name), appyType.labelId) return res def addDataChange(self, changes, labels=False): '''This method allows to add "manually" a data change into the objet's history. Indeed, data changes are "automatically" recorded only when a HTTP form is uploaded, not if, in the code, a setter is called on a field. The method is also called by the method historizeData below, that performs "automatic" recording when a HTTP form is uploaded.''' # Add to the p_changes dict the field labels if they are not present if not labels: for fieldName in changes.iterkeys(): appyType = self.getAppyType(fieldName) changes[fieldName] = (changes[fieldName], appyType.labelId) # Create the event to record in the history DateTime = self.getProductConfig().DateTime state = self.portal_workflow.getInfoFor(self, 'review_state') user = self.portal_membership.getAuthenticatedMember() event = {'action': '_datachange_', 'changes': changes, '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 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(): prev = previousData[fieldName][0] curr = getattr(self, fieldName) if (prev == curr) or ((prev == None) and (curr == '')) or \ ((prev == '') and (curr == None)): del previousData[fieldName] if previousData: self.addDataChange(previousData, labels=True) def goto(self, url, addParams=False): '''Brings the user to some p_url after an action has been executed.''' rq = self.REQUEST if not addParams: return rq.RESPONSE.redirect(url) # Add some context-related parameters if needed. params = [] if rq.get('page', ''): params.append('page=%s' % rq['page']) if rq.get('nav', ''): params.append('nav=%s' % rq['nav']) params = '&'.join(params) if not params: return rq.RESPONSE.redirect(url) if url.find('?') != -1: params = '&' + params else: params = '?' + params return rq.RESPONSE.redirect(url+params) def showField(self, name, layoutType='view'): '''Must I show field named p_name on this p_layoutType ?''' return self.getAppyType(name).isShowable(self, layoutType) def getMethod(self, methodName): '''Returns the method named p_methodName.''' return getattr(self, methodName, None) def getFormattedValue(self, name, useParamValue=False, value=None, forMasterId=False): '''Returns the value of field named p_name for this object (p_self). 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). If p_forMasterId is True, it returns the value as will be needed to produce an identifier used within HTML pages for master/slave relationships.''' appyType = self.getAppyType(name) # Which value will we use ? if not useParamValue: value = appyType.getValue(self) # Return the value as is if it is None or forMasterId if forMasterId: return value # Return the formatted value else return appyType.getFormattedValue(self, value) def _appy_getRefs(self, fieldName, ploneObjects=False, noListIfSingleObj=False, startNumber=None): '''p_fieldName is the name of a Ref field. This method returns an ordered list containing the objects linked to p_self through this field. If p_ploneObjects is True, the method returns the "true" Plone objects instead of the Appy wrappers. If p_startNumber is None, this method returns all referred objects. If p_startNumber is a number, this method will return x objects, starting at p_startNumber, x being appyType.maxPerPage.''' appyType = self.getAppyType(fieldName) sortedUids = self._appy_getSortedField(fieldName) batchNeeded = startNumber != None exec 'refUids= self.getRaw%s%s()' % (fieldName[0].upper(),fieldName[1:]) # There may be too much UIDs in sortedUids because these fields # are not updated when objects are deleted. So we do it now. TODO: do # such cleaning on object deletion? toDelete = [] for uid in sortedUids: if uid not in refUids: toDelete.append(uid) for uid in toDelete: sortedUids.remove(uid) # Prepare the result res = SomeObjects() res.totalNumber = res.batchSize = len(sortedUids) if batchNeeded: res.batchSize = appyType.maxPerPage if startNumber != None: res.startNumber = startNumber # Get the needed referred objects i = res.startNumber # Is it possible and more efficient to perform a single query in # uid_catalog and get the result in the order of specified uids? while i < (res.startNumber + res.batchSize): if i >= res.totalNumber: break refUid = sortedUids[i] refObject = self.uid_catalog(UID=refUid)[0].getObject() if not ploneObjects: refObject = refObject.appy() res.objects.append(refObject) i += 1 if res.objects and noListIfSingleObj: if appyType.multiplicity[1] == 1: res.objects = res.objects[0] return res def getAppyRefs(self, appyType, startNumber=None): '''Gets the objects linked to me through Ref p_appyType. If p_startNumber is None, this method returns all referred objects. If p_startNumber is a number, this method will return x objects, starting at p_startNumber, x being appyType.maxPerPage.''' if not appyType['isBack']: return self._appy_getRefs(appyType['name'], ploneObjects=True, startNumber=startNumber).__dict__ else: # Note Pagination is not yet implemented for backward refs. return SomeObjects(self.getBRefs(appyType['relationship'])).__dict__ def getAppyRefIndex(self, fieldName, obj): '''Gets the position of p_obj within Ref field named p_fieldName.''' sortedObjectsUids = self._appy_getSortedField(fieldName) res = sortedObjectsUids.index(obj.UID()) return res def getAppyRefPortalType(self, fieldName): '''Gets the portal type of objects linked to me through Ref field named p_fieldName.''' appyType = self.getAppyType(fieldName) tool = self.getTool() if self._appy_meta_type == 'Flavour': flavour = self.appy() else: portalTypeName = self._appy_getPortalType(self.REQUEST) flavour = tool.getFlavour(portalTypeName) return self._appy_getAtType(appyType.klass, flavour) def getAppyType(self, name, asDict=False, className=None): '''Returns the Appy type named p_name. If no p_className is defined, the field is supposed to belong to self's class.''' className = className or self.__class__.__name__ attrs = self.getProductConfig().attributesDict[className] appyType = attrs.get(name, None) if appyType and asDict: return appyType.__dict__ return appyType def getAllAppyTypes(self, className=None): '''Returns the ordered list of all Appy types for self's class if p_className is not specified, or for p_className else.''' className = className or self.__class__.__name__ return self.getProductConfig().attributes[className] def getGroupedAppyTypes(self, layoutType, page): '''Returns the fields sorted by group. For every field, the appyType (dict version) is given.''' res = [] groups = {} # The already encountered groups for appyType in self.getAllAppyTypes(): if appyType.page != page: continue if not appyType.isShowable(self, layoutType): continue if not appyType.group: res.append(appyType.__dict__) else: # Insert the GroupDescr instance corresponding to # appyType.group at the right place groupDescr = appyType.group.insertInto(res, groups, appyType.page, self.meta_type) GroupDescr.addWidget(groupDescr, appyType.__dict__) return res def getAppyTypes(self, layoutType, page): '''Returns the list of appyTypes that belong to a given p_page, for a given p_layoutType.''' res = [] for appyType in self.getAllAppyTypes(): if appyType.page != page: continue if not appyType.isShowable(self, layoutType): continue res.append(appyType) return res def getCssAndJs(self, layoutType, page): '''Returns the CSS and Javascript files that need to be loaded by the p_page for the given p_layoutType.''' css = [] js = [] for appyType in self.getAppyTypes(layoutType, page): typeCss = appyType.getCss(layoutType) if typeCss: for tcss in typeCss: if tcss not in css: css.append(tcss) typeJs = appyType.getJs(layoutType) if typeJs: for tjs in typeJs: if tjs not in js: js.append(tjs) return css, js def getAppyTypesFromNames(self, fieldNames, asDict=True): '''Gets the appy types names p_fieldNames.''' return [self.getAppyType(name, asDict) for name in fieldNames] def getAppyStates(self, phase, currentOnly=False): '''Returns information about the states that are related to p_phase. If p_currentOnly is True, we return the current state, even if not related to p_phase.''' res = [] dcWorkflow = self.getWorkflow(appy=False) if not dcWorkflow: return res currentState = self.portal_workflow.getInfoFor(self, 'review_state') if currentOnly: return [StateDescr(currentState,'current').get()] workflow = self.getWorkflow(appy=True) if workflow: stateStatus = 'done' for stateName in workflow._states: if stateName == currentState: stateStatus = 'current' elif stateStatus != 'done': stateStatus = 'future' state = getattr(workflow, stateName) if (state.phase == phase) and \ (self._appy_showState(workflow, state.show)): res.append(StateDescr(stateName, stateStatus).get()) return res def getAppyTransitions(self): '''Returns the transitions that the user can trigger on p_self.''' transitions = self.portal_workflow.getTransitionsFor(self) res = [] if transitions: # Retrieve the corresponding Appy transition, to check if the user # may view it. workflow = self.getWorkflow(appy=True) if not workflow: return transitions for transition in transitions: # Get the corresponding Appy transition appyTr = workflow._transitionsMapping[transition['id']] if self._appy_showTransition(workflow, appyTr.show): res.append(transition) return res def getAppyPhases(self, currentOnly=False, page=None): '''Gets the list of phases that are defined for this content type. If p_currentOnly is True, the search is limited to the current phase. If p_page is not None, the search is limited to the phase where p_page lies.''' # Get the list of phases res = [] # Ordered list of phases phases = {} # Dict of phases for appyType in self.getAllAppyTypes(): if appyType.phase not in phases: states = self.getAppyStates(appyType.phase) phase = PhaseDescr(appyType.phase, states, self) res.append(phase.__dict__) phases[appyType.phase] = phase else: phase = phases[appyType.phase] phase.addPage(appyType, self) # Remove phases that have no visible page for i in range(len(res)-1, -1, -1): if not res[i]['pages']: del phases[res[i]['name']] del res[i] # Then, compute status of phases for ph in phases.itervalues(): ph.computeStatus(res) ph.totalNbOfPhases = len(res) # Restrict the result if we must not produce the whole list of phases if currentOnly: for phaseInfo in res: if phaseInfo['phaseStatus'] == 'Current': return phaseInfo elif page: for phaseInfo in res: if page in phaseInfo['pages']: return phaseInfo else: return res def getPreviousPage(self, phase, page): '''Returns the page that precedes p_page which is in p_phase.''' pageIndex = phase['pages'].index(page) if pageIndex > 0: # We stay on the same phase, previous page return phase['pages'][pageIndex-1] else: if phase['previousPhase']: # We go to the last page of previous phase previousPhase = phase['previousPhase'] return previousPhase['pages'][-1] else: return None def getNextPage(self, phase, page): '''Returns the page that follows p_page which is in p_phase.''' pageIndex = phase['pages'].index(page) if pageIndex < len(phase['pages'])-1: # We stay on the same phase, next page return phase['pages'][pageIndex+1] else: if phase['nextPhase']: # We go to the first page of next phase nextPhase = phase['nextPhase'] return nextPhase['pages'][0] else: return None def changeRefOrder(self, fieldName, objectUid, newIndex, isDelta): '''This method changes the position of object with uid p_objectUid in reference field p_fieldName to p_newIndex i p_isDelta is False, or to actualIndex+p_newIndex if p_isDelta is True.''' sortedObjectsUids = self._appy_getSortedField(fieldName) oldIndex = sortedObjectsUids.index(objectUid) sortedObjectsUids.remove(objectUid) if isDelta: newIndex = oldIndex + newIndex else: pass # To implement later on sortedObjectsUids.insert(newIndex, objectUid) def onChangeRefOrder(self): '''This method is called when the user wants to change order of an item in a reference field.''' rq = self.REQUEST # Move the item up (-1), down (+1) ? move = -1 # Move up if rq['move'] == 'down': move = 1 # Down isDelta = True self.changeRefOrder(rq['fieldName'], rq['refObjectUid'], move, isDelta) def onSortReference(self): '''This method is called when the user wants to sort the content of a reference field.''' rq = self.REQUEST fieldName = rq.get('fieldName') sortKey = rq.get('sortKey') reverse = rq.get('reverse') == 'True' self.appy().sort(fieldName, sortKey=sortKey, reverse=reverse) def isRefSortable(self, fieldName): '''Can p_fieldName, which is a field defined on self, be used as a sort key in a reference field?''' return self.getAppyType(fieldName).isSortable(usage='ref') def getWorkflow(self, appy=True): '''Returns the Appy workflow instance that is relevant for this object. If p_appy is False, it returns the DC workflow.''' res = None if appy: # Get the workflow class first workflowClass = None if self.wrapperClass: appyClass = self.wrapperClass.__bases__[-1] if hasattr(appyClass, 'workflow'): workflowClass = appyClass.workflow if workflowClass: # Get the corresponding prototypical workflow instance res = self.getProductConfig().workflowInstances[workflowClass] else: dcWorkflows = self.portal_workflow.getWorkflowsFor(self) if dcWorkflows: res = dcWorkflows[0] return res def getWorkflowLabel(self, stateName=None): '''Gets the i18n label for the workflow current state. If no p_stateName is given, workflow label is given for the current state.''' res = '' wf = self.getWorkflow(appy=False) if wf: res = stateName if not res: res = self.portal_workflow.getInfoFor(self, 'review_state') appyWf = self.getWorkflow(appy=True) if appyWf: 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 = 5 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 may(self, transitionName): '''May the user execute transition named p_transitionName?''' # Get the Appy workflow instance workflow = self.getWorkflow() res = False if workflow: # Get the corresponding Appy transition transition = workflow._transitionsMapping[transitionName] user = self.portal_membership.getAuthenticatedMember() if isinstance(transition.condition, basestring): # It is a role. Transition may be triggered if the user has this # role. res = user.has_role(transition.condition, self) elif type(transition.condition) == types.FunctionType: res = transition.condition(workflow, self.appy()) elif type(transition.condition) in (tuple, list): # It is a list of roles and or functions. Transition may be # triggered if user has at least one of those roles and if all # functions return True. hasRole = None for roleOrFunction in transition.condition: if isinstance(roleOrFunction, basestring): if hasRole == None: hasRole = False if user.has_role(roleOrFunction, self): hasRole = True elif type(roleOrFunction) == types.FunctionType: if not roleOrFunction(workflow, self.appy()): return False if hasRole != False: res = True return res def executeAppyAction(self, actionName, reindex=True): '''Executes action with p_fieldName on this object.''' appyType = self.getAppyType(actionName) actionRes = appyType(self.appy()) self.reindexObject() return appyType.result, actionRes def onExecuteAppyAction(self): '''This method is called every time a user wants to execute an Appy action on an object.''' rq = self.REQUEST resultType, actionResult = self.executeAppyAction(rq['fieldName']) successfull, msg = actionResult if not msg: # Use the default i18n messages suffix = 'ko' if successfull: suffix = 'ok' appyType = self.getAppyType(rq['fieldName']) label = '%s_action_%s' % (appyType.labelId, suffix) msg = self.translate(label) if (resultType == 'computation') or not successfull: self.plone_utils.addPortalMessage(msg) return self.goto(rq['HTTP_REFERER'], True) else: # msg does not contain a message, but a complete file to show as is. # (or, if your prefer, the message must be shown directly to the # user, not encapsulated in a Plone page). res = self.getProductConfig().File(msg.name, msg.name, msg, content_type=mimetypes.guess_type(msg.name)[0]) return res.index_html(rq, rq.RESPONSE) def onTriggerTransition(self): '''This method is called whenever a user wants to trigger a workflow transition on an object.''' rq = self.REQUEST self.portal_workflow.doActionFor(self, rq['workflow_action'], comment = rq.get('comment', '')) # Where to redirect the user back ? urlBack = rq['HTTP_REFERER'] if urlBack.find('?') != -1: # Remove params; this way, the user may be redirected to correct # phase when relevant. urlBack = urlBack[:urlBack.find('?')] msg = self.translate(u'Your content\'s status has been modified.', domain='plone') self.plone_utils.addPortalMessage(msg) self.reindexObject() return self.goto(urlBack) def callAppySelect(self, selectMethod, brains): '''Selects objects from a Reference field.''' if selectMethod: obj = self.appy() allObjects = [b.getObject().appy() for b in brains] filteredObjects = selectMethod(obj, allObjects) filteredUids = [o.o.UID() for o in filteredObjects] res = [] for b in brains: if b.UID in filteredUids: res.append(b) else: res = brains return res def fieldValueSelected(self, fieldName, vocabValue): '''When displaying a selection box (ie a String with a validator being a list), must the _vocabValue appear as selected?''' rq = self.REQUEST # Get the value we must compare (from request or from database) if rq.has_key(fieldName): compValue = rq.get(fieldName) else: compValue = self.getAppyType(fieldName).getValue(self) # Compare the value if type(compValue) in sequenceTypes: if vocabValue in compValue: return True else: if vocabValue == compValue: return True def checkboxChecked(self, fieldName): '''When displaying a checkbox, must it be checked or not?''' rq = self.REQUEST # Get the value we must compare (from request or from database) if rq.has_key(fieldName): compValue = rq.get(fieldName) compValue = compValue in ('True', 1, '1') else: compValue = self.getAppyType(fieldName).getValue(self) # Compare the value return compValue def dateValueSelected(self, fieldName, fieldPart, dateValue): '''When displaying a date field, must the particular p_dateValue be selected in the field corresponding to the date part?''' # Get the value we must compare (from request or from database) rq = self.REQUEST partName = '%s_%s' % (fieldName, fieldPart) if rq.has_key(partName): compValue = rq.get(partName) if compValue.isdigit(): compValue = int(compValue) else: compValue = self.getAppyType(fieldName).getValue(self) if compValue: compValue = getattr(compValue, fieldPart)() # Compare the value return compValue == dateValue def getPossibleValues(self, name, withTranslations, withBlankValue, className=None): '''Gets the possible values for field named p_name. This field must be a String with isSelection()=True. If p_withTranslations is True, instead of returning a list of string values, the result is a list of tuples (s_value, s_translation). If p_withBlankValue is True, a blank value is prepended to the list. If no p_className is defined, the field is supposed to belong to self's class''' appyType = self.getAppyType(name, className=className) return appyType.getPossibleValues(self,withTranslations,withBlankValue) def appy(self): '''Returns a wrapper object allowing to manipulate p_self the Appy way.''' # Create the dict for storing Appy wrapper on the REQUEST if needed. rq = self.REQUEST if not hasattr(rq, 'appyWrappers'): rq.appyWrappers = {} # Return the Appy wrapper from rq.appyWrappers if already there uid = self.UID() if uid in rq.appyWrappers: return rq.appyWrappers[uid] # Create the Appy wrapper, cache it in rq.appyWrappers and return it wrapper = self.wrapperClass(self) rq.appyWrappers[uid] = wrapper return wrapper def _appy_getAtType(self, appyClass, flavour=None): '''Gets the name of the Archetypes class that corresponds to p_appyClass (which is a Python class coming from the user application). If p_flavour is specified, the method returns the name of the specific Archetypes class in this flavour (ie suffixed with the flavour number).''' res = ClassDescriptor.getClassName(appyClass) appName = self.getProductConfig().PROJECTNAME if res.find('Extensions_appyWrappers') != -1: # This is not a content type defined Maybe I am a tool or flavour res = appName + appyClass.__name__ elif issubclass(appyClass, appy.gen.Tool): # This is the custom tool res = '%sTool' % appName elif issubclass(appyClass, appy.gen.Flavour): # This is the custom Flavour res = '%sFlavour' % appName else: if flavour and flavour.number != 1: res += '_%d' % flavour.number return res def _appy_getRefsBack(self, fieldName, relName, ploneObjects=False, noListIfSingleObj=False): '''This method returns the list of objects linked to this one through the BackRef corresponding to the Archetypes relationship named p_relName.''' # Preamble: must I return a list or a single element? maxOne = False if noListIfSingleObj: # I must get the referred appyType to know its maximum multiplicity. appyType = self.getAppyType(fieldName) if appyType.multiplicity[1] == 1: maxOne = True # Get the referred objects through the Archetypes relationship. objs = self.getBRefs(relName) if maxOne: res = None if objs: res = objs[0] if res and not ploneObjects: res = res.appy() else: res = objs if not ploneObjects: res = [o.appy() for o in objs] return res def _appy_showPage(self, page, pageShow): '''Must I show p_page?''' if callable(pageShow): return pageShow(self.appy()) else: return pageShow def _appy_showState(self, workflow, stateShow): '''Must I show a state whose "show value" is p_stateShow?''' if callable(stateShow): return stateShow(workflow, self.appy()) else: return stateShow def _appy_showTransition(self, workflow, transitionShow): '''Must I show a transition whose "show value" is p_transitionShow?''' if callable(transitionShow): return transitionShow(workflow, self.appy()) else: return transitionShow def _appy_managePermissions(self): '''When an object is created or updated, we must update "add" permissions accordingly: if the object is a folder, we must set on it permissions that will allow to create, inside it, objects through Ref fields; if it is not a folder, we must update permissions on its parent folder instead.''' # Determine on which folder we need to set "add" permissions folder = self if not self.isPrincipiaFolderish: folder = self.getParentNode() # On this folder, set "add" permissions for every content type that will # be created through reference fields allCreators = set() for appyType in self.getAllAppyTypes(): if appyType.type == 'Ref': refContentTypeName = self.getAppyRefPortalType(appyType.name) refContentType = getattr(self.portal_types, refContentTypeName) refMetaType = refContentType.content_meta_type if refMetaType in self.getProductConfig(\ ).ADD_CONTENT_PERMISSIONS: # No specific "add" permission is defined for tool and # flavour, for example. appyClass = refContentType.wrapperClass.__bases__[-1] # Get roles that may add this content type creators = getattr(appyClass, 'creators', None) if not creators: creators = self.getProductConfig().defaultAddRoles allCreators = allCreators.union(creators) # Grant this "add" permission to those roles updateRolesForPermission( self.getProductConfig().ADD_CONTENT_PERMISSIONS[\ refMetaType], creators, folder) # Beyond content-type-specific "add" permissions, creators must also # have the main permission "Add portal content". if allCreators: updateRolesForPermission('Add portal content', tuple(allCreators), folder) def _appy_getPortalType(self, request): '''Guess the portal_type of p_self from info about p_self and p_request.''' res = None # If the object is being created, self.portal_type is not correctly # initialized yet. if request.has_key('__factory__info__'): factoryInfo = request['__factory__info__'] if factoryInfo.has_key('stack'): res = factoryInfo['stack'][0] if not res: res = self.portal_type return res def _appy_getSortedField(self, fieldName): '''Gets, for reference field p_fieldName, the Appy persistent list that contains the sorted list of referred object UIDs. If this list does not exist, it is created.''' sortedFieldName = '_appy_%s' % fieldName if not hasattr(self.aq_base, sortedFieldName): pList = self.getProductConfig().PersistentList exec 'self.%s = pList()' % sortedFieldName return getattr(self, sortedFieldName) def _appy_manageRefs(self, created): '''Every time an object is created or updated, this method updates the Reference fields accordingly.''' self._appy_manageRefsFromRequest() # If the creation was initiated by another object, update the # reference. if created and hasattr(self.REQUEST, 'SESSION'): # When used by the test system, no SESSION object is created. session = self.REQUEST.SESSION initiatorUid = session.get('initiator', None) initiator = None if initiatorUid: initiatorRes = self.uid_catalog.searchResults(UID=initiatorUid) if initiatorRes: initiator = initiatorRes[0].getObject() if initiator: fieldName = session.get('initiatorField') initiator.appy().link(fieldName, self) # Re-initialise the session session['initiator'] = None def _appy_manageRefsFromRequest(self): '''Appy manages itself some Ref fields (with link=True). So here we must update the Ref fields.''' fieldsInRequest = [] # Fields present in the request for requestKey in self.REQUEST.keys(): if requestKey.startswith('appy_ref_'): fieldName = requestKey[9:] # Security check if not self.getAppyType(fieldName).isShowable(self, 'edit'): continue fieldsInRequest.append(fieldName) fieldValue = self.REQUEST[requestKey] sortedRefField = self._appy_getSortedField(fieldName) del sortedRefField[:] if not fieldValue: fieldValue = [] if isinstance(fieldValue, basestring): fieldValue = [fieldValue] refObjects = [] for uid in fieldValue: obj = self.uid_catalog(UID=uid)[0].getObject() refObjects.append(obj) sortedRefField.append(uid) exec 'self.set%s%s(refObjects)' % (fieldName[0].upper(), fieldName[1:]) # Manage Ref fields that are not present in the request currentPage = self.REQUEST.get('page', 'main') for appyType in self.getAllAppyTypes(): if (appyType.type == 'Ref') and not appyType.isBack and \ (appyType.page == currentPage) and \ (appyType.name not in fieldsInRequest): # If this field is visible, it was not present in the request: # it means that we must remove any Ref from it. if appyType.isShowable(self, 'edit'): exec 'self.set%s%s([])' % (appyType.name[0].upper(), appyType.name[1:]) def getUrl(self): '''Returns the Appy URL for viewing this object.''' return self.absolute_url() + '/skyn/view' def translate(self, label, mapping={}, domain=None, default=None): '''Translates a given p_label into p_domain with p_mapping.''' cfg = self.getProductConfig() if not domain: domain = cfg.PROJECTNAME return self.translation_service.utranslate( domain, label, mapping, self, default=default) def getPageLayout(self, layoutType): '''Returns the layout coresponding to p_layoutType for p_self.''' appyClass = self.wrapperClass.__bases__[-1] if hasattr(appyClass, 'layouts'): layout = appyClass.layouts[layoutType] if isinstance(layout, basestring): layout = Table(layout) else: layout = defaultPageLayouts[layoutType] return layout.get() def getPageTemplate(self, skyn, templateName): '''Returns, in the skyn folder, the page template corresponding to p_templateName.''' res = skyn for name in templateName.split('/'): res = res.get(name) return res def download(self): '''Downloads the content of the file that is in the File field named p_name.''' name = self.REQUEST.get('name') if not name: return appyType = self.getAppyType(name) if (not appyType.type =='File') or not appyType.isShowable(self,'view'): return theFile = getattr(self, name, None) if theFile: response = self.REQUEST.RESPONSE response.setHeader('Content-Disposition', 'inline;filename="%s"' % \ theFile.filename) response.setHeader('Cachecontrol', 'no-cache') response.setHeader('Expires', 'Thu, 11 Dec 1975 12:05:05 GMT') return theFile.index_html(self.REQUEST, self.REQUEST.RESPONSE) # ------------------------------------------------------------------------------