diff --git a/gen/__init__.py b/gen/__init__.py
index 1e781e6..a1f9450 100644
--- a/gen/__init__.py
+++ b/gen/__init__.py
@@ -688,7 +688,13 @@ class Type:
if not self.editDefault:
# Return self.default, of self.default() if it is a method
if type(self.default) == types.FunctionType:
- return self.default(obj.appy())
+ appyObj = obj.appy()
+ try:
+ return self.default(appyObj)
+ except Exception, e:
+ appyObj.log('Exception while getting default value ' \
+ 'of field "%s": %s.' % (self.name, str(e)))
+ return None
else:
return self.default
# If value is editable, get the default value from the tool
@@ -704,6 +710,11 @@ class Type:
if self.isEmptyValue(value): return ''
return value
+ def getIndexType(self):
+ '''Returns the name of the technical, Zope-level index type for this
+ field.'''
+ return 'FieldIndex'
+
def getIndexValue(self, obj, forSearch=False):
'''This method returns a version for this field value on p_obj that is
ready for indexing purposes. Needs to be overridden by some child
@@ -1130,6 +1141,13 @@ class String(Type):
res = str(value)
return res
+ def getIndexValue(self, obj, forSearch=False):
+ '''For indexing purposes, we return only strings, not unicodes.'''
+ res = Type.getIndexValue(self, obj, forSearch)
+ if isinstance(res, unicode):
+ res = res.encode('utf-8')
+ return res
+
def getPossibleValues(self,obj,withTranslations=False,withBlankValue=False):
'''Returns the list of possible values for this field if it is a
selection field. If p_withTranslations is True,
@@ -1236,6 +1254,14 @@ class String(Type):
value = [value]
exec 'obj.%s = value' % self.name
+ def getIndexType(self):
+ '''Index type varies depending on String parameters.'''
+ # If String.isSelect, be it multivalued or not, we define a ZCTextIndex:
+ # this way we can use AND/OR operator.
+ if self.isSelect or (self.format in (String.TEXT, String.XHTML)):
+ return 'ZCTextIndex'
+ return Type.getIndexType(self)
+
class Boolean(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, show=True,
@@ -1291,10 +1317,11 @@ class Date(Type):
masterValue, focus, historized, True)
def getCss(self, layoutType):
- if layoutType == 'edit': return ('jscalendar/calendar-system.css',)
+ if (layoutType == 'edit') and self.calendar:
+ return ('jscalendar/calendar-system.css',)
def getJs(self, layoutType):
- if layoutType == 'edit':
+ if (layoutType == 'edit') and self.calendar:
return ('jscalendar/calendar_stripped.js',
'jscalendar/calendar-en.js')
diff --git a/gen/plone25/installer.py b/gen/plone25/installer.py
index 4cbc36c..2096349 100644
--- a/gen/plone25/installer.py
+++ b/gen/plone25/installer.py
@@ -247,6 +247,8 @@ class PloneInstaller:
appyType.template)
if os.path.exists(fileName):
setattr(self.appyTool, attrName, fileName)
+ self.appyTool.log('Imported "%s" in the tool in ' \
+ 'attribute "%s"'% (fileName,attrName))
else:
self.appyTool.log('Template "%s" was not found!' % \
fileName, type='error')
@@ -353,7 +355,7 @@ class PloneInstaller:
workflow = wfMethod(self, workflowName)
wfTool._setObject(workflowName, workflow)
else:
- self.log('%s already in workflows.' % workflowName)
+ self.appyTool.log('%s already in workflows.' % workflowName)
# Link the workflow to the current content type
wfTool.setChainForPortalTypes([contentType], workflowName)
return wfTool
@@ -404,14 +406,10 @@ class PloneInstaller:
indexInfo = {}
for className, appyTypes in self.attributes.iteritems():
for appyType in appyTypes:
- if appyType.indexed:
- n = appyType.name
- indexName = 'get%s%s' % (n[0].upper(), n[1:])
- indexType = 'FieldIndex'
- if (appyType.type == 'String') and appyType.isSelect and \
- appyType.isMultiValued():
- indexType = 'ZCTextIndex'
- indexInfo[indexName] = indexType
+ if not appyType.indexed or (appyType.name == 'title'): continue
+ n = appyType.name
+ indexName = 'get%s%s' % (n[0].upper(), n[1:])
+ indexInfo[indexName] = appyType.getIndexType()
if indexInfo:
PloneInstaller.updateIndexes(self.ploneSite, indexInfo, self)
@@ -454,11 +452,9 @@ class PloneInstaller:
frontPageName = self.productName + 'FrontPage'
site.manage_changeProperties(default_page=frontPageName)
- def log(self, msg): print msg
- def info(self, msg): return self.log(msg)
+ def info(self, msg): return self.appyTool.log(msg)
def install(self):
- self.log("Installation of %s:" % self.productName)
if self.minimalistPlone: self.customizePlone()
self.installRootFolder()
self.installTypes()
@@ -470,7 +466,7 @@ class PloneInstaller:
self.manageIndexes()
self.manageLanguages()
self.finalizeInstallation()
- self.log("Installation of %s done." % self.productName)
+ self.appyTool.log("Installation of %s done." % self.productName)
def uninstallTool(self):
site = self.ploneSite
@@ -496,10 +492,8 @@ class PloneInstaller:
nvProps.manage_changeProperties(**{'idsNotToList': current})
def uninstall(self):
- self.log("Uninstallation of %s:" % self.productName)
self.uninstallTool()
- self.log("Uninstallation of %s done." % self.productName)
- return self.toLog.getvalue()
+ return 'Done.'
# Stuff for tracking user activity ---------------------------------------------
loggedUsers = {}
diff --git a/gen/plone25/mixins/ToolMixin.py b/gen/plone25/mixins/ToolMixin.py
index 10b98e1..ee0d8ff 100644
--- a/gen/plone25/mixins/ToolMixin.py
+++ b/gen/plone25/mixins/ToolMixin.py
@@ -165,10 +165,12 @@ class ToolMixin(BaseMixin):
return res
def getSearchInfo(self, contentType, refInfo=None):
- '''Returns a tuple:
- 1) The list of searchable fields (ie fields among all indexed fields)
- 2) The number of columns for layouting those fields.'''
+ '''Returns, as a dict:
+ - the list of searchable fields (= some fields among all indexed
+ fields);
+ - the number of columns for layouting those fields.'''
fields = []
+ fieldDicts = []
if refInfo:
# The search is triggered from a Ref field.
refField = self.getRefInfo(refInfo)[1]
@@ -180,9 +182,12 @@ class ToolMixin(BaseMixin):
fieldNames = getattr(at, 'searchFieldsFor%s' % contentType,())
nbOfColumns = getattr(at, 'numberOfSearchColumnsFor%s' %contentType)
for name in fieldNames:
- appyType = self.getAppyType(name, asDict=True,className=contentType)
+ appyType = self.getAppyType(name,asDict=False,className=contentType)
+ appyDict = self.getAppyType(name, asDict=True,className=contentType)
fields.append(appyType)
- return fields, nbOfColumns
+ fieldDicts.append(appyDict)
+ return {'fields': fields, 'nbOfColumns': nbOfColumns,
+ 'fieldDicts': fieldDicts}
def getImportElements(self, contentType):
'''Returns the list of elements that can be imported from p_path for
@@ -336,13 +341,12 @@ class ToolMixin(BaseMixin):
if not searchName:
# It is the global search for all objects pf p_contentType
searchName = contentType
- s = self.REQUEST.SESSION
uids = {}
i = -1
for obj in res.objects:
i += 1
uids[startNumber+i] = obj.UID()
- s['search_%s' % searchName] = uids
+ self.REQUEST.SESSION['search_%s' % searchName] = uids
return res.__dict__
def getResultColumnsNames(self, contentType, refField):
@@ -452,15 +456,6 @@ class ToolMixin(BaseMixin):
return pythonClass.maySearch(self.appy())
return True
- def userMayNavigate(self, obj):
- '''This method checks if the currently logged user can display the
- navigation panel within the portlet. This is done by calling method
- "mayNavigate" on the currently shown object. If no such method
- exists, we return True.'''
- appyObj = obj.appy()
- if hasattr(appyObj, 'mayNavigate'): return appyObj.mayNavigate()
- return True
-
def onImportObjects(self):
'''This method is called when the user wants to create objects from
external data.'''
@@ -614,7 +609,7 @@ class ToolMixin(BaseMixin):
field, this method returns information about this reference: the
source content type and the Ref field (Appy type). If p_refInfo is
not given, we search it among search criteria in the session.'''
- if not refInfo:
+ if not refInfo and (self.REQUEST.get('search', None) == '_advanced'):
criteria = self.REQUEST.SESSION.get('searchCriteria', None)
if criteria and criteria.has_key('_ref'): refInfo = criteria['_ref']
if not refInfo: return ('', None)
@@ -847,6 +842,8 @@ class ToolMixin(BaseMixin):
session.invalidate()
from Products.CMFPlone import transaction_note
transaction_note('Logged out')
+ self.getProductConfig().logger.info('User "%s" has been logged out.' % \
+ userId)
# Remove user from variable "loggedUsers"
from appy.gen.plone25.installer import loggedUsers
if loggedUsers.has_key(userId): del loggedUsers[userId]
diff --git a/gen/plone25/mixins/__init__.py b/gen/plone25/mixins/__init__.py
index f6f6189..d33ccaa 100644
--- a/gen/plone25/mixins/__init__.py
+++ b/gen/plone25/mixins/__init__.py
@@ -276,16 +276,22 @@ class BaseMixin:
res[appyType.name] = appyType.getValue(self)
return res
- def addDataChange(self, changes):
+ def addDataChange(self, changes, notForPreviouslyEmptyValues=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.'''
+ 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.'''
# Add to the p_changes dict the field labels
- for fieldName in changes.iterkeys():
+ for fieldName in changes.keys():
appyType = self.getAppyType(fieldName)
- changes[fieldName] = (changes[fieldName], appyType.labelId)
+ if notForPreviouslyEmptyValues and \
+ appyType.isEmptyValue(changes[fieldName], self):
+ del changes[fieldName]
+ else:
+ 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')
@@ -474,21 +480,23 @@ class BaseMixin:
res.append(appyType)
return res
- def getCssAndJs(self, layoutType, page):
- '''Returns the CSS and Javascript files that need to be loaded by the
- p_page for the given p_layoutType.'''
+ def getCssAndJs(self, fields, layoutType):
+ '''Gets the list of Javascript and CSS files required by Appy types
+ p_fields when shown on p_layoutType.'''
+ # lists css and js below are not sets, because order of Javascript
+ # inclusion can be important, and this could be losed by using sets.
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
+ for field in fields:
+ fieldCss = field.getCss(layoutType)
+ if fieldCss:
+ for fcss in fieldCss:
+ if fcss not in css: css.append(fcss)
+ fieldJs = field.getJs(layoutType)
+ if fieldJs:
+ for fjs in fieldJs:
+ if fjs not in js: js.append(fjs)
+ return {'css':css, 'js':js}
def getAppyTypesFromNames(self, fieldNames, asDict=True, addTitle=True):
'''Gets the Appy types named p_fieldNames. If 'title' is not among
@@ -766,12 +774,27 @@ class BaseMixin:
res = True
return res
+ 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
+
+ def mayDelete(self):
+ '''May the currently logged user delete this object? This condition
+ comes as an addition/refinement to the corresponding workflow
+ permission.'''
+ appyObj = self.appy()
+ if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete()
+ return True
+
def executeAppyAction(self, actionName, reindex=True):
'''Executes action with p_fieldName on this object.'''
appyType = self.getAppyType(actionName)
actionRes = appyType(self.appy())
if self.getParentNode().get(self.id):
- # Else, it means that the action has led to self's removal.
+ # Else, it means that the action has led to self's deletion.
self.reindexObject()
return appyType.result, actionRes
@@ -869,7 +892,17 @@ class BaseMixin:
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)
+ 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)
def appy(self):
'''Returns a wrapper object allowing to manipulate p_self the Appy
diff --git a/gen/plone25/model.py b/gen/plone25/model.py
index 7ae7583..6568387 100644
--- a/gen/plone25/model.py
+++ b/gen/plone25/model.py
@@ -84,7 +84,7 @@ class User(ModelClass):
validator=validatePassword, **gm)
password2 = String(format=String.PASSWORD, show=showPassword, **gm)
gm['multiplicity'] = (0, None)
- roles = String(validator=Selection('getGrantableRoles'), **gm)
+ roles = String(validator=Selection('getGrantableRoles'), indexed=True, **gm)
# The Tool class ---------------------------------------------------------------
diff --git a/gen/plone25/skin/edit.pt b/gen/plone25/skin/edit.pt
index d43178f..bce7b84 100644
--- a/gen/plone25/skin/edit.pt
+++ b/gen/plone25/skin/edit.pt
@@ -6,12 +6,10 @@
tool contextObj/getTool;
appFolder tool/getAppFolder;
appName appFolder/getId;
- phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='edit');
+ phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType=layoutType);
phase phaseInfo/name;
page request/page|python:'main';
- cssAndJs python: contextObj.getCssAndJs(layoutType, page);
- css python: cssAndJs[0];
- js python: cssAndJs[1];
+ cssJs python: contextObj.getCssAndJs(contextObj.getAppyTypes(layoutType, page), layoutType);
confirmMsg request/confirmMsg | nothing;">
Include type-specific CSS and JS.
-
+
+
+
+
+
+
+
+
+
+