diff --git a/gen/__init__.py b/gen/__init__.py
index d3f50d0..58a23ca 100644
--- a/gen/__init__.py
+++ b/gen/__init__.py
@@ -317,6 +317,7 @@ class Search:
# In the dict below, keys are indexed field names and values are
# search values.
self.fields = fields
+
@staticmethod
def getIndexName(fieldName, usage='search'):
'''Gets the name of the technical index that corresponds to field named
@@ -333,13 +334,15 @@ class Search:
elif fieldName in defaultIndexes: return fieldName
else:
return 'get%s%s'% (fieldName[0].upper(),fieldName[1:])
+
@staticmethod
def getSearchValue(fieldName, fieldValue):
'''Returns a transformed p_fieldValue for producing a valid search
value as required for searching in the index corresponding to
p_fieldName.'''
- if fieldName == 'title':
- # Title is a TextIndex. We must split p_fieldValue into keywords.
+ if fieldName in ('title', 'SearchableText'):
+ # Title and SearchableText are TextIndex indexes. We must split
+ # p_fieldValue into keywords.
res = Keywords(fieldValue).get()
elif isinstance(fieldValue, basestring) and fieldValue.endswith('*'):
v = fieldValue[:-1]
@@ -366,6 +369,38 @@ class Search:
res = fieldValue
return res
+ def updateSearchCriteria(self, criteria, advanced=False):
+ '''This method updates dict p_criteria with all the search criteria
+ corresponding to this Search instance. If p_advanced is True,
+ p_criteria correspond to an advanced search, to be stored in the
+ session: in this case we need to keep the Appy names for parameters
+ sortBy and sortOrder (and not "resolve" them to Zope's sort_on and
+ sort_order).'''
+ # Put search criteria in p_criteria
+ for fieldName, fieldValue in self.fields.iteritems():
+ # Management of searches restricted to objects linked through a
+ # Ref field: not implemented yet.
+ if fieldName == '_ref': continue
+ # Make the correspondence between the name of the field and the
+ # name of the corresponding index, excepted if advanced is True: in
+ # that case, the correspondence will be done later.
+ if not advanced:
+ attrName = Search.getIndexName(fieldName)
+ # Express the field value in the way needed by the index
+ criteria[attrName]= Search.getSearchValue(fieldName, fieldValue)
+ else:
+ criteria[fieldName]= fieldValue
+ # Add a sort order if specified
+ if self.sortBy:
+ if not advanced:
+ criteria['sort_on'] = Search.getIndexName(self.sortBy,
+ usage='sort')
+ if self.sortOrder == 'desc': criteria['sort_order'] = 'reverse'
+ else: criteria['sort_order'] = None
+ else:
+ criteria['sortBy'] = self.sortBy
+ criteria['sortOrder'] = self.sortOrder
+
# ------------------------------------------------------------------------------
class Type:
'''Basic abstract class for defining any appy type.'''
diff --git a/gen/installer.py b/gen/installer.py
index 22e65d4..93c03a3 100644
--- a/gen/installer.py
+++ b/gen/installer.py
@@ -176,6 +176,12 @@ class ZopeInstaller:
wrapperClass = tool.getAppyClass(className, wrapper=True)
indexInfo.update(wrapperClass.getIndexes(includeDefaults=False))
updateIndexes(self, indexInfo)
+ # Re-index index "SearchableText", wrongly defined for Appy < 0.8.3.
+ stIndex = catalog.Indexes['SearchableText']
+ if stIndex.indexSize() == 0:
+ self.logger.info('Reindexing SearchableText...')
+ catalog.reindexIndex('SearchableText', self.app.REQUEST)
+ self.logger.info('Done.')
def getAddPermission(self, className):
'''What is the name of the permission allowing to create instances of
@@ -364,7 +370,8 @@ class ZopeInstaller:
wrapperClass = klass.wrapperClass
if not hasattr(wrapperClass, 'title'):
# Special field "type" is mandatory for every class.
- title = gen.String(multiplicity=(1,1),show='edit',indexed=True)
+ title = gen.String(multiplicity=(1,1), show='edit',
+ indexed=True, searchable=True)
title.init('title', None, 'appy')
setattr(wrapperClass, 'title', title)
# Special field "state" must be added for every class
diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py
index ffa7330..5eebb63 100644
--- a/gen/mixins/ToolMixin.py
+++ b/gen/mixins/ToolMixin.py
@@ -337,22 +337,8 @@ class ToolMixin(BaseMixin):
# Manage additional criteria from a search when relevant
if searchName: search = self.getSearch(className, searchName)
if search:
- # Add additional search criteria
- for fieldName, fieldValue in search.fields.iteritems():
- # Management of searches restricted to objects linked through a
- # Ref field: not implemented yet.
- if fieldName == '_ref': continue
- # Make the correspondance between the name of the field and the
- # name of the corresponding index.
- attrName = Search.getIndexName(fieldName)
- # Express the field value in the way needed by the index
- params[attrName] = Search.getSearchValue(fieldName, fieldValue)
- # Add a sort order if specified
- sortKey = search.sortBy
- if sortKey:
- params['sort_on'] = Search.getIndexName(sortKey, usage='sort')
- if search.sortOrder == 'desc': params['sort_order'] = 'reverse'
- else: params['sort_order'] = None
+ # Add in params search and sort criteria.
+ search.updateSearchCriteria(params)
# Determine or override sort if specified.
if sortBy:
params['sort_on'] = Search.getIndexName(sortBy, usage='sort')
@@ -587,6 +573,21 @@ class ToolMixin(BaseMixin):
day = int(day)-1
return res
+ def _getDefaultSearchCriteria(self):
+ '''We are about to perform an advanced search on instances of a given
+ class. Check, on this class, if in field Class.searchAdvanced, some
+ default criteria (field values, sort filters, etc) exist, and, if
+ yes, return it.'''
+ res = {}
+ rq = self.REQUEST
+ if 'className' not in rq.form: return res
+ klass = self.getAppyClass(rq.form['className'])
+ if not hasattr(klass, 'searchAdvanced'): return res
+ # In this attribute, we have the Search instance representing automatic
+ # advanced search criteria.
+ klass.searchAdvanced.updateSearchCriteria(res, advanced=True)
+ return res
+
transformMethods = {'uppercase': 'upper', 'lowercase': 'lower',
'capitalize': 'capitalize'}
def onSearchObjects(self):
@@ -594,7 +595,7 @@ class ToolMixin(BaseMixin):
search.pt.'''
rq = self.REQUEST
# Store the search criteria in the session
- criteria = {}
+ criteria = self._getDefaultSearchCriteria()
for attrName in rq.form.keys():
if attrName.startswith('w_') and \
not self._searchValueIsEmpty(attrName):
diff --git a/gen/model.py b/gen/model.py
index 97d670b..9297fbc 100644
--- a/gen/model.py
+++ b/gen/model.py
@@ -265,7 +265,8 @@ class Tool(ModelClass):
# Document generation page
dgp = {'page': gen.Page('documents', show=isManagerEdit)}
def validPythonWithUno(self, value): pass # Real method in the wrapper
- unoEnabledPython = gen.String(show=False,validator=validPythonWithUno,**dgp)
+ unoEnabledPython = gen.String(default='/usr/bin/python', show=False,
+ validator=validPythonWithUno, **dgp)
openOfficePort = gen.Integer(default=2002, show=False, **dgp)
# User interface page
numberOfResultsPerPage = gen.Integer(default=30,
diff --git a/gen/po.py b/gen/po.py
index f4301b1..ce92075 100644
--- a/gen/po.py
+++ b/gen/po.py
@@ -52,7 +52,6 @@ appyLabels = [
('import_done', 'Import terminated successfully.'),
('search_title', 'Advanced search'),
('search_button', 'Search'),
- ('search_objects', 'Search objects of this type.'),
('search_results', 'Search results'),
('search_results_descr', ' '),
('search_new', 'New search'),
@@ -108,8 +107,8 @@ appyLabels = [
('app_home', 'Home'),
('login_reserved', 'This login is reserved.'),
('login_in_use', 'This login is already in use.'),
- ('login_ko', 'Welcome! You are now logged in.'),
- ('login_ok', 'Login failed.'),
+ ('login_ko', 'Login failed.'),
+ ('login_ok', 'Welcome! You are now logged in.'),
('password_too_short', 'Passwords must contain at least ${nb} characters.'),
('passwords_mismatch', 'Passwords do not match.'),
('object_save', 'Save'),
diff --git a/gen/ui/portlet.pt b/gen/ui/portlet.pt
index 90b5a46..44c2a8e 100644
--- a/gen/ui/portlet.pt
+++ b/gen/ui/portlet.pt
@@ -50,6 +50,7 @@
appUrl app/absolute_url;
currentSearch req/search|nothing;
currentClass req/className|nothing;
+ currentPage python: req['PATH_INFO'].rsplit('/',1)[-1];
rootClasses tool/getRootClasses;
phases python: contextObj and contextObj.getAppyPhases() or None">
@@ -68,12 +69,12 @@
Section title (link triggers the default search), with action icons
-
+
Create a new object from a web form
- Search objects of this type
-
-
-
-
- Searches for this content type
+
+
+
+
+ Live search
+
+ Advanced search
+
+
+ Predefined searches
diff --git a/gen/ui/widgets/ref.pt b/gen/ui/widgets/ref.pt
index e8434e8..40f5b49 100644
--- a/gen/ui/widgets/ref.pt
+++ b/gen/ui/widgets/ref.pt
@@ -158,7 +158,7 @@
The search icon if field is queryable
-
+
Appy (top) navigation
diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py
index 578e0de..194cbf9 100644
--- a/gen/wrappers/ToolWrapper.py
+++ b/gen/wrappers/ToolWrapper.py
@@ -97,7 +97,7 @@ class ToolWrapper(AbstractWrapper):
"resultColumns"
Stores the list of columns that must be shown when displaying
- instances of the a given root p_klass.
+ instances of a given root p_klass.
"enableAdvancedSearch"
Determines if the advanced search screen must be enabled for
diff --git a/pod/doc_importers.py b/pod/doc_importers.py
index 623af46..480b982 100644
--- a/pod/doc_importers.py
+++ b/pod/doc_importers.py
@@ -174,6 +174,8 @@ pxToCm = 44.173513561
def getSize(filePath, fileType):
'''Gets the size of an image by reading first bytes.'''
x, y = (None, None)
+ # Get fileType from filePath if not given.
+ if not fileType: fileType = os.path.splitext(filePath)[1][1:]
f = file(filePath, 'rb')
if fileType in jpgTypes:
# Dummy read to skip header ID