2009-06-29 07:06:01 -05:00
|
|
|
'''This package contains mixin classes that are mixed in with generated classes:
|
2011-12-05 08:11:29 -06:00
|
|
|
- mixins/BaseMixin is mixed in with standard Zope classes;
|
2010-10-14 07:43:56 -05:00
|
|
|
- mixins/ToolMixin is mixed in with the generated application Tool class.'''
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2011-06-28 02:12:20 -05:00
|
|
|
import os, os.path, sys, types, mimetypes, urllib, cgi
|
2011-10-26 03:21:09 -05:00
|
|
|
from appy import Object
|
2011-12-05 08:11:29 -06:00
|
|
|
import appy.gen as gen
|
2010-08-05 11:23:17 -05:00
|
|
|
from appy.gen.utils import *
|
2012-11-02 16:27:54 -05:00
|
|
|
from appy.gen.layout import Table, defaultPageLayouts, ColumnLayout
|
2011-12-05 08:11:29 -06:00
|
|
|
from appy.gen.descriptors import WorkflowDescriptor, ClassDescriptor
|
2012-11-23 08:20:12 -06:00
|
|
|
from appy.shared.utils import sequenceTypes, normalizeText, Traceback
|
2012-06-27 06:27:24 -05:00
|
|
|
from appy.shared.data import rtlLanguages
|
2012-11-23 08:20:12 -06:00
|
|
|
from appy.shared.xml_parser import XmlMarshaller
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2010-10-14 07:43:56 -05:00
|
|
|
class BaseMixin:
|
2011-11-25 11:01:20 -06:00
|
|
|
'''Every Zope class generated by appy.gen inherits from this class or a
|
|
|
|
subclass of it.'''
|
2010-10-14 07:43:56 -05:00
|
|
|
_appy_meta_type = 'Class'
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2010-10-29 07:36:36 -05:00
|
|
|
def get_o(self):
|
2010-11-22 02:36:14 -06:00
|
|
|
'''In some cases, we want the Zope object, we don't know if the current
|
2010-10-29 07:36:36 -05:00
|
|
|
object is a Zope or Appy object. By defining this property,
|
|
|
|
"someObject.o" produces always the Zope object, be someObject an Appy
|
|
|
|
or Zope object.'''
|
|
|
|
return self
|
|
|
|
o = property(get_o)
|
|
|
|
|
2012-06-02 07:36:49 -05:00
|
|
|
def getInitiatorInfo(self):
|
|
|
|
'''Gets information about a potential initiator object from the request.
|
|
|
|
Returns a 3-tuple (initiator, pageName, field):
|
|
|
|
* initiator is the initiator (Zope) object;
|
|
|
|
* pageName is the page on the initiator where the origin of the Ref
|
|
|
|
field lies;
|
|
|
|
* field is the Ref instance.
|
|
|
|
'''
|
2012-06-02 10:39:05 -05:00
|
|
|
rq = self.REQUEST
|
|
|
|
if not rq.get('nav', '').startswith('ref.'): return None, None, None
|
2012-06-02 07:36:49 -05:00
|
|
|
splitted = rq['nav'].split('.')
|
2012-06-02 10:39:05 -05:00
|
|
|
initiator = self.getTool().getObject(splitted[1])
|
2012-06-02 07:36:49 -05:00
|
|
|
fieldName, page = splitted[2].split(':')
|
2012-06-02 10:39:05 -05:00
|
|
|
return initiator, page, initiator.getAppyType(fieldName)
|
2012-06-02 07:36:49 -05:00
|
|
|
|
2011-10-11 10:32:23 -05:00
|
|
|
def createOrUpdate(self, created, values,
|
|
|
|
initiator=None, initiatorField=None):
|
2009-10-18 07:52:27 -05:00
|
|
|
'''This method creates (if p_created is True) or updates an object.
|
2010-08-05 11:23:17 -05:00
|
|
|
p_values are manipulated versions of those from the HTTP request.
|
2011-11-25 11:01:20 -06:00
|
|
|
In the case of an object creation from the web (p_created is True
|
|
|
|
and a REQUEST object is present), p_self is a temporary object
|
|
|
|
created in /temp_folder, and this method moves it at its "final"
|
|
|
|
place. In the case of an update, this method simply updates fields
|
|
|
|
of p_self.'''
|
|
|
|
rq = getattr(self, 'REQUEST', None)
|
2009-10-18 07:52:27 -05:00
|
|
|
obj = self
|
2011-11-25 11:01:20 -06:00
|
|
|
if created and rq:
|
|
|
|
# Create the final object and put it at the right place.
|
|
|
|
tool = self.getTool()
|
|
|
|
id = tool.generateUid(obj.portal_type)
|
|
|
|
if not initiator:
|
|
|
|
folder = tool.getPath('/data')
|
|
|
|
else:
|
2012-06-02 07:36:49 -05:00
|
|
|
folder = initiator.getCreateFolder()
|
|
|
|
# Check that the user can add objects through this Ref.
|
|
|
|
initiatorField.checkAdd(initiator)
|
2011-11-25 11:01:20 -06:00
|
|
|
obj = createObject(folder, id, obj.portal_type, tool.getAppName())
|
2009-12-14 13:22:55 -06:00
|
|
|
previousData = None
|
2011-11-25 11:01:20 -06:00
|
|
|
if not created: previousData = obj.rememberPreviousData()
|
|
|
|
# Perform the change on the object
|
|
|
|
if rq:
|
2010-08-05 11:23:17 -05:00
|
|
|
# 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)
|
2009-12-14 13:22:55 -06:00
|
|
|
if previousData:
|
|
|
|
# Keep in history potential changes on historized fields
|
2011-11-25 11:01:20 -06:00
|
|
|
obj.historizeData(previousData)
|
2009-07-28 03:14:40 -05:00
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
# Manage potential link with an initiator object
|
2012-06-02 07:36:49 -05:00
|
|
|
if created and initiator: initiator.appy().link(initiatorField.name,obj)
|
2010-10-14 07:43:56 -05:00
|
|
|
|
2011-10-06 09:27:58 -05:00
|
|
|
# Manage "add" permissions and reindex the object
|
|
|
|
obj._appy_managePermissions()
|
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
# Call the custom "onEdit" if available
|
2011-01-28 07:36:30 -06:00
|
|
|
msg = None # The message to display to the user. It can be set by onEdit
|
2009-10-18 07:52:27 -05:00
|
|
|
if obj.wrapperClass:
|
2010-08-05 11:23:17 -05:00
|
|
|
appyObject = obj.appy()
|
2011-01-28 07:36:30 -06:00
|
|
|
if hasattr(appyObject, 'onEdit'):
|
|
|
|
msg = appyObject.onEdit(created)
|
2012-11-05 03:21:27 -06:00
|
|
|
# Update last modification date
|
|
|
|
if not created:
|
|
|
|
from DateTime import DateTime
|
|
|
|
obj.modified = DateTime()
|
2011-11-25 11:01:20 -06:00
|
|
|
obj.reindex()
|
2011-01-28 07:36:30 -06:00
|
|
|
return obj, msg
|
2009-10-20 09:57:00 -05:00
|
|
|
|
|
|
|
def delete(self):
|
|
|
|
'''This methods is self's suicide.'''
|
2010-11-26 10:30:46 -06:00
|
|
|
# Call a custom "onDelete" if it exists
|
|
|
|
appyObj = self.appy()
|
|
|
|
if hasattr(appyObj, 'onDelete'): appyObj.onDelete()
|
2011-09-26 14:19:34 -05:00
|
|
|
# Any people referencing me must forget me now
|
|
|
|
for field in self.getAllAppyTypes():
|
|
|
|
if field.type != 'Ref': continue
|
|
|
|
for obj in field.getValue(self):
|
2011-10-04 13:12:58 -05:00
|
|
|
field.back.unlinkObject(obj, self, back=True)
|
2011-11-28 15:50:01 -06:00
|
|
|
# Uncatalog the object
|
|
|
|
self.reindex(unindex=True)
|
2010-11-26 10:30:46 -06:00
|
|
|
# Delete the object
|
2009-10-20 09:57:00 -05:00
|
|
|
self.getParentNode().manage_delObjects([self.id])
|
|
|
|
|
2010-11-22 02:36:14 -06:00
|
|
|
def onDelete(self):
|
2012-10-08 03:08:54 -05:00
|
|
|
'''Called when an object deletion is triggered from the ui.'''
|
2010-11-22 02:36:14 -06:00
|
|
|
rq = self.REQUEST
|
|
|
|
self.delete()
|
|
|
|
if self.getUrl(rq['HTTP_REFERER'],mode='raw') ==self.getUrl(mode='raw'):
|
|
|
|
# We were consulting the object that has been deleted. Go back to
|
|
|
|
# the main page.
|
|
|
|
urlBack = self.getTool().getSiteUrl()
|
|
|
|
else:
|
|
|
|
urlBack = self.getUrl(rq['HTTP_REFERER'])
|
2011-02-12 10:09:11 -06:00
|
|
|
self.say(self.translate('delete_done'))
|
2010-11-22 02:36:14 -06:00
|
|
|
self.goto(urlBack)
|
|
|
|
|
2012-10-08 03:08:54 -05:00
|
|
|
def onUnlink(self):
|
|
|
|
'''Called when an object unlinking is triggered from the ui.'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
tool = self.getTool()
|
|
|
|
sourceObject = tool.getObject(rq['sourceUid'])
|
|
|
|
targetObject = tool.getObject(rq['targetUid'])
|
|
|
|
field = sourceObject.getAppyType(rq['fieldName'])
|
|
|
|
field.unlinkObject(sourceObject, targetObject)
|
|
|
|
urlBack = self.getUrl(rq['HTTP_REFERER'])
|
|
|
|
self.say(self.translate('unlink_done'))
|
|
|
|
self.goto(urlBack)
|
|
|
|
|
2009-10-20 09:57:00 -05:00
|
|
|
def onCreate(self):
|
|
|
|
'''This method is called when a user wants to create a root object in
|
2011-11-25 11:01:20 -06:00
|
|
|
the "data" folder or an object through a reference field. A temporary
|
|
|
|
object is created in /temp_folder and the edit page to it is
|
|
|
|
returned.'''
|
2009-10-20 09:57:00 -05:00
|
|
|
rq = self.REQUEST
|
2011-11-25 11:01:20 -06:00
|
|
|
className = rq.get('className')
|
2010-09-19 08:04:44 -05:00
|
|
|
# Create the params to add to the URL we will redirect the user to
|
|
|
|
# create the object.
|
|
|
|
urlParams = {'mode':'edit', 'page':'main', 'nav':''}
|
2012-06-02 07:36:49 -05:00
|
|
|
initiator, initiatorPage, initiatorField = self.getInitiatorInfo()
|
|
|
|
if initiator:
|
2009-10-20 09:57:00 -05:00
|
|
|
# The object to create will be linked to an initiator object through
|
2012-06-02 07:36:49 -05:00
|
|
|
# a Ref field. We create here a new navigation string with one more
|
2010-09-19 08:04:44 -05:00
|
|
|
# item, that will be the currently created item.
|
|
|
|
splitted = rq.get('nav').split('.')
|
|
|
|
splitted[-1] = splitted[-2] = str(int(splitted[-1])+1)
|
|
|
|
urlParams['nav'] = '.'.join(splitted)
|
2012-06-02 07:36:49 -05:00
|
|
|
# Check that the user can add objects through this Ref field
|
2012-06-02 10:39:05 -05:00
|
|
|
initiatorField.checkAdd(initiator)
|
2011-11-25 11:01:20 -06:00
|
|
|
# Create a temp object in /temp_folder
|
|
|
|
tool = self.getTool()
|
|
|
|
id = tool.generateUid(className)
|
|
|
|
appName = tool.getAppName()
|
|
|
|
obj = createObject(tool.getPath('/temp_folder'), id, className, appName)
|
|
|
|
return self.goto(obj.getUrl(**urlParams))
|
2009-10-20 09:57:00 -05:00
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
def onCreateWithoutForm(self):
|
|
|
|
'''This method is called when a user wants to create a object from a
|
|
|
|
reference field, automatically (without displaying a form).'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
self.appy().create(rq['fieldName'])
|
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
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))
|
2011-10-19 02:37:44 -05:00
|
|
|
# Validate sub-fields within Lists
|
|
|
|
if appyType.type != 'List': continue
|
|
|
|
i = -1
|
|
|
|
for row in value:
|
|
|
|
i += 1
|
|
|
|
for name, field in appyType.fields:
|
|
|
|
message = field.validate(self, getattr(row,name,None))
|
|
|
|
if message:
|
|
|
|
setattr(errors, '%s*%d' % (field.name, i), message)
|
2010-08-05 11:23:17 -05:00
|
|
|
|
|
|
|
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
|
2011-02-06 10:39:36 -06:00
|
|
|
msg = obj.validate(values, errors)
|
2010-10-14 07:43:56 -05:00
|
|
|
# Those custom validation methods may have added fields in the given
|
2010-08-05 11:23:17 -05:00
|
|
|
# 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):
|
2010-11-16 08:32:47 -06:00
|
|
|
resValue = self.translate('field_invalid')
|
2010-08-05 11:23:17 -05:00
|
|
|
setattr(errors, key, resValue)
|
2011-02-06 10:39:36 -06:00
|
|
|
return msg
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2009-10-18 07:52:27 -05:00
|
|
|
def onUpdate(self):
|
|
|
|
'''This method is executed when a user wants to update an object.
|
2011-11-25 11:01:20 -06:00
|
|
|
The object may be a temporary object created in /temp_folder.
|
|
|
|
In this case, the update consists in moving it to its "final" place.
|
|
|
|
If the object is not a temporary one, this method updates its
|
|
|
|
fields in the database.'''
|
2009-10-18 07:52:27 -05:00
|
|
|
rq = self.REQUEST
|
2010-11-22 02:36:14 -06:00
|
|
|
tool = self.getTool()
|
2012-06-03 11:34:56 -05:00
|
|
|
errorMessage = self.translate('validation_error')
|
2010-08-05 11:23:17 -05:00
|
|
|
isNew = rq.get('is_new') == 'True'
|
2011-09-20 12:21:48 -05:00
|
|
|
# If this object is created from an initiator, get info about him.
|
2012-06-02 07:36:49 -05:00
|
|
|
initiator, initiatorPage, initiatorField = self.getInitiatorInfo()
|
2011-09-20 12:21:48 -05:00
|
|
|
# If the user clicked on 'Cancel', go back to the previous page.
|
2010-08-05 11:23:17 -05:00
|
|
|
if rq.get('buttonCancel.x', None):
|
2011-09-20 12:21:48 -05:00
|
|
|
if initiator:
|
|
|
|
# Go back to the initiator page.
|
|
|
|
urlBack = initiator.getUrl(page=initiatorPage, nav='')
|
2009-10-25 15:42:08 -05:00
|
|
|
else:
|
2011-09-20 12:21:48 -05:00
|
|
|
if isNew:
|
|
|
|
# Go back to the root of the site.
|
|
|
|
urlBack = tool.getSiteUrl()
|
|
|
|
else:
|
|
|
|
urlBack = self.getUrl()
|
2012-06-03 11:34:56 -05:00
|
|
|
self.say(self.translate('object_canceled'))
|
2010-09-17 08:32:48 -05:00
|
|
|
return self.goto(urlBack)
|
2009-10-18 07:52:27 -05:00
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
# Object for storing validation errors
|
2011-10-26 03:21:09 -05:00
|
|
|
errors = Object()
|
2010-08-05 11:23:17 -05:00
|
|
|
# Object for storing the (converted) values from the request
|
2011-10-26 03:21:09 -05:00
|
|
|
values = Object()
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2009-10-18 07:52:27 -05:00
|
|
|
# Trigger field-specific validation
|
2010-08-05 11:23:17 -05:00
|
|
|
self.intraFieldValidation(errors, values)
|
|
|
|
if errors.__dict__:
|
|
|
|
rq.set('errors', errors.__dict__)
|
2011-02-12 10:09:11 -06:00
|
|
|
self.say(errorMessage)
|
2011-02-15 13:15:58 -06:00
|
|
|
return self.gotoEdit()
|
2010-08-05 11:23:17 -05:00
|
|
|
|
|
|
|
# Trigger inter-field validation
|
2011-02-06 10:39:36 -06:00
|
|
|
msg = self.interFieldValidation(errors, values)
|
|
|
|
if not msg: msg = errorMessage
|
2010-08-05 11:23:17 -05:00
|
|
|
if errors.__dict__:
|
|
|
|
rq.set('errors', errors.__dict__)
|
2011-02-12 10:09:11 -06:00
|
|
|
self.say(msg)
|
2011-02-15 13:15:58 -06:00
|
|
|
return self.gotoEdit()
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2010-11-22 02:36:14 -06:00
|
|
|
# Before saving data, must we ask a confirmation by the user ?
|
|
|
|
appyObj = self.appy()
|
|
|
|
saveConfirmed = rq.get('confirmed') == 'True'
|
|
|
|
if hasattr(appyObj, 'confirm') and not saveConfirmed:
|
|
|
|
msg = appyObj.confirm(values)
|
|
|
|
if msg:
|
|
|
|
rq.set('confirmMsg', msg.replace("'", "\\'"))
|
2011-02-15 13:15:58 -06:00
|
|
|
return self.gotoEdit()
|
2010-11-22 02:36:14 -06:00
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
# Create or update the object in the database
|
2011-10-11 10:32:23 -05:00
|
|
|
obj, msg = self.createOrUpdate(isNew, values, initiator, initiatorField)
|
2010-08-05 11:23:17 -05:00
|
|
|
|
|
|
|
# Redirect the user to the appropriate page
|
2012-06-03 11:34:56 -05:00
|
|
|
if not msg: msg = self.translate('object_saved')
|
2011-01-28 07:36:30 -06:00
|
|
|
# If the object has already been deleted (ie, it is a kind of transient
|
|
|
|
# object like a one-shot form and has already been deleted in method
|
|
|
|
# onEdit), redirect to the main site page.
|
2011-10-11 10:32:23 -05:00
|
|
|
if not getattr(obj.getParentNode().aq_base, obj.id, None):
|
2011-01-28 07:36:30 -06:00
|
|
|
return self.goto(tool.getSiteUrl(), msg)
|
|
|
|
# If the user can't access the object anymore, redirect him to the
|
|
|
|
# main site page.
|
2011-09-08 09:33:16 -05:00
|
|
|
if not obj.allows('View'):
|
2011-01-28 07:36:30 -06:00
|
|
|
return self.goto(tool.getSiteUrl(), msg)
|
2010-11-22 02:36:14 -06:00
|
|
|
if rq.get('buttonOk.x', None) or saveConfirmed:
|
2011-02-12 10:09:11 -06:00
|
|
|
obj.say(msg)
|
2011-09-20 12:21:48 -05:00
|
|
|
if isNew and initiator:
|
|
|
|
return self.goto(initiator.getUrl(page=initiatorPage, nav=''))
|
|
|
|
else:
|
|
|
|
return self.goto(obj.getUrl())
|
2010-08-05 11:23:17 -05:00
|
|
|
if rq.get('buttonPrevious.x', None):
|
2010-09-13 14:04:10 -05:00
|
|
|
# Go to the previous page for this object.
|
2010-08-05 11:23:17 -05:00
|
|
|
# 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
|
2010-09-13 14:04:10 -05:00
|
|
|
# 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.
|
2010-10-19 03:47:42 -05:00
|
|
|
phaseInfo = self.getAppyPhases(currentOnly=True, layoutType='edit')
|
|
|
|
pageName, pageInfo = self.getPreviousPage(phaseInfo, rq['page'])
|
|
|
|
if pageName:
|
2010-09-17 08:32:48 -05:00
|
|
|
# Return to the edit or view page?
|
2010-10-19 03:47:42 -05:00
|
|
|
if pageInfo['showOnEdit']:
|
|
|
|
rq.set('page', pageName)
|
2011-02-15 13:15:58 -06:00
|
|
|
return obj.gotoEdit()
|
2010-09-13 14:04:10 -05:00
|
|
|
else:
|
2010-10-19 03:47:42 -05:00
|
|
|
return self.goto(obj.getUrl(page=pageName))
|
2009-10-18 07:52:27 -05:00
|
|
|
else:
|
2011-02-12 10:09:11 -06:00
|
|
|
obj.say(msg)
|
2010-09-17 08:32:48 -05:00
|
|
|
return self.goto(obj.getUrl())
|
2010-08-05 11:23:17 -05:00
|
|
|
if rq.get('buttonNext.x', None):
|
2010-09-13 14:04:10 -05:00
|
|
|
# Go to the next page for this object
|
2010-11-26 10:30:46 -06:00
|
|
|
# We remember page name, because the next method may set a new
|
|
|
|
# current page if the current one is not visible anymore.
|
|
|
|
pageName = rq['page']
|
2010-10-19 03:47:42 -05:00
|
|
|
phaseInfo = self.getAppyPhases(currentOnly=True, layoutType='edit')
|
2010-11-26 10:30:46 -06:00
|
|
|
pageName, pageInfo = self.getNextPage(phaseInfo, pageName)
|
2010-10-19 03:47:42 -05:00
|
|
|
if pageName:
|
2010-09-17 08:32:48 -05:00
|
|
|
# Return to the edit or view page?
|
2010-10-19 03:47:42 -05:00
|
|
|
if pageInfo['showOnEdit']:
|
|
|
|
rq.set('page', pageName)
|
2011-02-15 13:15:58 -06:00
|
|
|
return obj.gotoEdit()
|
2010-09-13 14:04:10 -05:00
|
|
|
else:
|
2010-10-19 03:47:42 -05:00
|
|
|
return self.goto(obj.getUrl(page=pageName))
|
2010-08-05 11:23:17 -05:00
|
|
|
else:
|
2011-02-12 10:09:11 -06:00
|
|
|
obj.say(msg)
|
2010-09-17 08:32:48 -05:00
|
|
|
return self.goto(obj.getUrl())
|
2011-02-15 13:15:58 -06:00
|
|
|
return obj.gotoEdit()
|
2009-07-28 03:14:40 -05:00
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def reindex(self, indexes=None, unindex=False):
|
|
|
|
'''Reindexes this object the catalog. If names of indexes are specified
|
|
|
|
in p_indexes, recataloging is limited to those indexes. If p_unindex
|
|
|
|
is True, instead of cataloguing the object, it uncatalogs it.'''
|
2012-03-27 08:49:41 -05:00
|
|
|
path = '/'.join(self.getPhysicalPath())
|
2011-11-25 11:01:20 -06:00
|
|
|
catalog = self.getPhysicalRoot().catalog
|
|
|
|
if unindex:
|
2012-03-27 08:49:41 -05:00
|
|
|
catalog.uncatalog_object(path)
|
2011-11-25 11:01:20 -06:00
|
|
|
else:
|
2011-11-28 15:50:01 -06:00
|
|
|
if indexes:
|
2012-03-27 08:49:41 -05:00
|
|
|
catalog.catalog_object(self, path, idxs=indexes)
|
2011-11-28 15:50:01 -06:00
|
|
|
else:
|
2012-09-04 11:00:22 -05:00
|
|
|
# Get the list of indexes that apply on this object. Else, Zope
|
|
|
|
# will reindex all indexes defined in the catalog, and through
|
|
|
|
# acquisition, wrong methods can be called on wrong objects.
|
|
|
|
iNames = self.wrapperClass.getIndexes().keys()
|
|
|
|
catalog.catalog_object(self, path, idxs=iNames)
|
2011-11-25 11:01:20 -06:00
|
|
|
|
2012-11-23 08:20:12 -06:00
|
|
|
def xml(self, action=None):
|
|
|
|
'''If no p_action is defined, this method returns the XML version of
|
|
|
|
this object. Else, it calls method named p_action on the
|
|
|
|
corresponding Appy wrapper and returns, as XML, the its result.'''
|
|
|
|
self.REQUEST.RESPONSE.setHeader('Content-Type','text/xml;charset=utf-8')
|
|
|
|
# Check if the user is allowed to consult this object
|
|
|
|
if not self.allows('View'):
|
|
|
|
return XmlMarshaller().marshall('Unauthorized')
|
|
|
|
if not action:
|
|
|
|
marshaller = XmlMarshaller(rootTag=self.getClass().__name__,
|
|
|
|
dumpUnicode=True)
|
|
|
|
res = marshaller.marshall(self, objectType='appy')
|
|
|
|
else:
|
|
|
|
appyObj = self.appy()
|
|
|
|
try:
|
|
|
|
methodRes = getattr(appyObj, action)()
|
2012-11-26 06:58:27 -06:00
|
|
|
res = XmlMarshaller().marshall(methodRes, objectType='appy')
|
2012-11-23 08:20:12 -06:00
|
|
|
except Exception, e:
|
2012-11-26 06:58:27 -06:00
|
|
|
tb = Traceback.get()
|
|
|
|
res = XmlMarshaller().marshall(tb, objectType='appy')
|
2012-11-23 08:20:12 -06:00
|
|
|
return res
|
|
|
|
|
2011-02-12 10:09:11 -06:00
|
|
|
def say(self, msg, type='info'):
|
|
|
|
'''Prints a p_msg in the user interface. p_logLevel may be "info",
|
|
|
|
"warning" or "error".'''
|
2011-11-25 11:01:20 -06:00
|
|
|
rq = self.REQUEST
|
2011-12-05 11:15:45 -06:00
|
|
|
if 'messages' not in rq.SESSION.keys():
|
|
|
|
plist = self.getProductConfig().PersistentList
|
|
|
|
messages = rq.SESSION['messages'] = plist()
|
2011-11-25 11:01:20 -06:00
|
|
|
else:
|
2011-12-05 11:15:45 -06:00
|
|
|
messages = rq.SESSION['messages']
|
|
|
|
messages.append( (type, msg) )
|
2011-02-12 10:09:11 -06:00
|
|
|
|
|
|
|
def log(self, msg, type='info'):
|
|
|
|
'''Logs a p_msg in the log file. p_logLevel may be "info", "warning"
|
|
|
|
or "error".'''
|
|
|
|
logger = self.getProductConfig().logger
|
|
|
|
if type == 'warning': logMethod = logger.warn
|
|
|
|
elif type == 'error': logMethod = logger.error
|
|
|
|
else: logMethod = logger.info
|
|
|
|
logMethod(msg)
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def do(self):
|
|
|
|
'''Performs some action from the user interface.'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
action = rq['action']
|
|
|
|
if rq.get('objectUid', None):
|
|
|
|
obj = self.getTool().getObject(rq['objectUid'])
|
2011-09-08 09:33:16 -05:00
|
|
|
else:
|
2011-11-25 11:01:20 -06:00
|
|
|
obj = self
|
2012-05-09 02:45:15 -05:00
|
|
|
if rq.get('appy', None) == '1': obj = obj.appy()
|
|
|
|
return getattr(obj, 'on'+action)()
|
2011-07-26 15:15:04 -05:00
|
|
|
|
2009-12-14 13:22:55 -06:00
|
|
|
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 = {}
|
2010-08-05 11:23:17 -05:00
|
|
|
for appyType in self.getAllAppyTypes():
|
|
|
|
if appyType.historized:
|
2010-10-14 07:43:56 -05:00
|
|
|
res[appyType.name] = appyType.getValue(self)
|
2009-12-14 13:22:55 -06:00
|
|
|
return res
|
|
|
|
|
2011-09-08 09:33:16 -05:00
|
|
|
def addHistoryEvent(self, action, **kw):
|
|
|
|
'''Adds an event in the object history.'''
|
2011-11-25 11:01:20 -06:00
|
|
|
userId = self.getUser().getId()
|
2011-09-08 09:33:16 -05:00
|
|
|
from DateTime import DateTime
|
|
|
|
event = {'action': action, 'actor': userId, 'time': DateTime(),
|
|
|
|
'comments': ''}
|
|
|
|
event.update(kw)
|
2011-11-25 11:01:20 -06:00
|
|
|
if 'review_state' not in event: event['review_state'] = self.State()
|
2011-09-08 09:33:16 -05:00
|
|
|
# Add the event to the history
|
|
|
|
histKey = self.workflow_history.keys()[0]
|
|
|
|
self.workflow_history[histKey] += (event,)
|
|
|
|
|
2010-12-17 07:46:55 -06:00
|
|
|
def addDataChange(self, changes, notForPreviouslyEmptyValues=False):
|
2010-01-14 01:56:04 -06:00
|
|
|
'''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
|
2010-12-17 07:46:55 -06:00
|
|
|
a field. The method is also called by m_historizeData below, that
|
|
|
|
performs "automatic" recording when a HTTP form is uploaded. Field
|
|
|
|
changes for which the previous value was empty are not recorded into
|
|
|
|
the history if p_notForPreviouslyEmptyValues is True.'''
|
2010-10-14 07:43:56 -05:00
|
|
|
# Add to the p_changes dict the field labels
|
2010-12-17 07:46:55 -06:00
|
|
|
for fieldName in changes.keys():
|
2010-10-14 07:43:56 -05:00
|
|
|
appyType = self.getAppyType(fieldName)
|
2010-12-17 07:46:55 -06:00
|
|
|
if notForPreviouslyEmptyValues and \
|
|
|
|
appyType.isEmptyValue(changes[fieldName], self):
|
|
|
|
del changes[fieldName]
|
|
|
|
else:
|
|
|
|
changes[fieldName] = (changes[fieldName], appyType.labelId)
|
2011-09-08 09:33:16 -05:00
|
|
|
# Add an event in the history
|
|
|
|
self.addHistoryEvent('_datachange_', changes=changes)
|
2010-01-14 01:56:04 -06:00
|
|
|
|
2009-12-14 13:22:55 -06:00
|
|
|
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
|
2010-10-14 07:43:56 -05:00
|
|
|
for field in previousData.keys():
|
|
|
|
prev = previousData[field]
|
|
|
|
appyType = self.getAppyType(field)
|
|
|
|
curr = appyType.getValue(self)
|
2011-03-22 10:45:46 -05:00
|
|
|
try:
|
|
|
|
if (prev == curr) or ((prev == None) and (curr == '')) or \
|
|
|
|
((prev == '') and (curr == None)):
|
|
|
|
del previousData[field]
|
|
|
|
except UnicodeDecodeError, ude:
|
|
|
|
# The string comparisons above may imply silent encoding-related
|
|
|
|
# conversions that may produce this exception.
|
|
|
|
pass
|
2010-10-14 07:43:56 -05:00
|
|
|
if (appyType.type == 'Ref') and (field in previousData):
|
2010-10-29 07:36:36 -05:00
|
|
|
previousData[field] = [r.title for r in previousData[field]]
|
2009-12-14 13:22:55 -06:00
|
|
|
if previousData:
|
2010-10-14 07:43:56 -05:00
|
|
|
self.addDataChange(previousData)
|
2009-12-14 13:22:55 -06:00
|
|
|
|
2011-01-28 07:36:30 -06:00
|
|
|
def goto(self, url, msg=None):
|
2009-10-25 15:42:08 -05:00
|
|
|
'''Brings the user to some p_url after an action has been executed.'''
|
2011-12-05 11:15:45 -06:00
|
|
|
if msg: self.say(msg)
|
2010-09-17 08:32:48 -05:00
|
|
|
return self.REQUEST.RESPONSE.redirect(url)
|
2009-10-25 15:42:08 -05:00
|
|
|
|
2011-02-15 13:15:58 -06:00
|
|
|
def gotoEdit(self):
|
|
|
|
'''Brings the user to the edit page for this object. This method takes
|
|
|
|
care of not carrying any password value. Unlike m_goto above, there
|
|
|
|
is no HTTP redirect here: we execute directly macro "edit" and we
|
|
|
|
return the result.'''
|
|
|
|
page = self.REQUEST.get('page', 'main')
|
|
|
|
for field in self.getAppyTypes('edit', page):
|
2012-02-16 11:13:51 -06:00
|
|
|
if (field.type == 'String') and (field.format in (3,4)):
|
2011-02-15 13:15:58 -06:00
|
|
|
self.REQUEST.set(field.name, '')
|
2011-11-25 11:01:20 -06:00
|
|
|
return self.ui.edit(self)
|
2011-02-15 13:15:58 -06:00
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
def showField(self, name, layoutType='view'):
|
|
|
|
'''Must I show field named p_name on this p_layoutType ?'''
|
2011-02-01 04:09:54 -06:00
|
|
|
if name == 'state': return False
|
2010-08-05 11:23:17 -05:00
|
|
|
return self.getAppyType(name).isShowable(self, layoutType)
|
|
|
|
|
|
|
|
def getMethod(self, methodName):
|
|
|
|
'''Returns the method named p_methodName.'''
|
2011-10-11 10:32:23 -05:00
|
|
|
# If I write "self.aq_base" instead of self, acquisition will be
|
|
|
|
# broken on returned object.
|
2010-08-05 11:23:17 -05:00
|
|
|
return getattr(self, methodName, None)
|
|
|
|
|
2012-06-02 07:36:49 -05:00
|
|
|
def getCreateFolder(self):
|
|
|
|
'''When an object must be created from this one through a Ref field, we
|
|
|
|
must know where to put the newly create object: within this one if it
|
|
|
|
is folderish, besides this one in its parent else.
|
|
|
|
'''
|
|
|
|
if self.isPrincipiaFolderish: return self
|
|
|
|
return self.getParentNode()
|
|
|
|
|
2011-10-26 03:21:09 -05:00
|
|
|
def getFieldValue(self, name, onlyIfSync=False, layoutType=None,
|
|
|
|
outerValue=None):
|
2010-09-17 08:32:48 -05:00
|
|
|
'''Returns the database value of field named p_name for p_self.
|
|
|
|
If p_onlyIfSync is True, it returns the value only if appyType can be
|
|
|
|
retrieved in synchronous mode.'''
|
|
|
|
appyType = self.getAppyType(name)
|
|
|
|
if not onlyIfSync or (onlyIfSync and appyType.sync[layoutType]):
|
2011-10-19 02:37:44 -05:00
|
|
|
# We must really get the field value.
|
|
|
|
if '*' not in name: return appyType.getValue(self)
|
|
|
|
# The field is an inner field from a List.
|
|
|
|
listName, name, i = name.split('*')
|
2011-10-26 03:21:09 -05:00
|
|
|
listType = self.getAppyType(listName)
|
|
|
|
return listType.getInnerValue(outerValue, name, int(i))
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2010-09-17 02:27:14 -05:00
|
|
|
def getFormattedFieldValue(self, name, value):
|
|
|
|
'''Gets a nice, string representation of p_value which is a value from
|
|
|
|
field named p_name.'''
|
|
|
|
return self.getAppyType(name).getFormattedValue(self, value)
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2011-10-19 02:37:44 -05:00
|
|
|
def getRequestFieldValue(self, name):
|
|
|
|
'''Gets the value of field p_name as may be present in the request.'''
|
|
|
|
# Return the request value for standard fields.
|
|
|
|
if '*' not in name:
|
|
|
|
return self.getAppyType(name).getRequestValue(self.REQUEST)
|
|
|
|
# For sub-fields within Lists, the corresponding request values have
|
|
|
|
# already been computed in the request key corresponding to the whole
|
|
|
|
# List.
|
|
|
|
listName, name, rowIndex = name.split('*')
|
|
|
|
rowIndex = int(rowIndex)
|
|
|
|
if rowIndex == -1: return ''
|
|
|
|
allValues = self.REQUEST.get(listName)
|
|
|
|
if not allValues: return ''
|
|
|
|
return getattr(allValues[rowIndex], name, '')
|
|
|
|
|
2011-10-11 10:32:23 -05:00
|
|
|
def getFileInfo(self, fileObject):
|
|
|
|
'''Returns filename and size of p_fileObject.'''
|
|
|
|
if not fileObject: return {'filename': '', 'size': 0}
|
|
|
|
return {'filename': fileObject.filename, 'size': fileObject.size}
|
|
|
|
|
2010-08-27 01:59:53 -05:00
|
|
|
def getAppyRefs(self, name, startNumber=None):
|
|
|
|
'''Gets the objects linked to me through Ref field named p_name.
|
2009-10-25 15:42:08 -05:00
|
|
|
If p_startNumber is None, this method returns all referred objects.
|
2010-10-14 07:43:56 -05:00
|
|
|
If p_startNumber is a number, this method will return
|
|
|
|
appyType.maxPerPage objects, starting at p_startNumber.'''
|
2011-09-26 14:19:34 -05:00
|
|
|
field = self.getAppyType(name)
|
|
|
|
return field.getValue(self, type='zobjects', someObjects=True,
|
|
|
|
startNumber=startNumber).__dict__
|
2010-08-27 01:59:53 -05:00
|
|
|
|
|
|
|
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)
|
2012-09-17 14:11:54 -05:00
|
|
|
def getReferenceLabel(self, name, refObject, className=None):
|
2010-08-27 01:59:53 -05:00
|
|
|
'''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.'''
|
2012-09-17 14:11:54 -05:00
|
|
|
appyType = self.getAppyType(name, className=className)
|
|
|
|
res = ''
|
2010-08-27 01:59:53 -05:00
|
|
|
for fieldName in appyType.shownInfo:
|
|
|
|
refType = refObject.o.getAppyType(fieldName)
|
|
|
|
value = getattr(refObject, fieldName)
|
|
|
|
value = refType.getFormattedValue(refObject.o, value)
|
2011-01-14 02:06:25 -06:00
|
|
|
if refType.type == 'String':
|
|
|
|
if refType.format == 2:
|
|
|
|
value = self.xhtmlToText.sub(' ', value)
|
|
|
|
elif type(value) in sequenceTypes:
|
|
|
|
value = ', '.join(value)
|
2010-08-27 01:59:53 -05:00
|
|
|
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.'''
|
2011-10-11 10:32:23 -05:00
|
|
|
refs = getattr(self.aq_base, fieldName, None)
|
2011-09-26 14:19:34 -05:00
|
|
|
if not refs: raise IndexError()
|
|
|
|
return refs.index(obj.UID())
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2012-06-02 07:36:49 -05:00
|
|
|
def mayAddReference(self, name):
|
2012-03-27 08:49:41 -05:00
|
|
|
'''May the user add references via Ref field named p_name in
|
|
|
|
p_folder?'''
|
2012-06-02 07:36:49 -05:00
|
|
|
return self.getAppyType(name).mayAdd(self)
|
2012-03-27 08:49:41 -05:00
|
|
|
|
2010-11-13 10:54:08 -06:00
|
|
|
def isDebug(self):
|
|
|
|
'''Are we in debug mode ?'''
|
|
|
|
for arg in sys.argv:
|
|
|
|
if arg == 'debug-mode=on': return True
|
|
|
|
|
|
|
|
def getClass(self, reloaded=False):
|
|
|
|
'''Returns the Appy class that dictates self's behaviour.'''
|
|
|
|
if not reloaded:
|
|
|
|
return self.getTool().getAppyClass(self.__class__.__name__)
|
|
|
|
else:
|
|
|
|
klass = self.appy().klass
|
|
|
|
moduleName = klass.__module__
|
|
|
|
exec 'import %s' % moduleName
|
|
|
|
exec 'reload(%s)' % moduleName
|
|
|
|
exec 'res = %s.%s' % (moduleName, klass.__name__)
|
2010-11-16 08:32:47 -06:00
|
|
|
# More manipulations may have occurred in m_update
|
|
|
|
if hasattr(res, 'update'):
|
2011-12-05 03:52:18 -06:00
|
|
|
parentName= res.__bases__[-1].__name__
|
|
|
|
moduleName= 'Products.%s.wrappers' % self.getTool().getAppName()
|
2010-11-16 08:32:47 -06:00
|
|
|
exec 'import %s' % moduleName
|
|
|
|
exec 'parent = %s.%s' % (moduleName, parentName)
|
|
|
|
res.update(parent)
|
2010-11-13 10:54:08 -06:00
|
|
|
return res
|
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
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.'''
|
2011-10-19 02:37:44 -05:00
|
|
|
isInnerType = '*' in name # An inner type lies within a List type.
|
|
|
|
subName = None
|
2012-03-01 10:35:23 -06:00
|
|
|
if isInnerType:
|
|
|
|
elems = name.split('*')
|
|
|
|
if len(elems) == 2: name, subName = elems
|
|
|
|
else: name, subName, i = elems
|
2011-09-14 14:01:58 -05:00
|
|
|
if not className:
|
|
|
|
klass = self.__class__.wrapperClass
|
|
|
|
else:
|
|
|
|
klass = self.getTool().getAppyClass(className, wrapper=True)
|
|
|
|
res = getattr(klass, name, None)
|
2011-10-19 02:37:44 -05:00
|
|
|
if res and isInnerType: res = res.getField(subName)
|
2011-09-14 14:01:58 -05:00
|
|
|
if res and asDict: return res.__dict__
|
|
|
|
return res
|
2010-08-05 11:23:17 -05:00
|
|
|
|
|
|
|
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.'''
|
2011-09-14 14:01:58 -05:00
|
|
|
if not className:
|
|
|
|
klass = self.__class__.wrapperClass
|
|
|
|
else:
|
|
|
|
klass = self.getTool().getAppyClass(className, wrapper=True)
|
|
|
|
return klass.__fields__
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2012-05-05 10:04:19 -05:00
|
|
|
def getGroupedAppyTypes(self, layoutType, pageName, cssJs=None):
|
2010-08-05 11:23:17 -05:00
|
|
|
'''Returns the fields sorted by group. For every field, the appyType
|
2012-10-03 07:44:34 -05:00
|
|
|
(dict version) is given. If a dict is given in p_cssJs, we will add
|
|
|
|
it in the css and js files required by the fields.'''
|
2009-06-29 07:06:01 -05:00
|
|
|
res = []
|
2010-08-05 11:23:17 -05:00
|
|
|
groups = {} # The already encountered groups
|
2012-05-05 10:04:19 -05:00
|
|
|
# If a dict is given in p_cssJs, we must fill it with the CSS and JS
|
|
|
|
# files required for every returned appyType.
|
|
|
|
collectCssJs = isinstance(cssJs, dict)
|
|
|
|
css = js = None
|
2011-09-14 14:01:58 -05:00
|
|
|
# If param "refresh" is there, we must reload the Python class
|
|
|
|
refresh = ('refresh' in self.REQUEST)
|
|
|
|
if refresh:
|
2010-11-13 10:54:08 -06:00
|
|
|
klass = self.getClass(reloaded=True)
|
2010-08-05 11:23:17 -05:00
|
|
|
for appyType in self.getAllAppyTypes():
|
2011-09-14 14:01:58 -05:00
|
|
|
if refresh: appyType = appyType.reload(klass, self)
|
2010-10-19 03:47:42 -05:00
|
|
|
if appyType.page.name != pageName: continue
|
2010-08-05 11:23:17 -05:00
|
|
|
if not appyType.isShowable(self, layoutType): continue
|
2012-05-05 10:04:19 -05:00
|
|
|
if collectCssJs:
|
|
|
|
if css == None: css = []
|
|
|
|
appyType.getCss(layoutType, css)
|
|
|
|
if js == None: js = []
|
|
|
|
appyType.getJs(layoutType, js)
|
2010-08-05 11:23:17 -05:00
|
|
|
if not appyType.group:
|
|
|
|
res.append(appyType.__dict__)
|
2009-06-29 07:06:01 -05:00
|
|
|
else:
|
2010-08-05 11:23:17 -05:00
|
|
|
# Insert the GroupDescr instance corresponding to
|
|
|
|
# appyType.group at the right place
|
|
|
|
groupDescr = appyType.group.insertInto(res, groups,
|
2011-11-28 15:50:01 -06:00
|
|
|
appyType.page, self.meta_type)
|
2010-08-05 11:23:17 -05:00
|
|
|
GroupDescr.addWidget(groupDescr, appyType.__dict__)
|
2012-05-05 10:04:19 -05:00
|
|
|
if collectCssJs:
|
|
|
|
cssJs['css'] = css
|
|
|
|
cssJs['js'] = js
|
2009-06-29 07:06:01 -05:00
|
|
|
return res
|
|
|
|
|
2010-10-19 03:47:42 -05:00
|
|
|
def getAppyTypes(self, layoutType, pageName):
|
2012-11-23 08:20:12 -06:00
|
|
|
'''Returns the list of fields that belong to a given page (p_pageName)
|
|
|
|
for a given p_layoutType. If p_pageName is None, fields of all pages
|
|
|
|
are returned.'''
|
2009-06-29 07:06:01 -05:00
|
|
|
res = []
|
2012-11-23 08:20:12 -06:00
|
|
|
for field in self.getAllAppyTypes():
|
|
|
|
if pageName and (field.page.name != pageName): continue
|
|
|
|
if not field.isShowable(self, layoutType): continue
|
|
|
|
res.append(field)
|
2009-06-29 07:06:01 -05:00
|
|
|
return res
|
|
|
|
|
2012-05-05 10:04:19 -05:00
|
|
|
def getCssJs(self, fields, layoutType, res):
|
|
|
|
'''Gets, in p_res ~{'css':[s_css], 'js':[s_js]}~ the lists of
|
|
|
|
Javascript and CSS files required by Appy types p_fields when shown
|
|
|
|
on p_layoutType.'''
|
2012-03-01 10:35:23 -06:00
|
|
|
# Lists css and js below are not sets, because order of Javascript
|
2010-12-17 07:46:55 -06:00
|
|
|
# inclusion can be important, and this could be losed by using sets.
|
2010-08-05 11:23:17 -05:00
|
|
|
css = []
|
|
|
|
js = []
|
2010-12-17 07:46:55 -06:00
|
|
|
for field in fields:
|
2012-05-05 10:04:19 -05:00
|
|
|
field.getCss(layoutType, css)
|
|
|
|
field.getJs(layoutType, js)
|
|
|
|
res['css'] = css
|
|
|
|
res['js'] = js
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2012-11-02 16:27:54 -05:00
|
|
|
def getColumnsSpecifiers(self, columnLayouts, dir):
|
|
|
|
'''Extracts and returns, from a list of p_columnLayouts, the information
|
|
|
|
that is necessary for displaying a column in a result screen or for
|
|
|
|
a Ref field.'''
|
2010-12-06 04:11:40 -06:00
|
|
|
res = []
|
2012-11-02 16:27:54 -05:00
|
|
|
tool = self.getTool()
|
|
|
|
for info in columnLayouts:
|
|
|
|
fieldName, width, align = ColumnLayout(info).get()
|
|
|
|
align = tool.flipLanguageDirection(align, dir)
|
|
|
|
field = self.getAppyType(fieldName, asDict=True)
|
|
|
|
if not field:
|
|
|
|
self.log('Field "%s", used in a column specifier, was not ' \
|
|
|
|
'found.' % fieldName, type='warning')
|
2010-12-06 04:11:40 -06:00
|
|
|
else:
|
2012-11-02 16:27:54 -05:00
|
|
|
res.append({'field':field, 'width':width, 'align': align})
|
2010-12-06 04:11:40 -06:00
|
|
|
return res
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2011-02-01 04:09:54 -06:00
|
|
|
def getAppyTransitions(self, includeFake=True, includeNotShowable=False):
|
2011-07-26 15:15:04 -05:00
|
|
|
'''This method returns info about transitions that one can trigger from
|
|
|
|
the user interface.
|
|
|
|
* if p_includeFake is True, it retrieves transitions that the user
|
|
|
|
can't trigger, but for which he needs to know for what reason he
|
|
|
|
can't trigger it;
|
|
|
|
* if p_includeNotShowable is True, it includes transitions for which
|
|
|
|
show=False. Indeed, because "showability" is only a GUI concern,
|
|
|
|
and not a security concern, in some cases it has sense to set
|
|
|
|
includeNotShowable=True, because those transitions are triggerable
|
|
|
|
from a security point of view.
|
|
|
|
'''
|
2009-11-06 04:33:56 -06:00
|
|
|
res = []
|
2011-07-26 15:15:04 -05:00
|
|
|
wf = self.getWorkflow()
|
2011-11-25 11:01:20 -06:00
|
|
|
currentState = self.State(name=False)
|
2011-07-26 15:15:04 -05:00
|
|
|
# Loop on every transition
|
|
|
|
for name in dir(wf):
|
|
|
|
transition = getattr(wf, name)
|
|
|
|
if (transition.__class__.__name__ != 'Transition'): continue
|
|
|
|
# Filter transitions that do not have currentState as start state
|
|
|
|
if not transition.hasState(currentState, True): continue
|
|
|
|
# Check if the transition can be triggered
|
|
|
|
mayTrigger = transition.isTriggerable(self, wf)
|
2011-02-01 04:09:54 -06:00
|
|
|
# Compute the condition that will lead to including or not this
|
|
|
|
# transition
|
|
|
|
if not includeFake:
|
|
|
|
includeIt = mayTrigger
|
|
|
|
else:
|
2011-12-05 08:11:29 -06:00
|
|
|
includeIt = mayTrigger or isinstance(mayTrigger, gen.No)
|
2011-02-01 04:09:54 -06:00
|
|
|
if not includeNotShowable:
|
2011-07-26 15:15:04 -05:00
|
|
|
includeIt = includeIt and transition.isShowable(wf, self)
|
2011-02-01 04:09:54 -06:00
|
|
|
if not includeIt: continue
|
|
|
|
# Add transition-info to the result.
|
2011-07-26 15:15:04 -05:00
|
|
|
label = self.getWorkflowLabel(name)
|
|
|
|
tInfo = {'name': name, 'title': self.translate(label),
|
|
|
|
'confirm': '', 'may_trigger': True}
|
|
|
|
if transition.confirm:
|
|
|
|
cLabel = '%s_confirm' % label
|
|
|
|
tInfo['confirm'] = self.translate(cLabel, format='js')
|
2011-02-01 04:09:54 -06:00
|
|
|
if not mayTrigger:
|
|
|
|
tInfo['may_trigger'] = False
|
|
|
|
tInfo['reason'] = mayTrigger.msg
|
|
|
|
res.append(tInfo)
|
2009-11-06 04:33:56 -06:00
|
|
|
return res
|
|
|
|
|
2010-10-19 03:47:42 -05:00
|
|
|
def getAppyPhases(self, currentOnly=False, layoutType='view'):
|
2009-06-29 07:06:01 -05:00
|
|
|
'''Gets the list of phases that are defined for this content type. If
|
2010-10-19 03:47:42 -05:00
|
|
|
p_currentOnly is True, the search is limited to the phase where the
|
|
|
|
current page (as defined in the request) lies.'''
|
2009-06-29 07:06:01 -05:00
|
|
|
# Get the list of phases
|
|
|
|
res = [] # Ordered list of phases
|
|
|
|
phases = {} # Dict of phases
|
2010-08-05 11:23:17 -05:00
|
|
|
for appyType in self.getAllAppyTypes():
|
2010-10-19 03:47:42 -05:00
|
|
|
typePhase = appyType.page.phase
|
|
|
|
if typePhase not in phases:
|
2012-11-05 03:21:27 -06:00
|
|
|
phase = PhaseDescr(typePhase, self)
|
2009-06-29 07:06:01 -05:00
|
|
|
res.append(phase.__dict__)
|
2010-10-19 03:47:42 -05:00
|
|
|
phases[typePhase] = phase
|
2009-06-29 07:06:01 -05:00
|
|
|
else:
|
2010-10-19 03:47:42 -05:00
|
|
|
phase = phases[typePhase]
|
|
|
|
phase.addPage(appyType, self, layoutType)
|
2012-03-27 03:37:41 -05:00
|
|
|
if (appyType.type == 'Ref') and appyType.navigable:
|
|
|
|
phase.addPageLinks(appyType, self)
|
2009-06-29 07:06:01 -05:00
|
|
|
# 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():
|
2010-08-05 11:23:17 -05:00
|
|
|
ph.computeStatus(res)
|
2009-06-29 07:06:01 -05:00
|
|
|
ph.totalNbOfPhases = len(res)
|
2010-10-19 03:47:42 -05:00
|
|
|
# Restrict the result to the current phase if required
|
2009-06-29 07:06:01 -05:00
|
|
|
if currentOnly:
|
2010-10-19 03:47:42 -05:00
|
|
|
rq = self.REQUEST
|
2012-06-02 10:39:05 -05:00
|
|
|
page = rq.get('page', None)
|
|
|
|
if not page:
|
|
|
|
if layoutType == 'edit': page = self.getDefaultEditPage()
|
|
|
|
else: page = self.getDefaultViewPage()
|
2009-06-29 07:06:01 -05:00
|
|
|
for phaseInfo in res:
|
2010-08-05 11:23:17 -05:00
|
|
|
if page in phaseInfo['pages']:
|
2009-06-29 07:06:01 -05:00
|
|
|
return phaseInfo
|
2010-10-19 03:47:42 -05:00
|
|
|
# If I am here, it means that the page as defined in the request,
|
2012-06-02 10:39:05 -05:00
|
|
|
# or the default page, is not existing nor visible in any phase.
|
2010-10-29 07:36:36 -05:00
|
|
|
# In this case I find the first visible page among all phases.
|
|
|
|
viewAttr = 'showOn%s' % layoutType.capitalize()
|
|
|
|
for phase in res:
|
|
|
|
for page in phase['pages']:
|
|
|
|
if phase['pagesInfo'][page][viewAttr]:
|
|
|
|
rq.set('page', page)
|
|
|
|
pageFound = True
|
|
|
|
break
|
|
|
|
return phase
|
2009-06-29 07:06:01 -05:00
|
|
|
else:
|
2012-11-29 13:45:21 -06:00
|
|
|
# Return an empty list if we have a single, link-free page within
|
|
|
|
# a single phase.
|
|
|
|
if (len(res) == 1) and (len(res[0]['pages']) == 1) and \
|
|
|
|
not res[0]['pagesInfo'][res[0]['pages'][0]].get('links'):
|
|
|
|
return None
|
2009-06-29 07:06:01 -05:00
|
|
|
return res
|
|
|
|
|
2012-10-31 15:17:31 -05:00
|
|
|
def getIcons(self):
|
|
|
|
'''Gets the icons that can be shown besides the title of an object.'''
|
|
|
|
appyObj = self.appy()
|
|
|
|
if hasattr(appyObj, 'getIcons'): return appyObj.getIcons()
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def getSubTitle(self):
|
|
|
|
'''Gets the content that must appear below the title of an object.'''
|
|
|
|
appyObj = self.appy()
|
|
|
|
if hasattr(appyObj, 'getSubTitle'): return appyObj.getSubTitle()
|
|
|
|
return ''
|
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
def getPreviousPage(self, phase, page):
|
|
|
|
'''Returns the page that precedes p_page which is in p_phase.'''
|
2010-11-26 10:30:46 -06:00
|
|
|
try:
|
|
|
|
pageIndex = phase['pages'].index(page)
|
|
|
|
except ValueError:
|
|
|
|
# The current page is probably not visible anymore. Return the
|
|
|
|
# first available page in current phase.
|
|
|
|
res = phase['pages'][0]
|
|
|
|
return res, phase['pagesInfo'][res]
|
2010-08-05 11:23:17 -05:00
|
|
|
if pageIndex > 0:
|
|
|
|
# We stay on the same phase, previous page
|
2010-09-13 14:04:10 -05:00
|
|
|
res = phase['pages'][pageIndex-1]
|
2010-10-19 03:47:42 -05:00
|
|
|
resInfo = phase['pagesInfo'][res]
|
|
|
|
return res, resInfo
|
2010-08-05 11:23:17 -05:00
|
|
|
else:
|
|
|
|
if phase['previousPhase']:
|
|
|
|
# We go to the last page of previous phase
|
|
|
|
previousPhase = phase['previousPhase']
|
2010-09-13 14:04:10 -05:00
|
|
|
res = previousPhase['pages'][-1]
|
2010-10-19 03:47:42 -05:00
|
|
|
resInfo = previousPhase['pagesInfo'][res]
|
|
|
|
return res, resInfo
|
2010-08-05 11:23:17 -05:00
|
|
|
else:
|
2010-09-13 14:04:10 -05:00
|
|
|
return None, None
|
2010-08-05 11:23:17 -05:00
|
|
|
|
|
|
|
def getNextPage(self, phase, page):
|
|
|
|
'''Returns the page that follows p_page which is in p_phase.'''
|
2010-11-26 10:30:46 -06:00
|
|
|
try:
|
|
|
|
pageIndex = phase['pages'].index(page)
|
|
|
|
except ValueError:
|
|
|
|
# The current page is probably not visible anymore. Return the
|
|
|
|
# first available page in current phase.
|
|
|
|
res = phase['pages'][0]
|
|
|
|
return res, phase['pagesInfo'][res]
|
2010-08-05 11:23:17 -05:00
|
|
|
if pageIndex < len(phase['pages'])-1:
|
|
|
|
# We stay on the same phase, next page
|
2010-09-13 14:04:10 -05:00
|
|
|
res = phase['pages'][pageIndex+1]
|
2010-10-19 03:47:42 -05:00
|
|
|
resInfo = phase['pagesInfo'][res]
|
|
|
|
return res, resInfo
|
2010-08-05 11:23:17 -05:00
|
|
|
else:
|
|
|
|
if phase['nextPhase']:
|
|
|
|
# We go to the first page of next phase
|
|
|
|
nextPhase = phase['nextPhase']
|
2010-09-13 14:04:10 -05:00
|
|
|
res = nextPhase['pages'][0]
|
2010-10-19 03:47:42 -05:00
|
|
|
resInfo = nextPhase['pagesInfo'][res]
|
|
|
|
return res, resInfo
|
2010-08-05 11:23:17 -05:00
|
|
|
else:
|
2010-09-13 14:04:10 -05:00
|
|
|
return None, None
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2009-10-20 09:57:00 -05:00
|
|
|
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.'''
|
2011-10-11 10:32:23 -05:00
|
|
|
refs = getattr(self.aq_base, fieldName, None)
|
2011-09-26 14:19:34 -05:00
|
|
|
oldIndex = refs.index(objectUid)
|
|
|
|
refs.remove(objectUid)
|
2009-06-29 07:06:01 -05:00
|
|
|
if isDelta:
|
|
|
|
newIndex = oldIndex + newIndex
|
|
|
|
else:
|
|
|
|
pass # To implement later on
|
2011-09-26 14:19:34 -05:00
|
|
|
refs.insert(newIndex, objectUid)
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2009-10-20 09:57:00 -05:00
|
|
|
def onChangeRefOrder(self):
|
|
|
|
'''This method is called when the user wants to change order of an
|
|
|
|
item in a reference field.'''
|
|
|
|
rq = self.REQUEST
|
2009-10-25 15:42:08 -05:00
|
|
|
# Move the item up (-1), down (+1) ?
|
2009-10-20 09:57:00 -05:00
|
|
|
move = -1 # Move up
|
2009-10-25 15:42:08 -05:00
|
|
|
if rq['move'] == 'down':
|
|
|
|
move = 1 # Down
|
2009-10-20 09:57:00 -05:00
|
|
|
isDelta = True
|
|
|
|
self.changeRefOrder(rq['fieldName'], rq['refObjectUid'], move, isDelta)
|
|
|
|
|
2010-04-30 05:05:29 -05:00
|
|
|
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)
|
|
|
|
|
2011-07-26 15:15:04 -05:00
|
|
|
def notifyWorkflowCreated(self):
|
2012-02-09 09:36:50 -06:00
|
|
|
'''This method is called every time an object is created, be it temp or
|
|
|
|
not. The objective here is to initialise workflow-related data on
|
|
|
|
the object.'''
|
2011-07-26 15:15:04 -05:00
|
|
|
wf = self.getWorkflow()
|
|
|
|
# Get the initial workflow state
|
2011-11-25 11:01:20 -06:00
|
|
|
initialState = self.State(name=False)
|
2011-07-26 15:15:04 -05:00
|
|
|
# Create a Transition instance representing the initial transition.
|
2011-12-05 08:11:29 -06:00
|
|
|
initialTransition = gen.Transition((initialState, initialState))
|
2011-07-26 15:15:04 -05:00
|
|
|
initialTransition.trigger('_init_', self, wf, '')
|
|
|
|
|
2011-09-08 09:33:16 -05:00
|
|
|
def getWorkflow(self, name=False, className=None):
|
|
|
|
'''Returns the workflow applicable for p_self (or for any instance of
|
|
|
|
p_className if given), or its name, if p_name is True.'''
|
|
|
|
if not className:
|
2012-06-01 08:57:19 -05:00
|
|
|
wrapperClass = self.wrapperClass
|
2011-09-08 09:33:16 -05:00
|
|
|
else:
|
2012-06-01 08:57:19 -05:00
|
|
|
wrapperClass = self.getTool().getAppyClass(className, wrapper=True)
|
|
|
|
wf = wrapperClass.getWorkflow()
|
2011-07-26 15:15:04 -05:00
|
|
|
if not name: return wf
|
|
|
|
return WorkflowDescriptor.getWorkflowName(wf)
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
def getWorkflowLabel(self, stateName=None):
|
2011-07-26 15:15:04 -05:00
|
|
|
'''Gets the i18n label for p_stateName, or for the current object state
|
|
|
|
if p_stateName is not given. Note that if p_stateName is given, it
|
|
|
|
can also represent the name of a transition.'''
|
2011-11-25 11:01:20 -06:00
|
|
|
stateName = stateName or self.State()
|
2011-07-26 15:15:04 -05:00
|
|
|
return '%s_%s' % (self.getWorkflow(name=True), stateName)
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2011-09-08 09:33:16 -05:00
|
|
|
def refreshSecurity(self):
|
|
|
|
'''Refresh security info on this object. Returns True if the info has
|
|
|
|
effectively been updated.'''
|
|
|
|
wf = self.getWorkflow()
|
|
|
|
try:
|
|
|
|
# Get the state definition of the object's current state.
|
2011-11-25 11:01:20 -06:00
|
|
|
state = getattr(wf, self.State())
|
2011-09-08 09:33:16 -05:00
|
|
|
except AttributeError:
|
|
|
|
# The workflow information for this object does not correspond to
|
|
|
|
# its current workflow attribution. Add a new fake event
|
|
|
|
# representing passage of this object to the initial state of his
|
|
|
|
# currently attributed workflow.
|
2011-11-25 11:01:20 -06:00
|
|
|
stateName = self.State(name=True, initial=True)
|
2011-09-08 09:33:16 -05:00
|
|
|
self.addHistoryEvent(None, review_state=stateName)
|
2011-11-25 11:01:20 -06:00
|
|
|
state = self.State(name=False, initial=True)
|
2012-08-07 10:38:54 -05:00
|
|
|
self.log('Wrong workflow info for a "%s"; is now in state "%s".' % \
|
2011-09-08 09:33:16 -05:00
|
|
|
(self.meta_type, stateName))
|
|
|
|
# Update permission attributes on the object if required
|
2012-03-19 11:00:44 -05:00
|
|
|
updated = state.updatePermissions(wf, self)
|
|
|
|
if updated:
|
|
|
|
# Reindex the object because security-related info is indexed.
|
|
|
|
self.reindex()
|
|
|
|
return updated
|
2011-09-08 09:33:16 -05:00
|
|
|
|
2012-07-11 10:27:40 -05:00
|
|
|
def applyUserIdChange(self, oldId, newId):
|
|
|
|
'''A user whose ID was p_oldId has now p_newId. If the old ID was
|
|
|
|
mentioned in self's local roles, update it to the new ID. This
|
|
|
|
method returns 1 if a change occurred, 0 else.'''
|
|
|
|
if oldId in self.__ac_local_roles__:
|
|
|
|
localRoles = self.__ac_local_roles__.copy()
|
|
|
|
localRoles[newId] = localRoles[oldId]
|
|
|
|
del localRoles[oldId]
|
|
|
|
self.__ac_local_roles__ = localRoles
|
|
|
|
self.reindex()
|
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
2009-12-14 13:22:55 -06:00
|
|
|
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
|
|
|
|
|
2011-01-28 19:18:14 -06:00
|
|
|
def getHistory(self, startNumber=0, reverse=True, includeInvisible=False,
|
|
|
|
batchSize=5):
|
2009-12-14 13:22:55 -06:00
|
|
|
'''Returns the history for this object, sorted in reverse order (most
|
|
|
|
recent change first) if p_reverse is True.'''
|
|
|
|
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],
|
2011-01-28 19:18:14 -06:00
|
|
|
'totalNumber': len(history)}
|
2009-12-14 13:22:55 -06:00
|
|
|
|
2010-12-17 07:46:55 -06:00
|
|
|
def mayNavigate(self):
|
|
|
|
'''May the currently logged user see the navigation panel linked to
|
|
|
|
this object?'''
|
|
|
|
appyObj = self.appy()
|
|
|
|
if hasattr(appyObj, 'mayNavigate'): return appyObj.mayNavigate()
|
|
|
|
return True
|
|
|
|
|
2012-06-02 10:39:05 -05:00
|
|
|
def getDefaultViewPage(self):
|
|
|
|
'''Which view page must be shown by default?'''
|
|
|
|
appyObj = self.appy()
|
|
|
|
if hasattr(appyObj, 'getDefaultViewPage'):
|
|
|
|
return appyObj.getDefaultViewPage()
|
|
|
|
return 'main'
|
|
|
|
|
|
|
|
def getDefaultEditPage(self):
|
|
|
|
'''Which edit page must be shown by default?'''
|
|
|
|
appyObj = self.appy()
|
|
|
|
if hasattr(appyObj, 'getDefaultEditPage'):
|
|
|
|
return appyObj.getDefaultEditPage()
|
|
|
|
return 'main'
|
|
|
|
|
2012-02-23 08:58:06 -06:00
|
|
|
def mayAct(self):
|
|
|
|
'''May the currently logged user see column "actions" for this
|
|
|
|
object? This can be used for hiding the "edit" icon, for example:
|
|
|
|
when a user may edit only a restricted set of fields on an object,
|
|
|
|
we may avoid showing him the global "edit" icon.'''
|
|
|
|
appyObj = self.appy()
|
|
|
|
if hasattr(appyObj, 'mayAct'): return appyObj.mayAct()
|
|
|
|
return True
|
|
|
|
|
2010-12-17 07:46:55 -06:00
|
|
|
def mayDelete(self):
|
2012-10-08 03:08:54 -05:00
|
|
|
'''May the currently logged user delete this object?'''
|
2012-06-01 08:57:19 -05:00
|
|
|
res = self.allows('Delete objects')
|
|
|
|
if not res: return
|
|
|
|
# An additional, user-defined condition, may refine the base permission.
|
2010-12-17 07:46:55 -06:00
|
|
|
appyObj = self.appy()
|
|
|
|
if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete()
|
|
|
|
return True
|
|
|
|
|
2012-10-08 03:08:54 -05:00
|
|
|
def mayEdit(self, permission='Modify portal content'):
|
|
|
|
'''May the currently logged user edit this object? p_perm can be a
|
|
|
|
field-specific permission.'''
|
|
|
|
res = self.allows(permission)
|
2012-06-01 08:57:19 -05:00
|
|
|
if not res: return
|
|
|
|
# An additional, user-defined condition, may refine the base permission.
|
|
|
|
appyObj = self.appy()
|
|
|
|
if hasattr(appyObj, 'mayEdit'): return appyObj.mayEdit()
|
|
|
|
return True
|
|
|
|
|
2009-06-29 07:06:01 -05:00
|
|
|
def executeAppyAction(self, actionName, reindex=True):
|
|
|
|
'''Executes action with p_fieldName on this object.'''
|
2010-08-05 11:23:17 -05:00
|
|
|
appyType = self.getAppyType(actionName)
|
2010-02-08 01:53:30 -06:00
|
|
|
actionRes = appyType(self.appy())
|
2011-11-25 11:01:20 -06:00
|
|
|
parent = self.getParentNode()
|
|
|
|
parentAq = getattr(parent, 'aq_base', parent)
|
|
|
|
if not hasattr(parentAq, self.id):
|
2010-12-17 07:46:55 -06:00
|
|
|
# Else, it means that the action has led to self's deletion.
|
2011-11-25 11:01:20 -06:00
|
|
|
self.reindex()
|
2009-10-25 15:42:08 -05:00
|
|
|
return appyType.result, actionRes
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2009-10-20 09:57:00 -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
|
2009-10-25 15:42:08 -05:00
|
|
|
resultType, actionResult = self.executeAppyAction(rq['fieldName'])
|
|
|
|
successfull, msg = actionResult
|
2009-10-20 09:57:00 -05:00
|
|
|
if not msg:
|
|
|
|
# Use the default i18n messages
|
|
|
|
suffix = 'ko'
|
2011-09-10 18:59:22 -05:00
|
|
|
if successfull: suffix = 'ok'
|
|
|
|
msg = self.translate('action_%s' % suffix)
|
2009-10-25 15:42:08 -05:00
|
|
|
if (resultType == 'computation') or not successfull:
|
2011-02-12 10:09:11 -06:00
|
|
|
self.say(msg)
|
2010-09-17 08:32:48 -05:00
|
|
|
return self.goto(self.getUrl(rq['HTTP_REFERER']))
|
2011-02-12 10:09:11 -06:00
|
|
|
elif resultType.startswith('file'):
|
2011-01-14 02:06:25 -06:00
|
|
|
# msg does not contain a message, but a file instance.
|
|
|
|
response = self.REQUEST.RESPONSE
|
|
|
|
response.setHeader('Content-Type',mimetypes.guess_type(msg.name)[0])
|
|
|
|
response.setHeader('Content-Disposition', 'inline;filename="%s"' %\
|
2011-02-12 10:09:11 -06:00
|
|
|
os.path.basename(msg.name))
|
2011-01-14 02:06:25 -06:00
|
|
|
response.write(msg.read())
|
|
|
|
msg.close()
|
2011-02-12 10:09:11 -06:00
|
|
|
if resultType == 'filetmp':
|
|
|
|
# p_msg is a temp file. We need to delete it.
|
|
|
|
try:
|
|
|
|
os.remove(msg.name)
|
|
|
|
self.log('Temp file "%s" was deleted.' % msg.name)
|
|
|
|
except IOError, err:
|
|
|
|
self.log('Could not remove temp "%s" (%s).' % \
|
|
|
|
(msg.name, str(err)), type='warning')
|
|
|
|
except OSError, err:
|
|
|
|
self.log('Could not remove temp "%s" (%s).' % \
|
|
|
|
(msg.name, str(err)), type='warning')
|
2010-11-26 10:30:46 -06:00
|
|
|
elif resultType == 'redirect':
|
|
|
|
# msg does not contain a message, but the URL where to redirect
|
|
|
|
# the user.
|
|
|
|
return self.goto(msg)
|
2009-10-20 09:57:00 -05:00
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def trigger(self, transitionName, comment='', doAction=True, doNotify=True,
|
2012-07-23 08:24:44 -05:00
|
|
|
doHistory=True, doSay=True, noSecurity=False):
|
2011-07-26 15:15:04 -05:00
|
|
|
'''Triggers transition named p_transitionName.'''
|
|
|
|
# Check that this transition exists.
|
|
|
|
wf = self.getWorkflow()
|
|
|
|
if not hasattr(wf, transitionName) or \
|
|
|
|
getattr(wf, transitionName).__class__.__name__ != 'Transition':
|
|
|
|
raise 'Transition "%s" was not found.' % transitionName
|
|
|
|
# Is this transition triggerable?
|
|
|
|
transition = getattr(wf, transitionName)
|
2012-07-23 08:24:44 -05:00
|
|
|
if not transition.isTriggerable(self, wf, noSecurity=noSecurity):
|
2011-09-09 10:39:58 -05:00
|
|
|
raise 'Transition "%s" can\'t be triggered.' % transitionName
|
2011-07-26 15:15:04 -05:00
|
|
|
# Trigger the transition
|
|
|
|
transition.trigger(transitionName, self, wf, comment, doAction=doAction,
|
|
|
|
doNotify=doNotify, doHistory=doHistory, doSay=doSay)
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def onTrigger(self):
|
2009-10-20 09:57:00 -05:00
|
|
|
'''This method is called whenever a user wants to trigger a workflow
|
|
|
|
transition on an object.'''
|
|
|
|
rq = self.REQUEST
|
2011-11-25 11:01:20 -06:00
|
|
|
self.trigger(rq['workflow_action'], comment=rq.get('comment', ''))
|
|
|
|
self.reindex()
|
2010-11-22 02:36:14 -06:00
|
|
|
return self.goto(self.getUrl(rq['HTTP_REFERER']))
|
2009-10-20 09:57:00 -05:00
|
|
|
|
2010-08-12 04:56:42 -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?'''
|
2010-08-05 11:23:17 -05:00
|
|
|
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:
|
2010-08-12 04:56:42 -05:00
|
|
|
compValue = dbValue
|
2010-08-05 11:23:17 -05:00
|
|
|
# Compare the value
|
|
|
|
if type(compValue) in sequenceTypes:
|
|
|
|
if vocabValue in compValue: return True
|
2009-06-29 07:06:01 -05:00
|
|
|
else:
|
2010-08-05 11:23:17 -05:00
|
|
|
if vocabValue == compValue: return True
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2010-08-12 04:56:42 -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?'''
|
2010-08-05 11:23:17 -05:00
|
|
|
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:
|
2010-08-12 04:56:42 -05:00
|
|
|
compValue = dbValue
|
2010-08-05 11:23:17 -05:00
|
|
|
# Compare the value
|
|
|
|
return compValue
|
|
|
|
|
2010-08-12 04:56:42 -05:00
|
|
|
def dateValueSelected(self, fieldName, fieldPart, dateValue, dbValue):
|
2010-08-05 11:23:17 -05:00
|
|
|
'''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:
|
2010-08-12 04:56:42 -05:00
|
|
|
compValue = dbValue
|
2010-08-05 11:23:17 -05:00
|
|
|
if compValue:
|
|
|
|
compValue = getattr(compValue, fieldPart)()
|
|
|
|
# Compare the value
|
|
|
|
return compValue == dateValue
|
|
|
|
|
2011-03-25 12:03:45 -05:00
|
|
|
def getSelectableYears(self, name):
|
|
|
|
'''Gets the list of selectable years for Date field named p_name.'''
|
|
|
|
return self.getAppyType(name).getSelectableYears()
|
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
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,
|
2011-12-08 09:01:57 -06:00
|
|
|
the field is supposed to belong to self's class.'''
|
2010-08-05 11:23:17 -05:00
|
|
|
appyType = self.getAppyType(name, className=className)
|
2010-12-17 07:46:55 -06:00
|
|
|
if className:
|
|
|
|
# We need an instance of className, but self can be an instance of
|
|
|
|
# another class. So here we will search such an instance.
|
|
|
|
brains = self.executeQuery(className, maxResults=1, brainsOnly=True)
|
|
|
|
if brains:
|
|
|
|
obj = brains[0].getObject()
|
|
|
|
else:
|
|
|
|
obj = self
|
|
|
|
else:
|
|
|
|
obj = self
|
|
|
|
return appyType.getPossibleValues(obj, withTranslations, withBlankValue)
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2012-02-16 11:13:51 -06:00
|
|
|
def getCaptchaChallenge(self, name):
|
|
|
|
return self.getAppyType(name).getCaptchaChallenge(self.REQUEST.SESSION)
|
|
|
|
|
2009-08-04 07:39:43 -05:00
|
|
|
def appy(self):
|
2010-02-08 01:53:30 -06:00
|
|
|
'''Returns a wrapper object allowing to manipulate p_self the Appy
|
|
|
|
way.'''
|
2010-08-05 11:23:17 -05:00
|
|
|
# Create the dict for storing Appy wrapper on the REQUEST if needed.
|
2011-11-25 11:01:20 -06:00
|
|
|
rq = getattr(self, 'REQUEST', None)
|
2011-12-05 11:15:45 -06:00
|
|
|
if not rq:
|
|
|
|
# We are in test mode or Zope is starting. Use static variable
|
|
|
|
# config.fakeRequest instead.
|
|
|
|
rq = self.getProductConfig().fakeRequest
|
2011-12-05 03:52:18 -06:00
|
|
|
if not hasattr(rq, 'wrappers'): rq.wrappers = {}
|
2011-12-05 11:15:45 -06:00
|
|
|
# Return the Appy wrapper if already present in the cache
|
2010-08-05 11:23:17 -05:00
|
|
|
uid = self.UID()
|
2011-12-05 03:52:18 -06:00
|
|
|
if uid in rq.wrappers: return rq.wrappers[uid]
|
|
|
|
# Create the Appy wrapper, cache it in rq.wrappers and return it
|
2010-08-05 11:23:17 -05:00
|
|
|
wrapper = self.wrapperClass(self)
|
2011-12-05 03:52:18 -06:00
|
|
|
rq.wrappers[uid] = wrapper
|
2010-08-05 11:23:17 -05:00
|
|
|
return wrapper
|
2011-11-25 11:01:20 -06:00
|
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
2012-11-05 03:21:27 -06:00
|
|
|
# Methods for computing values of standard Appy indexes
|
2011-11-25 11:01:20 -06:00
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
def UID(self):
|
|
|
|
'''Returns the unique identifier for this object.'''
|
|
|
|
return self._at_uid
|
|
|
|
|
|
|
|
def Title(self):
|
|
|
|
'''Returns the title for this object.'''
|
|
|
|
title = self.getAppyType('title')
|
|
|
|
if title: return title.getValue(self)
|
|
|
|
return self.id
|
|
|
|
|
|
|
|
def SortableTitle(self):
|
|
|
|
'''Returns the title as must be stored in index "SortableTitle".'''
|
2012-10-18 13:57:03 -05:00
|
|
|
return normalizeText(self.Title())
|
2011-11-25 11:01:20 -06:00
|
|
|
|
|
|
|
def SearchableText(self):
|
|
|
|
'''This method concatenates the content of every field with
|
|
|
|
searchable=True for indexing purposes.'''
|
|
|
|
res = []
|
|
|
|
for field in self.getAllAppyTypes():
|
|
|
|
if not field.searchable: continue
|
|
|
|
res.append(field.getIndexValue(self, forSearch=True))
|
|
|
|
return res
|
|
|
|
|
|
|
|
def Creator(self):
|
|
|
|
'''Who create this object?'''
|
|
|
|
return self.creator
|
|
|
|
|
|
|
|
def Created(self):
|
|
|
|
'''When was this object created ?'''
|
|
|
|
return self.created
|
|
|
|
|
2012-11-05 03:21:27 -06:00
|
|
|
def Modified(self):
|
|
|
|
'''When was this object last modified ?'''
|
|
|
|
if hasattr(self.aq_base, 'modified'): return self.modified
|
|
|
|
return self.created
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def State(self, name=True, initial=False):
|
|
|
|
'''Returns information about the current object state. If p_name is
|
|
|
|
True, the returned info is the state name. Else, it is the State
|
|
|
|
instance. If p_initial is True, instead of returning info about the
|
|
|
|
current state, it returns info about the workflow initial state.'''
|
|
|
|
wf = self.getWorkflow()
|
|
|
|
if initial or not hasattr(self.aq_base, 'workflow_history'):
|
|
|
|
# No workflow information is available (yet) on this object, or
|
|
|
|
# initial state is asked. In both cases, return info about this
|
|
|
|
# initial state.
|
|
|
|
res = 'active'
|
|
|
|
for elem in dir(wf):
|
|
|
|
attr = getattr(wf, elem)
|
|
|
|
if (attr.__class__.__name__ == 'State') and attr.initial:
|
|
|
|
res = elem
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
# Return info about the current object state
|
|
|
|
key = self.workflow_history.keys()[0]
|
|
|
|
res = self.workflow_history[key][-1]['review_state']
|
|
|
|
# Return state name or state definition?
|
|
|
|
if name: return res
|
|
|
|
else: return getattr(wf, res)
|
|
|
|
|
|
|
|
def ClassName(self):
|
|
|
|
'''Returns the name of the (Zope) class for self.'''
|
|
|
|
return self.portal_type
|
|
|
|
|
2011-11-28 15:50:01 -06:00
|
|
|
def Allowed(self):
|
|
|
|
'''Returns the list of roles and users that are allowed to view this
|
|
|
|
object. This index value will be used within catalog queries for
|
|
|
|
filtering objects the user is allowed to see.'''
|
|
|
|
res = set()
|
|
|
|
# Get, from the workflow, roles having permission 'View'.
|
|
|
|
for role in self.getProductConfig().rolesForPermissionOn('View', self):
|
|
|
|
res.add(role)
|
|
|
|
# Add users having, locally, this role on this object.
|
|
|
|
localRoles = getattr(self, '__ac_local_roles__', None)
|
|
|
|
if not localRoles: return list(res)
|
|
|
|
for id, roles in localRoles.iteritems():
|
|
|
|
for role in roles:
|
|
|
|
if role in res:
|
|
|
|
res.add('user:%s' % id)
|
|
|
|
return list(res)
|
|
|
|
|
2012-11-05 03:21:27 -06:00
|
|
|
def showState(self):
|
|
|
|
'''Must I show self's current state ?'''
|
|
|
|
stateShow = self.State(name=False).show
|
2009-06-29 07:06:01 -05:00
|
|
|
if callable(stateShow):
|
2012-11-05 03:21:27 -06:00
|
|
|
return stateShow(self.getWorkflow(), self.appy())
|
|
|
|
return stateShow
|
2009-06-29 07:06:01 -05:00
|
|
|
|
[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.
2012-10-31 07:20:25 -05:00
|
|
|
def _appy_listStates(self):
|
|
|
|
'''Lists the possible states for this object.'''
|
|
|
|
res = []
|
|
|
|
workflow = self.getWorkflow()
|
|
|
|
for elem in dir(workflow):
|
|
|
|
if getattr(workflow, elem).__class__.__name__ != 'State': continue
|
|
|
|
res.append((elem, self.translate(self.getWorkflowLabel(elem))))
|
|
|
|
return res
|
|
|
|
|
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
|
2012-06-02 07:36:49 -05:00
|
|
|
folder = self.getCreateFolder()
|
2009-06-29 07:06:01 -05:00
|
|
|
# On this folder, set "add" permissions for every content type that will
|
|
|
|
# be created through reference fields
|
2010-08-27 01:59:53 -05:00
|
|
|
allCreators = {} # One key for every add permission
|
|
|
|
addPermissions = self.getProductConfig().ADD_CONTENT_PERMISSIONS
|
2010-08-05 11:23:17 -05:00
|
|
|
for appyType in self.getAllAppyTypes():
|
2010-08-27 01:59:53 -05:00
|
|
|
if appyType.type != 'Ref': continue
|
|
|
|
if appyType.isBack or appyType.link: continue
|
2011-09-26 14:19:34 -05:00
|
|
|
# Indeed, no possibility to create objects with such Refs
|
2012-06-02 07:36:49 -05:00
|
|
|
tool = self.getTool()
|
|
|
|
refType = tool.getPortalType(appyType.klass)
|
2010-09-02 09:16:08 -05:00
|
|
|
if refType not in addPermissions: continue
|
2010-08-27 01:59:53 -05:00
|
|
|
# Get roles that may add this content type
|
2012-06-02 07:36:49 -05:00
|
|
|
appyWrapper = tool.getAppyClass(refType, wrapper=True)
|
|
|
|
creators = appyWrapper.getCreators(self.getProductConfig())
|
2010-08-27 01:59:53 -05:00
|
|
|
# Add those creators to the list of creators for this meta_type
|
2010-09-02 09:16:08 -05:00
|
|
|
addPermission = addPermissions[refType]
|
2010-08-27 01:59:53 -05:00
|
|
|
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
|
|
|
|
|
|
|
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-09-17 08:32:48 -05:00
|
|
|
getUrlDefaults = {'page':True, 'nav':True}
|
|
|
|
def getUrl(self, base=None, mode='view', **kwargs):
|
|
|
|
'''Returns a Appy URL.
|
|
|
|
* If p_base is None, it will be the base URL for this object
|
|
|
|
(ie, self.absolute_url()).
|
2010-11-22 02:36:14 -06:00
|
|
|
* p_mode can be "edit", "view" or "raw" (a non-param, base URL)
|
2010-09-17 08:32:48 -05:00
|
|
|
* p_kwargs can store additional parameters to add to the URL.
|
|
|
|
In this dict, every value that is a string will be added to the
|
|
|
|
URL as-is. Every value that is True will be replaced by the value
|
|
|
|
in the request for the corresponding key (if existing; else, the
|
|
|
|
param will not be included in the URL at all).'''
|
2010-11-22 02:36:14 -06:00
|
|
|
# Define the URL suffix
|
|
|
|
suffix = ''
|
2011-11-25 11:01:20 -06:00
|
|
|
if mode != 'raw': suffix = '/ui/%s' % mode
|
2010-11-22 02:36:14 -06:00
|
|
|
# Define base URL if omitted
|
2010-09-19 08:04:44 -05:00
|
|
|
if not base:
|
2010-11-22 02:36:14 -06:00
|
|
|
base = self.absolute_url() + suffix
|
|
|
|
# If a raw URL is asked, remove any param and suffix.
|
|
|
|
if mode == 'raw':
|
|
|
|
if '?' in base: base = base[:base.index('?')]
|
|
|
|
base = base.strip('/')
|
|
|
|
for mode in ('view', 'edit'):
|
2011-11-25 11:01:20 -06:00
|
|
|
suffix = 'ui/%s' % mode
|
2010-11-22 02:36:14 -06:00
|
|
|
if base.endswith(suffix):
|
|
|
|
base = base[:-len(suffix)].strip('/')
|
|
|
|
break
|
|
|
|
return base
|
2010-09-17 08:32:48 -05:00
|
|
|
# Manage default args
|
|
|
|
if not kwargs: kwargs = self.getUrlDefaults
|
|
|
|
if 'page' not in kwargs: kwargs['page'] = True
|
|
|
|
if 'nav' not in kwargs: kwargs['nav'] = True
|
|
|
|
# Create URL parameters from kwargs
|
|
|
|
params = []
|
|
|
|
for name, value in kwargs.iteritems():
|
|
|
|
if isinstance(value, basestring):
|
|
|
|
params.append('%s=%s' % (name, value))
|
|
|
|
elif self.REQUEST.get(name, ''):
|
|
|
|
params.append('%s=%s' % (name, self.REQUEST[name]))
|
|
|
|
if params:
|
|
|
|
params = '&'.join(params)
|
|
|
|
if base.find('?') != -1: params = '&' + params
|
|
|
|
else: params = '?' + params
|
|
|
|
else:
|
|
|
|
params = ''
|
2010-09-19 08:04:44 -05:00
|
|
|
return '%s%s' % (base, params)
|
2009-10-18 07:52:27 -05:00
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def getUser(self):
|
|
|
|
'''Gets the Zope object representing the authenticated user.'''
|
|
|
|
from AccessControl import getSecurityManager
|
|
|
|
user = getSecurityManager().getUser()
|
|
|
|
if not user:
|
|
|
|
from AccessControl.User import nobody
|
|
|
|
return nobody
|
|
|
|
return user
|
|
|
|
|
2011-11-28 15:50:01 -06:00
|
|
|
def getTool(self):
|
|
|
|
'''Returns the application tool.'''
|
|
|
|
return self.getPhysicalRoot().config
|
|
|
|
|
|
|
|
def getProductConfig(self):
|
|
|
|
'''Returns a reference to the config module.'''
|
|
|
|
return self.__class__.config
|
|
|
|
|
2012-02-23 08:58:06 -06:00
|
|
|
def getParent(self):
|
|
|
|
'''If this object is stored within another one, this method returns it.
|
|
|
|
Else (if the object is stored directly within the tool or the root
|
|
|
|
data folder) it returns None.'''
|
|
|
|
parent = self.getParentNode()
|
2012-03-26 12:09:45 -05:00
|
|
|
# Not-Managers can't navigate back to the tool
|
|
|
|
if (parent.id == 'config') and not self.getUser().has_role('Manager'):
|
|
|
|
return False
|
2012-11-29 13:45:21 -06:00
|
|
|
if parent.meta_type not in ('Folder', 'Temporary Folder'): return parent
|
|
|
|
|
|
|
|
def getBreadCrumb(self):
|
|
|
|
'''Gets breadcrumb info about this object and its parents.'''
|
|
|
|
res = [{'url': self.absolute_url(),
|
|
|
|
'title': self.getFieldValue('title', layoutType='view')}]
|
|
|
|
parent = self.getParent()
|
|
|
|
if parent:
|
|
|
|
res = parent.getBreadCrumb() + res
|
|
|
|
return res
|
2012-02-23 08:58:06 -06:00
|
|
|
|
2011-11-28 15:50:01 -06:00
|
|
|
def index_html(self):
|
2012-11-23 08:20:12 -06:00
|
|
|
'''Redirects to /ui.'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
if rq.has_key('do'):
|
|
|
|
# The user wants to call a method on this object and get its result
|
|
|
|
# as XML.
|
|
|
|
return self.xml(action=rq['do'])
|
|
|
|
else:
|
|
|
|
# The user wants to consult the view page for this object
|
|
|
|
return rq.RESPONSE.redirect(self.getUrl())
|
2011-11-28 15:50:01 -06:00
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def userIsAnon(self):
|
|
|
|
'''Is the currently logged user anonymous ?'''
|
|
|
|
return self.getUser().getUserName() == 'Anonymous User'
|
|
|
|
|
2011-01-19 13:51:43 -06:00
|
|
|
def getUserLanguage(self):
|
2011-01-17 07:49:56 -06:00
|
|
|
'''Gets the language (code) of the current user.'''
|
2012-04-19 02:20:15 -05:00
|
|
|
if not hasattr(self, 'REQUEST'): return 'en'
|
2012-05-29 13:50:18 -05:00
|
|
|
# Try the value which comes from the cookie. Indeed, if such a cookie is
|
|
|
|
# present, it means that the user has explicitly chosen this language
|
|
|
|
# via the language selector.
|
|
|
|
rq = self.REQUEST
|
|
|
|
if '_ZopeLg' in rq.cookies: return rq.cookies['_ZopeLg']
|
|
|
|
# Try the LANGUAGE key from the request: it corresponds to the language
|
|
|
|
# as configured in the user's browser.
|
2011-01-17 07:49:56 -06:00
|
|
|
res = self.REQUEST.get('LANGUAGE', None)
|
|
|
|
if res: return res
|
2012-05-29 13:50:18 -05:00
|
|
|
# Try the HTTP_ACCEPT_LANGUAGE key from the request, which stores
|
2011-01-17 07:49:56 -06:00
|
|
|
# language preferences as defined in the user's browser. Several
|
|
|
|
# languages can be listed, from most to less wanted.
|
|
|
|
res = self.REQUEST.get('HTTP_ACCEPT_LANGUAGE', None)
|
|
|
|
if not res: return 'en'
|
|
|
|
if ',' in res: res = res[:res.find(',')]
|
|
|
|
if '-' in res: res = res[:res.find('-')]
|
|
|
|
return res
|
|
|
|
|
2012-06-27 06:27:24 -05:00
|
|
|
def getLanguageDirection(self, lang):
|
|
|
|
'''Determines if p_lang is a LTR or RTL language.'''
|
|
|
|
if lang in rtlLanguages: return 'rtl'
|
|
|
|
return 'ltr'
|
|
|
|
|
2011-02-23 04:30:44 -06:00
|
|
|
def formatText(self, text, format='html'):
|
|
|
|
'''Produces a representation of p_text into the desired p_format, which
|
2011-06-28 02:12:20 -05:00
|
|
|
is "html" by default.'''
|
|
|
|
if 'html' in format:
|
|
|
|
if format == 'html_from_text': text = cgi.escape(text)
|
2011-02-23 04:30:44 -06:00
|
|
|
res = text.replace('\r\n', '<br/>').replace('\n', '<br/>')
|
|
|
|
elif format == 'js':
|
|
|
|
res = text.replace('\r\n', '').replace('\n', '')
|
|
|
|
res = res.replace("'", "\\'")
|
2012-05-03 03:51:54 -05:00
|
|
|
elif format == 'text':
|
|
|
|
res = text.replace('<br/>', '\n')
|
2011-02-23 04:30:44 -06:00
|
|
|
else:
|
|
|
|
res = text
|
|
|
|
return res
|
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
def translate(self, label, mapping={}, domain=None, default=None,
|
2011-09-10 18:59:22 -05:00
|
|
|
language=None, format='html', field=None):
|
2011-03-04 03:30:45 -06:00
|
|
|
'''Translates a given p_label into p_domain with p_mapping.
|
|
|
|
|
|
|
|
If p_field is given, p_label does not correspond to a full label
|
|
|
|
name, but to a label type linked to p_field: "label", "descr"
|
|
|
|
or "help". Indeed, in this case, a specific i18n mapping may be
|
|
|
|
available on the field, so we must merge this mapping into
|
2011-09-10 18:59:22 -05:00
|
|
|
p_mapping.'''
|
2009-10-18 07:52:27 -05:00
|
|
|
cfg = self.getProductConfig()
|
|
|
|
if not domain: domain = cfg.PROJECTNAME
|
2011-12-08 09:01:57 -06:00
|
|
|
# Get the label name, and the field-specific mapping if any.
|
|
|
|
if field:
|
|
|
|
# p_field is the dict version of a appy type or group
|
|
|
|
if field['type'] != 'group':
|
|
|
|
fieldMapping = field['mapping'][label]
|
|
|
|
if fieldMapping:
|
|
|
|
if callable(fieldMapping):
|
|
|
|
appyField = self.getAppyType(field['name'])
|
|
|
|
fieldMapping=appyField.callMethod(self,fieldMapping)
|
|
|
|
mapping.update(fieldMapping)
|
|
|
|
label = field['%sId' % label]
|
|
|
|
# We will get the translation from a Translation object.
|
|
|
|
# In what language must we get the translation?
|
|
|
|
if not language: language = self.getUserLanguage()
|
|
|
|
tool = self.getTool()
|
|
|
|
try:
|
|
|
|
translation = getattr(tool, language).appy()
|
|
|
|
except AttributeError:
|
|
|
|
# We have no translation for this language. Fallback to 'en'.
|
|
|
|
translation = getattr(tool, 'en').appy()
|
|
|
|
res = getattr(translation, label, '')
|
|
|
|
if not res:
|
|
|
|
# Fallback to 'en'.
|
|
|
|
translation = getattr(tool, 'en').appy()
|
2011-01-14 02:06:25 -06:00
|
|
|
res = getattr(translation, label, '')
|
2011-12-08 09:01:57 -06:00
|
|
|
# If still no result, put the label instead of a translated message
|
|
|
|
if not res: res = label
|
|
|
|
else:
|
|
|
|
# Perform replacements, according to p_format.
|
|
|
|
res = self.formatText(res, format)
|
|
|
|
# Perform variable replacements
|
|
|
|
for name, repl in mapping.iteritems():
|
|
|
|
if not isinstance(repl, basestring): repl = str(repl)
|
|
|
|
res = res.replace('${%s}' % name, repl)
|
2010-11-10 08:15:00 -06:00
|
|
|
return res
|
2010-08-05 11:23:17 -05:00
|
|
|
|
|
|
|
def getPageLayout(self, layoutType):
|
2010-09-17 08:32:48 -05:00
|
|
|
'''Returns the layout corresponding to p_layoutType for p_self.'''
|
2010-08-05 11:23:17 -05:00
|
|
|
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()
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def getPageTemplate(self, ui, templateName):
|
|
|
|
'''Returns, in the ui folder, the page template corresponding to
|
2010-08-05 11:23:17 -05:00
|
|
|
p_templateName.'''
|
2011-11-25 11:01:20 -06:00
|
|
|
res = ui
|
|
|
|
for name in templateName.split('/'): res = getattr(res, name)
|
2010-08-05 11:23:17 -05:00
|
|
|
return res
|
|
|
|
|
2012-01-04 11:03:46 -06:00
|
|
|
def download(self, name=None):
|
|
|
|
'''Downloads the content of the file that is in the File field whose
|
|
|
|
name is in the request. This name can also represent an attribute
|
2012-04-19 02:20:15 -05:00
|
|
|
storing an image within a rich text field. If p_name is not given, it
|
|
|
|
is retrieved from the request.'''
|
2010-08-05 11:23:17 -05:00
|
|
|
name = self.REQUEST.get('name')
|
|
|
|
if not name: return
|
2012-01-04 11:03:46 -06:00
|
|
|
if '_img_' not in name:
|
|
|
|
appyType = self.getAppyType(name)
|
|
|
|
else:
|
|
|
|
appyType = self.getAppyType(name.split('_img_')[0])
|
|
|
|
if not appyType.isShowable(self, 'view'):
|
|
|
|
from zExceptions import NotFound
|
|
|
|
raise NotFound()
|
2011-10-11 10:32:23 -05:00
|
|
|
theFile = getattr(self.aq_base, name, None)
|
2010-08-05 11:23:17 -05:00
|
|
|
if theFile:
|
|
|
|
response = self.REQUEST.RESPONSE
|
|
|
|
response.setHeader('Content-Disposition', 'inline;filename="%s"' % \
|
|
|
|
theFile.filename)
|
2012-01-04 11:03:46 -06:00
|
|
|
# Define content type
|
|
|
|
if theFile.content_type:
|
|
|
|
response.setHeader('Content-Type', theFile.content_type)
|
2010-08-05 11:23:17 -05:00
|
|
|
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)
|
2010-11-30 10:41:18 -06:00
|
|
|
|
2012-01-04 11:03:46 -06:00
|
|
|
def upload(self):
|
|
|
|
'''Receives an image uploaded by the user via ckeditor and stores it in
|
|
|
|
a special field on this object.'''
|
|
|
|
# Get the name of the rich text field for which an image must be stored.
|
|
|
|
params = self.REQUEST['QUERY_STRING'].split('&')
|
|
|
|
fieldName = params[0].split('=')[1]
|
|
|
|
ckNum = params[1].split('=')[1]
|
|
|
|
# We will store the image in a field named [fieldName]_img_[nb].
|
|
|
|
i = 1
|
|
|
|
attrName = '%s_img_%d' % (fieldName, i)
|
|
|
|
while True:
|
|
|
|
if not hasattr(self.aq_base, attrName):
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
i += 1
|
|
|
|
attrName = '%s_img_%d' % (fieldName, i)
|
|
|
|
# Store the image. Create a fake File instance for doing the job.
|
|
|
|
fakeFile = gen.File(isImage=True)
|
|
|
|
fakeFile.name = attrName
|
|
|
|
fakeFile.store(self, self.REQUEST['upload'])
|
|
|
|
# Return the URL of the image.
|
|
|
|
url = '%s/download?name=%s' % (self.absolute_url(), attrName)
|
2012-04-25 09:21:23 -05:00
|
|
|
response = self.REQUEST.RESPONSE
|
|
|
|
response.setHeader('Content-Type', 'text/html')
|
2012-01-04 11:03:46 -06:00
|
|
|
resp = "<script type='text/javascript'>window.parent.CKEDITOR.tools" \
|
|
|
|
".callFunction(%s, '%s');</script>" % (ckNum, url)
|
2012-04-25 09:21:23 -05:00
|
|
|
response.write(resp)
|
2012-01-04 11:03:46 -06:00
|
|
|
|
|
|
|
def allows(self, permission, raiseError=False):
|
2011-09-08 09:33:16 -05:00
|
|
|
'''Has the logged user p_permission on p_self ?'''
|
2012-01-04 11:03:46 -06:00
|
|
|
hasPermission = self.getUser().has_permission(permission, self)
|
|
|
|
if not hasPermission and raiseError:
|
|
|
|
from AccessControl import Unauthorized
|
|
|
|
raise Unauthorized
|
|
|
|
return hasPermission
|
2011-09-28 14:17:15 -05:00
|
|
|
|
|
|
|
def getEditorInit(self, name):
|
2012-01-04 11:03:46 -06:00
|
|
|
'''Gets the Javascript init code for displaying a rich editor for
|
2012-01-02 09:59:11 -06:00
|
|
|
field named p_name.'''
|
2012-01-09 10:00:47 -06:00
|
|
|
# Define the attributes that will initialize the ckeditor instance for
|
|
|
|
# this field.
|
|
|
|
field = self.getAppyType(name)
|
2012-05-05 10:04:19 -05:00
|
|
|
ckAttrs = {'toolbar': field.richText and 'AppyRich' or 'Appy',
|
2012-09-10 04:44:22 -05:00
|
|
|
'format_tags': '%s' % ';'.join(field.styles),
|
|
|
|
'width': field.width}
|
2012-01-09 10:00:47 -06:00
|
|
|
if field.allowImageUpload:
|
|
|
|
ckAttrs['filebrowserUploadUrl'] = '%s/upload' % self.absolute_url()
|
2012-09-10 04:44:22 -05:00
|
|
|
ck = []
|
2012-01-09 10:00:47 -06:00
|
|
|
for k, v in ckAttrs.iteritems():
|
2012-09-10 04:44:22 -05:00
|
|
|
if isinstance(v, int): sv = str(v)
|
|
|
|
else: sv = '"%s"' % v
|
|
|
|
ck.append('%s: %s' % (k, sv))
|
|
|
|
res = 'CKEDITOR.replace("%s", {%s})' % (name, ', '.join(ck))
|
2012-01-09 10:00:47 -06:00
|
|
|
return res
|
2011-11-25 11:01:20 -06:00
|
|
|
|
2012-01-02 09:59:11 -06:00
|
|
|
def getCalendarInit(self, name, years):
|
|
|
|
'''Gets the Javascript init code for displaying a calendar popup for
|
|
|
|
field named p_name.'''
|
|
|
|
return 'Calendar.setup({inputField: "%s", button: "%s_img", ' \
|
|
|
|
'onSelect: onSelectDate, range:[%d,%d]});' % \
|
|
|
|
(name, name, years[0], years[-1])
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def isTemporary(self):
|
|
|
|
'''Is this object temporary ?'''
|
|
|
|
parent = self.getParentNode()
|
|
|
|
if not parent: # Is propably being created through code
|
|
|
|
return False
|
|
|
|
return parent.getId() == 'temp_folder'
|
2012-07-26 10:22:22 -05:00
|
|
|
|
|
|
|
def onProcess(self):
|
|
|
|
'''This method is a general hook for transfering processing of a request
|
|
|
|
to a given field, whose name must be in the request.'''
|
|
|
|
return self.getAppyType(self.REQUEST['name']).process(self)
|
2012-10-03 07:44:34 -05:00
|
|
|
|
|
|
|
def callField(self, name, method, *args, **kwargs):
|
|
|
|
'''This method i a general hook for calling a p_method defined on a
|
|
|
|
field named p_name.'''
|
|
|
|
field = self.getAppyType(name)
|
|
|
|
exec 'res = field.%s(*args, **kwargs)' % method
|
|
|
|
return res
|
2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|