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

1079 lines
49 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, Role
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.unmarkCreationFlag()
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 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). Moreover, previous and next
# pages may not be available in "edit" mode, so we return the edit
# or view pages depending on page.show.
currentPage = rq.get('page')
phaseInfo = self.getAppyPhases(page=currentPage)
previousPage, show = self.getPreviousPage(phaseInfo, currentPage)
if previousPage:
# Return the edit or view page?
if show != 'view':
rq.set('page', previousPage)
return obj.skyn.edit(obj)
else:
urlBack = '%s/skyn/view?page=%s' % (obj.absolute_url(),
previousPage)
return self.goto(urlBack)
else:
obj.plone_utils.addPortalMessage(msg)
return self.goto('%s/skyn/view' % obj.absolute_url())
if rq.get('buttonNext.x', None):
# Go to the next page for this object
currentPage = rq.get('page')
phaseInfo = self.getAppyPhases(page=currentPage)
nextPage, show = self.getNextPage(phaseInfo, currentPage)
if nextPage:
# Return the edit or view page?
if show != 'view':
rq.set('page', nextPage)
return obj.skyn.edit(obj)
else:
urlBack = '%s/skyn/view?page=%s' % (obj.absolute_url(),
nextPage)
return self.goto(urlBack)
else:
obj.plone_utils.addPortalMessage(msg)
return self.goto('%s/skyn/view' % obj.absolute_url())
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 getFieldValue(self, name, useParamValue=False, value=None,
formatted=True):
'''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_formatted is False, it will return the true database
(or default) value. Else, it will produce a nice, string and
potentially translated value.'''
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 not formatted: 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?
toUnlink = []
while i < (res.startNumber + res.batchSize):
if i >= res.totalNumber: break
refUid = sortedUids[i]
refObject = self.uid_catalog(UID=refUid)[0].getObject()
i += 1
tool = self.getTool()
if refObject.meta_type != tool.getPortalType(appyType.klass):
toUnlink.append(refObject)
continue
if not ploneObjects:
refObject = refObject.appy()
res.objects.append(refObject)
# Unlink dummy linked objects
if toUnlink:
suffix = '%s%s' % (fieldName[0].upper(), fieldName[1:])
exec 'linkedObjects = self.get%s()' % suffix
for dummyObject in toUnlink:
linkedObjects.remove(dummyObject)
self.getProductConfig().logger.warn('DB error: Ref %s.%s ' \
'contains a %s instance "%s". It was removed.' % \
(self.meta_type, fieldName, dummyObject.meta_type,
dummyObject.getId()))
exec 'self.set%s(linkedObjects)' % suffix
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, name, startNumber=None):
'''Gets the objects linked to me through Ref field named p_name.
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(name)
if not appyType.isBack:
return self._appy_getRefs(name, ploneObjects=True,
startNumber=startNumber).__dict__
else:
# Note that pagination is not yet implemented for backward refs.
return SomeObjects(self.getBRefs(appyType.relationship)).__dict__
def getSelectableAppyRefs(self, name):
'''p_name is the name of a Ref field. This method returns the list of
all objects that can be selected to be linked as references to p_self
through field p_name.'''
appyType = self.getAppyType(name)
if not appyType.select:
# No select method has been defined: we must retrieve all objects
# of the referred type that the user is allowed to access.
return self.appy().search(appyType.klass)
else:
return appyType.select(self.appy())
xhtmlToText = re.compile('<.*?>', re.S)
def getReferenceLabel(self, name, refObject):
'''p_name is the name of a Ref field with link=True. I need to display,
on an edit view, the p_refObject in the listbox that will allow
the user to choose which object(s) to link through the Ref.
The information to display may only be the object title or more if
field.shownInfo is used.'''
appyType = self.getAppyType(name)
res = refObject.title
if 'title' in appyType.shownInfo:
# We may place it at another place
res = ''
for fieldName in appyType.shownInfo:
refType = refObject.o.getAppyType(fieldName)
value = getattr(refObject, fieldName)
value = refType.getFormattedValue(refObject.o, value)
if (refType.type == 'String') and (refType.format == 2):
value = self.xhtmlToText.sub(' ', value)
prefix = ''
if res:
prefix = ' | '
res += prefix + value
maxWidth = appyType.width or 30
if len(res) > maxWidth:
res = res[:maxWidth-2] + '...'
return res
def getReferenceUid(self, refObject):
'''Returns the UID of referred object p_refObject.'''
return refObject.o.UID()
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 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
res = phase['pages'][pageIndex-1]
show = phase['pageShows'][res]
return res, show
else:
if phase['previousPhase']:
# We go to the last page of previous phase
previousPhase = phase['previousPhase']
res = previousPhase['pages'][-1]
show = previousPhase['pageShows'][res]
return res, show
else:
return None, 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
res = phase['pages'][pageIndex+1]
show = phase['pageShows'][res]
return res, show
else:
if phase['nextPhase']:
# We go to the first page of next phase
nextPhase = phase['nextPhase']
res = nextPhase['pages'][0]
show = nextPhase['pageShows'][res]
return res, show
else:
return None, 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, Role):
2009-06-29 07:06:01 -05:00
# It is a role. Transition may be triggered if the user has this
# role.
res = user.has_role(transition.condition.name, self)
2009-06-29 07:06:01 -05:00
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)
def getFlavour(self):
'''Returns the flavour corresponding to this object.'''
return self.getTool().getFlavour(self.portal_type)
2009-06-29 07:06:01 -05:00
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
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 = dbValue
# 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, dbValue):
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 = dbValue
# Compare the value
return compValue
def dateValueSelected(self, fieldName, fieldPart, dateValue, dbValue):
'''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 = dbValue
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_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_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 = {} # One key for every add permission
addPermissions = self.getProductConfig().ADD_CONTENT_PERMISSIONS
for appyType in self.getAllAppyTypes():
if appyType.type != 'Ref': continue
if appyType.isBack or appyType.link: continue
# Indeed, no possibility to create objects with such Ref
refType = self.getTool().getPortalType(appyType.klass)
if refType not in addPermissions: continue
# Get roles that may add this content type
creators = getattr(appyType.klass, 'creators', None)
if not creators:
creators = self.getProductConfig().defaultAddRoles
# Add those creators to the list of creators for this meta_type
addPermission = addPermissions[refType]
if addPermission in allCreators:
allCreators[addPermission] = allCreators[\
addPermission].union(creators)
else:
allCreators[addPermission] = set(creators)
# Update the permissions
for permission, creators in allCreators.iteritems():
updateRolesForPermission(permission, tuple(creators), folder)
2009-06-29 07:06:01 -05:00
# Beyond content-type-specific "add" permissions, creators must also
# have the main permission "Add portal content".
permission = 'Add portal content'
for creators in allCreators.itervalues():
updateRolesForPermission(permission, tuple(creators), folder)
2009-06-29 07:06:01 -05:00
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
# ------------------------------------------------------------------------------