appypod-rattail/gen/plone25/mixins/__init__.py

1045 lines
47 KiB
Python
Raw Normal View History

2009-06-29 07:06:01 -05:00
'''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
2009-06-29 07:06:01 -05:00
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
2009-06-29 07:06:01 -05:00
# ------------------------------------------------------------------------------
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':
2010-01-20 14:51:17 -06:00
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'))
2010-01-08 11:03:59 -06:00
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)
2010-01-08 11:03:59 -06:00
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():
2009-12-15 14:30:43 -06:00
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)
2010-01-08 11:03:59 -06:00
def goto(self, url, addParams=False):
'''Brings the user to some p_url after an action has been executed.'''
2010-01-08 11:03:59 -06:00
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'])
2010-01-08 11:03:59 -06:00
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)
2009-06-29 07:06:01 -05:00
def _appy_getRefs(self, fieldName, ploneObjects=False,
noListIfSingleObj=False, startNumber=None):
2009-06-29 07:06:01 -05:00
'''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)
2010-02-12 03:59:42 -06:00
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]
2009-06-29 07:06:01 -05:00
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__
2009-06-29 07:06:01 -05:00
def getAppyRefIndex(self, fieldName, obj):
'''Gets the position of p_obj within Ref field named p_fieldName.'''
2010-02-12 03:59:42 -06:00
sortedObjectsUids = self._appy_getSortedField(fieldName)
2009-06-29 07:06:01 -05:00
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()
2009-06-29 07:06:01 -05:00
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.'''
2009-06-29 07:06:01 -05:00
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__)
2009-06-29 07:06:01 -05:00
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__)
2009-06-29 07:06:01 -05:00
return res
def getAppyTypes(self, layoutType, page):
'''Returns the list of appyTypes that belong to a given p_page, for a
given p_layoutType.'''
2009-06-29 07:06:01 -05:00
res = []
for appyType in self.getAllAppyTypes():
if appyType.page != page: continue
if not appyType.isShowable(self, layoutType): continue
res.append(appyType)
2009-06-29 07:06:01 -05:00
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]
2009-06-29 07:06:01 -05:00
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):
2009-06-29 07:06:01 -05:00
'''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.'''
2009-06-29 07:06:01 -05:00
# 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)
2009-06-29 07:06:01 -05:00
res.append(phase.__dict__)
phases[appyType.phase] = phase
2009-06-29 07:06:01 -05:00
else:
phase = phases[appyType.phase]
2009-06-29 07:06:01 -05:00
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)
2009-06-29 07:06:01 -05:00
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:
2009-06-29 07:06:01 -05:00
for phaseInfo in res:
if page in phaseInfo['pages']:
2009-06-29 07:06:01 -05:00
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):
2009-06-29 07:06:01 -05:00
'''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.'''
2010-02-12 03:59:42 -06:00
sortedObjectsUids = self._appy_getSortedField(fieldName)
2009-06-29 07:06:01 -05:00
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')
2009-06-29 07:06:01 -05:00
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]
2009-06-29 07:06:01 -05:00
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}
2009-06-29 07:06:01 -05:00
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())
2009-06-29 07:06:01 -05:00
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()):
2009-06-29 07:06:01 -05:00
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())
2009-06-29 07:06:01 -05:00
self.reindexObject()
return appyType.result, actionRes
2009-06-29 07:06:01 -05:00
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)
2010-01-08 11:03:59 -06:00
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)
2010-01-12 01:58:40 -06:00
self.reindexObject()
return self.goto(urlBack)
2009-06-29 07:06:01 -05:00
def callAppySelect(self, selectMethod, brains):
'''Selects objects from a Reference field.'''
if selectMethod:
obj = self.appy()
allObjects = [b.getObject().appy() for b in brains]
2009-06-29 07:06:01 -05:00
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):
2009-06-29 07:06:01 -05:00
'''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)
2009-06-29 07:06:01 -05:00
else:
compValue = self.getAppyType(fieldName).getValue(self)
# Compare the value
if type(compValue) in sequenceTypes:
if vocabValue in compValue: return True
2009-06-29 07:06:01 -05:00
else:
if vocabValue == compValue: return True
2009-06-29 07:06:01 -05:00
def checkboxChecked(self, fieldName):
2009-06-29 07:06:01 -05:00
'''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')
2009-06-29 07:06:01 -05:00
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)
2009-06-29 07:06:01 -05:00
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
2009-06-29 07:06:01 -05:00
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)
2009-06-29 07:06:01 -05:00
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]
2009-06-29 07:06:01 -05:00
return res
def _appy_showPage(self, page, pageShow):
'''Must I show p_page?'''
if callable(pageShow):
return pageShow(self.appy())
2009-06-29 07:06:01 -05:00
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())
2009-06-29 07:06:01 -05:00
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
2009-06-29 07:06:01 -05:00
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)
2009-06-29 07:06:01 -05:00
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
2010-02-12 03:59:42 -06:00
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)
2009-06-29 07:06:01 -05:00
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.
2009-06-29 07:06:01 -05:00
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)
2009-06-29 07:06:01 -05:00
# 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
2009-06-29 07:06:01 -05:00
fieldsInRequest.append(fieldName)
fieldValue = self.REQUEST[requestKey]
2010-02-12 03:59:42 -06:00
sortedRefField = self._appy_getSortedField(fieldName)
2009-06-29 07:06:01 -05:00
del sortedRefField[:]
if not fieldValue: fieldValue = []
2009-06-29 07:06:01 -05:00
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):
2009-06-29 07:06:01 -05:00
# 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)
2009-06-29 07:06:01 -05:00
# ------------------------------------------------------------------------------