[gen] Improved search abilities. [pod] Bugfix: import several times an image from the same URL.
This commit is contained in:
parent
2307a284cc
commit
24089ef674
|
@ -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.'''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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,10 +69,10 @@
|
|||
<tal:comment replace="nothing">Section title (link triggers the default search), with action icons</tal:comment>
|
||||
<a tal:define="queryParam python: searchInfo['default'] and searchInfo['default']['name'] or ''"
|
||||
tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, queryParam);
|
||||
class python:test(not currentSearch and (currentClass==rootClass), 'portletCurrent', '')"
|
||||
class python: (not currentSearch and (currentClass==rootClass) and (currentPage=='query')) and 'portletCurrent' or ''"
|
||||
tal:content="structure python: _(rootClass + '_plural')">
|
||||
</a>
|
||||
<span tal:define="addPermission python: '%s: Add %s' % (appName, rootClass);
|
||||
<tal:icons define="addPermission python: '%s: Add %s' % (appName, rootClass);
|
||||
userMayAdd python: user.has_permission(addPermission, appFolder);
|
||||
createMeans python: tool.getCreateMeans(rootClass)">
|
||||
<tal:comment replace="nothing">Create a new object from a web form</tal:comment>
|
||||
|
@ -86,15 +87,33 @@
|
|||
title python: _('query_import')">
|
||||
<img tal:attributes="src string: $appUrl/ui/import.png"/>
|
||||
</a>
|
||||
<tal:comment replace="nothing">Search objects of this type</tal:comment>
|
||||
<a tal:define="showSearch python: tool.getAttr('enableAdvancedSearchFor%s' % rootClass)"
|
||||
tal:condition="showSearch"
|
||||
tal:attributes="href python: '%s/ui/search?className=%s' % (toolUrl, rootClass);
|
||||
title python: _('search_objects')">
|
||||
<img tal:attributes="src string: $appUrl/ui/search.gif"/>
|
||||
</tal:icons>
|
||||
<br/>
|
||||
|
||||
<tal:search condition="python: tool.getAttr('enableAdvancedSearchFor%s' % rootClass)">
|
||||
<tal:comment replace="nothing">Live search</tal:comment>
|
||||
<form tal:attributes="action string: $appUrl/config/do">
|
||||
<input type="hidden" name="action" value="SearchObjects"/>
|
||||
<input type="hidden" name="className" tal:attributes="value rootClass"/>
|
||||
<table cellpadding="0" cellspacing="0">
|
||||
<tr valign="middle">
|
||||
<td style="padding-right: 3px"><input type="text" size="14" name="w_SearchableText"/></td>
|
||||
<td><input type="image" style="cursor:pointer"
|
||||
tal:attributes="src string: $appUrl/ui/search.gif;
|
||||
title python: _('search_button')"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<tal:comment replace="nothing">Advanced search</tal:comment>
|
||||
<div tal:define="highlighted python: (currentClass == rootClass) and (currentPage == 'search')"
|
||||
tal:attributes="class python: highlighted and 'portletSearch portletCurrent' or 'portletSearch'">
|
||||
<a tal:define="text python: _('search_title')"
|
||||
tal:attributes="href python: '%s/ui/search?className=%s' % (toolUrl, rootClass); title text">
|
||||
<span tal:replace="text"/>...
|
||||
</a>
|
||||
</span>
|
||||
<tal:comment replace="nothing">Searches for this content type</tal:comment>
|
||||
</div>
|
||||
</tal:search>
|
||||
<tal:comment replace="nothing">Predefined searches</tal:comment>
|
||||
<tal:widget repeat="widget searchInfo/searches">
|
||||
<tal:group condition="python: widget['type'] == 'group'">
|
||||
<metal:s use-macro="app/ui/portlet/macros/group"/>
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
<tal:comment replace="nothing">The search icon if field is queryable</tal:comment>
|
||||
<a tal:condition="python: objs and appyType['queryable']"
|
||||
tal:attributes="href python: '%s/ui/search?className=%s&ref=%s:%s' % (tool.absolute_url(), linkedPortalType, contextObj.UID(), appyType['name'])">
|
||||
<img src="search.gif" tal:attributes="title python: _('search_objects')"/></a>
|
||||
<img src="search.gif" tal:attributes="title python: _('search_title')"/></a>
|
||||
</div>
|
||||
|
||||
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue