Remove now obsolete loops.search package.
This commit is contained in:
parent
ad8c236aa5
commit
0d1bb5a722
6 changed files with 0 additions and 955 deletions
|
@ -1,239 +0,0 @@
|
|||
===================================================================
|
||||
loops.search - Provide search functionality for the loops framework
|
||||
===================================================================
|
||||
|
||||
($Id$)
|
||||
|
||||
|
||||
Let's do some basic set up
|
||||
|
||||
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
|
||||
>>> site = placefulSetUp(True)
|
||||
|
||||
>>> from zope import component, interface
|
||||
>>> from zope.interface import implements
|
||||
|
||||
and setup a simple loops site with a concept manager and some concepts
|
||||
(with all the type machinery, what in real life is done via standard
|
||||
ZCML setup):
|
||||
|
||||
>>> from loops.concept import Concept
|
||||
>>> from loops.type import ConceptType, TypeConcept
|
||||
>>> from loops.interfaces import ITypeConcept
|
||||
>>> from loops.base import Loops
|
||||
>>> from loops.expert.testsetup import TestSite
|
||||
>>> t = TestSite(site)
|
||||
>>> concepts, resources, views = t.setup()
|
||||
|
||||
>>> loopsRoot = site['loops']
|
||||
>>> query = concepts['query']
|
||||
>>> topic = concepts['topic']
|
||||
|
||||
In addition we create a concept that holds the search page and a node
|
||||
(page) that links to this concept:
|
||||
|
||||
>>> search = concepts['search'] = Concept(u'Search')
|
||||
>>> search.conceptType = query
|
||||
|
||||
>>> from loops.view import Node
|
||||
>>> page = views['page'] = Node('Search Page')
|
||||
>>> page.target = search
|
||||
|
||||
Search views
|
||||
------------
|
||||
|
||||
Now we are ready to create a search view object:
|
||||
|
||||
>>> from zope.publisher.browser import TestRequest
|
||||
>>> from loops.expert.browser.search import Search
|
||||
>>> searchView = Search(search, TestRequest())
|
||||
|
||||
The search view provides values for identifying the search form itself
|
||||
and the parameter rows; the rowNum is auto-incremented, so it should be
|
||||
accessed exactly once per row:
|
||||
|
||||
>>> searchView.rowNum
|
||||
1
|
||||
>>> searchView.rowNum
|
||||
2
|
||||
|
||||
The search view provides vocabularies for types that allow the selection
|
||||
of types to search for; this needs an ITypeManager adapter registered via
|
||||
zcml in real life:
|
||||
|
||||
>>> from loops.type import LoopsTypeManager
|
||||
>>> component.provideAdapter(LoopsTypeManager)
|
||||
|
||||
>>> t = searchView.typesForSearch()
|
||||
>>> len(t)
|
||||
14
|
||||
>>> t.getTermByToken('loops:resource:*').title
|
||||
'Any Resource'
|
||||
|
||||
>>> t = searchView.conceptTypesForSearch()
|
||||
>>> len(t)
|
||||
11
|
||||
>>> t.getTermByToken('loops:concept:*').title
|
||||
'Any Concept'
|
||||
|
||||
To execute the search in the context of a node we have to set up a node
|
||||
view for our page. The submitReplacing method returns a JavaScript call
|
||||
that will replace the results part on the search page; as this registers
|
||||
the dojo library with the view's controller we also have to supply
|
||||
a controller attribute for the search view.
|
||||
|
||||
>>> from loops.browser.node import NodeView
|
||||
>>> request = TestRequest()
|
||||
>>> pageView = NodeView(page, request)
|
||||
|
||||
>>> from cybertools.browser.liquid.controller import Controller
|
||||
>>> searchView.controller = Controller(searchView, request)
|
||||
|
||||
>>> searchView.submitReplacing('1.results', '1.search.form', pageView)
|
||||
'submitReplacing("1.results", "1.search.form",
|
||||
"http://127.0.0.1/loops/views/page/.target80/@@searchresults.html");...'
|
||||
|
||||
Basic (text/title) search
|
||||
-------------------------
|
||||
|
||||
The searchresults.html view, i.e. the SearchResults view class provides the
|
||||
result set of the search via its `results` property.
|
||||
|
||||
Before accessing the `results` property we have to prepare a
|
||||
resource we can search for and index it in the catalog.
|
||||
|
||||
>>> from loops.resource import Resource
|
||||
>>> rplone = resources['plone'] = Resource()
|
||||
|
||||
>>> from zope.app.catalog.interfaces import ICatalog
|
||||
>>> from loops import util
|
||||
>>> catalog = component.getUtility(ICatalog)
|
||||
>>> catalog.index_doc(int(util.getUidForObject(rplone)), rplone)
|
||||
|
||||
>>> from loops.expert.browser.search import SearchResults
|
||||
>>> form = {'search.2.title': True, 'search.2.text': u'plone'}
|
||||
>>> request = TestRequest(form=form)
|
||||
>>> resultsView = SearchResults(page, request)
|
||||
|
||||
>>> results = list(resultsView.results)
|
||||
>>> len(results)
|
||||
1
|
||||
>>> results[0].context == rplone
|
||||
True
|
||||
|
||||
>>> form = {'search.2.title': True, 'search.2.text': u'foo'}
|
||||
>>> request = TestRequest(form=form)
|
||||
>>> resultsView = SearchResults(page, request)
|
||||
>>> len(list(resultsView.results))
|
||||
0
|
||||
|
||||
Search via related concepts
|
||||
---------------------------
|
||||
|
||||
We first have to prepare some test concepts (topics); we also assign our test
|
||||
resource (rplone) from above to one of the topics:
|
||||
|
||||
>>> czope = concepts['zope'] = Concept(u'Zope')
|
||||
>>> czope2 = concepts['zope2'] = Concept(u'Zope 2')
|
||||
>>> czope3 = concepts['zope3'] = Concept(u'Zope 3')
|
||||
>>> cplone = concepts['plone'] = Concept(u'Plone')
|
||||
>>> for c in (czope, czope2, czope3, cplone):
|
||||
... c.conceptType = topic
|
||||
... catalog.index_doc(int(util.getUidForObject(c)), c)
|
||||
>>> czope.assignChild(czope2)
|
||||
>>> czope.assignChild(czope3)
|
||||
>>> czope2.assignChild(cplone)
|
||||
>>> rplone.assignConcept(cplone)
|
||||
|
||||
Now we can fill our search form and execute the query; note that all concepts
|
||||
found are listed, plus all their children and all resources associated
|
||||
with them:
|
||||
|
||||
>>> uid = util.getUidForObject(concepts['zope'])
|
||||
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid}
|
||||
>>> request = TestRequest(form=form)
|
||||
>>> resultsView = SearchResults(page, request)
|
||||
>>> results = list(resultsView.results)
|
||||
>>> len(results)
|
||||
5
|
||||
>>> results[0].context.__name__
|
||||
u'plone'
|
||||
|
||||
>>> uid = util.getUidForObject(concepts['zope3'])
|
||||
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': uid}
|
||||
>>> request = TestRequest(form=form)
|
||||
>>> resultsView = SearchResults(page, request)
|
||||
>>> results = list(resultsView.results)
|
||||
>>> len(results)
|
||||
1
|
||||
>>> results[0].context.__name__
|
||||
u'zope3'
|
||||
|
||||
To support easy entry of concepts to search for we can preselect the available
|
||||
concepts (optionally restricted to a certain type) by entering text parts
|
||||
of the concepts' titles:
|
||||
|
||||
>>> form = {'searchType': 'loops:concept:topic', 'name': u'zope'}
|
||||
>>> request = TestRequest(form=form)
|
||||
>>> view = Search(page, request)
|
||||
>>> view.listConcepts()
|
||||
u"{identifier: 'id', items: [{label: 'Zope (Topic)', name: 'Zope', id: '85'}, {label: 'Zope 2 (Topic)', name: 'Zope 2', id: '87'}, {label: 'Zope 3 (Topic)', name: 'Zope 3', id: '89'}]}"
|
||||
|
||||
Preset Concept Types on Search Forms
|
||||
------------------------------------
|
||||
|
||||
Often we want to include certain types in our search. We can instruct
|
||||
the search form to include lines for these types by giving these types
|
||||
a certain qualifier, via the option attribute of the type interface.
|
||||
|
||||
Let's start with a new type, the customer type.
|
||||
|
||||
>>> customer = concepts['customer']
|
||||
>>> custType = ITypeConcept(customer)
|
||||
>>> custType.options
|
||||
[]
|
||||
|
||||
>>> cust1 = concepts['cust1']
|
||||
>>> cust2 = concepts['cust2']
|
||||
>>> for c in (cust1, cust2):
|
||||
... c.conceptType = customer
|
||||
... catalog.index_doc(int(util.getUidForObject(c)), c)
|
||||
|
||||
>>> from cybertools.typology.interfaces import IType
|
||||
>>> IType(cust1).qualifiers
|
||||
('concept',)
|
||||
|
||||
>>> searchView = Search(search, TestRequest())
|
||||
>>> list(searchView.presetSearchTypes)
|
||||
[]
|
||||
|
||||
We can now add a 'search' qualifier to the customer type's options
|
||||
and thus include the customer type in the preset search types.
|
||||
|
||||
>>> custType.options = ('qualifier:search',)
|
||||
>>> IType(cust1).qualifiers
|
||||
('concept', 'search')
|
||||
>>> searchView = Search(search, TestRequest())
|
||||
>>> list(searchView.presetSearchTypes)
|
||||
[{'token': 'loops:concept:customer', 'title': u'Customer'}]
|
||||
|
||||
>>> searchView.conceptsForType('loops:concept:customer')
|
||||
[{'token': 'none', 'title': u'not selected'},
|
||||
{'token': '58', 'title': u'Customer 1'},
|
||||
{'token': '60', 'title': u'Customer 2'},
|
||||
{'token': '62', 'title': u'Customer 3'}]
|
||||
|
||||
Let's use this new search option for querying:
|
||||
|
||||
>>> form = {'search.4.text_selected': u'58'}
|
||||
>>> resultsView = SearchResults(page, TestRequest(form=form))
|
||||
>>> results = list(resultsView.results)
|
||||
>>> results[0].title
|
||||
u'Customer 1'
|
||||
|
||||
|
||||
Automatic Filtering
|
||||
-------------------
|
||||
|
||||
TODO - more to come...
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
"""
|
||||
$Id$
|
||||
"""
|
||||
|
|
@ -1,295 +0,0 @@
|
|||
#
|
||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Definition of basic view classes and other browser related stuff for the
|
||||
loops.search package.
|
||||
|
||||
$Id: browser.py 4160 2011-02-07 16:53:08Z helmutm $
|
||||
"""
|
||||
|
||||
from zope import interface, component
|
||||
from zope import traversing
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.formlib.namedtemplate import NamedTemplate, NamedTemplateImplementation
|
||||
from zope.i18nmessageid import MessageFactory
|
||||
|
||||
from cybertools.ajax import innerHtml
|
||||
from cybertools.relation.interfaces import IRelationRegistry
|
||||
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
||||
from cybertools.typology.interfaces import ITypeManager
|
||||
from loops.browser.common import BaseView
|
||||
from loops.browser.node import NodeView
|
||||
from loops.common import adapted, AdapterBase
|
||||
#from loops.expert.browser.search import searchMacrosTemplate as search_template
|
||||
from loops.expert.concept import ConceptQuery, FullQuery
|
||||
from loops.interfaces import IResource
|
||||
from loops.organize.personal.browser.filter import FilterView
|
||||
from loops import util
|
||||
from loops.util import _
|
||||
|
||||
|
||||
#search_template = ViewPageTemplateFile('search.pt')
|
||||
|
||||
|
||||
class Search(BaseView):
|
||||
|
||||
maxRowNum = 0
|
||||
|
||||
@Lazy
|
||||
def search_macros(self):
|
||||
return self.controller.getTemplateMacros('search', search_template)
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
return self.search_macros['search']
|
||||
|
||||
@property
|
||||
def rowNum(self):
|
||||
""" Return the rowNum to be used for identifying the current search
|
||||
parameter row.
|
||||
"""
|
||||
n = self.request.get('loops.rowNum', 0)
|
||||
if n: # if given directly we don't use the calculation
|
||||
return n
|
||||
n = (self.maxRowNum or self.request.get('loops.maxRowNum', 0)) + 1
|
||||
self.maxRowNum = n
|
||||
return n
|
||||
|
||||
@Lazy
|
||||
def presetSearchTypes(self):
|
||||
""" Return a list of concept type info dictionaries (see BaseView)
|
||||
that should be displayed on a separate search parameter row.
|
||||
"""
|
||||
#return ITypeManager(self.context).listTypes(include=('search',))
|
||||
return self.listTypesForSearch(include=('search',))
|
||||
|
||||
def conceptsForType(self, token):
|
||||
result = ConceptQuery(self).query(type=token)
|
||||
fv = FilterView(self.context, self.request)
|
||||
result = fv.apply(result)
|
||||
result.sort(key=lambda x: x.title)
|
||||
noSelection = dict(token='none', title=u'not selected')
|
||||
return [noSelection] + [dict(title=adapted(o, self.languageInfo).title,
|
||||
token=util.getUidForObject(o))
|
||||
for o in result]
|
||||
|
||||
def initDojo(self):
|
||||
self.registerDojo()
|
||||
cm = self.controller.macros
|
||||
jsCall = ('dojo.require("dojo.parser");'
|
||||
'dojo.require("dijit.form.FilteringSelect");'
|
||||
'dojo.require("dojox.data.QueryReadStore");')
|
||||
cm.register('js-execute', jsCall, jsCall=jsCall)
|
||||
|
||||
def listConcepts(self, filterMethod=None):
|
||||
""" Used for dijit.FilteringSelect.
|
||||
"""
|
||||
request = self.request
|
||||
request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8')
|
||||
title = request.get('name')
|
||||
if title == '*':
|
||||
title = None
|
||||
types = request.get('searchType')
|
||||
data = []
|
||||
if title or types:
|
||||
if title is not None:
|
||||
title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ')
|
||||
#title = title.split(' ', 1)[0]
|
||||
if not types:
|
||||
types = ['loops:concept:*']
|
||||
if not isinstance(types, (list, tuple)):
|
||||
types = [types]
|
||||
for type in types:
|
||||
result = self.executeQuery(title=title or None, type=type,
|
||||
exclude=('hidden',))
|
||||
fv = FilterView(self.context, self.request)
|
||||
result = fv.apply(result)
|
||||
for o in result:
|
||||
if o.getLoopsRoot() == self.loopsRoot:
|
||||
adObj = adapted(o, self.languageInfo)
|
||||
if filterMethod is not None and not filterMethod(adObj):
|
||||
continue
|
||||
name = self.getRowName(adObj)
|
||||
if title and title.endswith('*'):
|
||||
title = title[:-1]
|
||||
sort = ((title and name.startswith(title) and '0' or '1')
|
||||
+ name.lower())
|
||||
if o.conceptType is None:
|
||||
raise ValueError('Concept Type missing for %r.' % name)
|
||||
data.append({'label': self.getRowLabel(adObj, name),
|
||||
'name': name,
|
||||
'id': util.getUidForObject(o),
|
||||
'sort': sort})
|
||||
data.sort(key=lambda x: x['sort'])
|
||||
if not title:
|
||||
data.insert(0, {'label': '', 'name': '', 'id': ''})
|
||||
json = []
|
||||
for item in data[:100]:
|
||||
json.append("{label: '%s', name: '%s', id: '%s'}" %
|
||||
(item['label'], item['name'], item['id']))
|
||||
json = "{identifier: 'id', items: [%s]}" % ', '.join(json)
|
||||
#print '***', json
|
||||
return json
|
||||
|
||||
def executeQuery(self, **kw):
|
||||
return ConceptQuery(self).query(**kw)
|
||||
|
||||
def getRowName(self, obj):
|
||||
return obj.getLongTitle()
|
||||
|
||||
def getRowLabel(self, obj, name=None):
|
||||
if isinstance(obj, AdapterBase):
|
||||
obj = obj.context
|
||||
if name is None:
|
||||
name = obj.title
|
||||
return '%s (%s)' % (name, obj.conceptType.title)
|
||||
|
||||
@Lazy
|
||||
def statesDefinitions(self):
|
||||
return [component.getUtility(IStatesDefinition, name=n)
|
||||
for n in self.globalOptions('organize.stateful.resource', ())]
|
||||
|
||||
@Lazy
|
||||
def selectedStates(self):
|
||||
result = {}
|
||||
for k, v in self.request.form.items():
|
||||
if k.startswith('state.') and v:
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
def submitReplacing(self, targetId, formId, view):
|
||||
self.registerDojo()
|
||||
return 'submitReplacing("%s", "%s", "%s"); return false;' % (
|
||||
targetId, formId,
|
||||
'%s/.target%s/@@searchresults.html' % (view.url, self.uniqueId))
|
||||
|
||||
@Lazy
|
||||
def results(self):
|
||||
form = self.request.form
|
||||
type = form.get('search.1.text', 'loops:*')
|
||||
text = form.get('search.2.text')
|
||||
if text is not None:
|
||||
text = util.toUnicode(text, encoding='ISO8859-15') # IE hack!!!
|
||||
useTitle = form.get('search.2.title')
|
||||
useFull = form.get('search.2.full')
|
||||
conceptType = form.get('search.3.type', 'loops:concept:*')
|
||||
#conceptTitle = form.get('search.3.text')
|
||||
#if conceptTitle is not None:
|
||||
# conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15')
|
||||
conceptUid = form.get('search.3.text')
|
||||
result = FullQuery(self).query(text=text, type=type,
|
||||
useTitle=useTitle, useFull=useFull,
|
||||
#conceptTitle=conceptTitle,
|
||||
conceptUid=conceptUid,
|
||||
conceptType=conceptType)
|
||||
rowNum = 4
|
||||
while rowNum < 10:
|
||||
addCriteria = form.get('search.%i.text_selected' % rowNum)
|
||||
rowNum += 1
|
||||
if not addCriteria:
|
||||
break
|
||||
if addCriteria == 'none':
|
||||
continue
|
||||
addSelection = FullQuery(self).query(text=text, type=type,
|
||||
useTitle=useTitle, useFull=useFull,
|
||||
conceptUid=addCriteria)
|
||||
if result:
|
||||
result = [r for r in result if r in addSelection]
|
||||
else:
|
||||
result = addSelection
|
||||
result = [r for r in result if self.checkStates(r)]
|
||||
fv = FilterView(self.context, self.request)
|
||||
result = fv.apply(result)
|
||||
result = sorted(result, key=lambda x: x.title.lower())
|
||||
return self.viewIterator(result)
|
||||
|
||||
def checkStates(self, obj):
|
||||
if not IResource.providedBy(obj):
|
||||
return True
|
||||
for std, states in self.selectedStates.items():
|
||||
if std.startswith('state.resource.'):
|
||||
std = std[len('state.resource.'):]
|
||||
else:
|
||||
continue
|
||||
stf = component.queryAdapter(obj, IStateful, name=std)
|
||||
if stf is None:
|
||||
continue
|
||||
for state in states:
|
||||
if stf.state == state:
|
||||
break
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
#class SearchResults(BaseView):
|
||||
class SearchResults(NodeView):
|
||||
""" Provides results as inner HTML """
|
||||
|
||||
@Lazy
|
||||
def search_macros(self):
|
||||
return self.controller.getTemplateMacros('search', search_template)
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
return self.search_macros['search_results']
|
||||
|
||||
def __call__(self):
|
||||
return innerHtml(self)
|
||||
|
||||
@Lazy
|
||||
def results(self):
|
||||
form = self.request.form
|
||||
type = form.get('search.1.text', 'loops:*')
|
||||
text = form.get('search.2.text')
|
||||
if text is not None:
|
||||
text = util.toUnicode(text, encoding='ISO8859-15') # IE hack!!!
|
||||
useTitle = form.get('search.2.title')
|
||||
useFull = form.get('search.2.full')
|
||||
conceptType = form.get('search.3.type', 'loops:concept:*')
|
||||
#conceptTitle = form.get('search.3.text')
|
||||
#if conceptTitle is not None:
|
||||
# conceptTitle = util.toUnicode(conceptTitle, encoding='ISO8859-15')
|
||||
conceptUid = form.get('search.3.text')
|
||||
result = FullQuery(self).query(text=text, type=type,
|
||||
useTitle=useTitle, useFull=useFull,
|
||||
#conceptTitle=conceptTitle,
|
||||
conceptUid=conceptUid,
|
||||
conceptType=conceptType)
|
||||
rowNum = 4
|
||||
while rowNum < 10:
|
||||
addCriteria = form.get('search.%i.text_selected' % rowNum)
|
||||
rowNum += 1
|
||||
if not addCriteria:
|
||||
break
|
||||
if addCriteria == 'none':
|
||||
continue
|
||||
addSelection = FullQuery(self).query(text=text, type=type,
|
||||
useTitle=useTitle, useFull=useFull,
|
||||
conceptUid=addCriteria)
|
||||
if result:
|
||||
result = [r for r in result if r in addSelection]
|
||||
else:
|
||||
result = addSelection
|
||||
fv = FilterView(self.context, self.request)
|
||||
result = fv.apply(result)
|
||||
result = sorted(result, key=lambda x: x.title.lower())
|
||||
return self.viewIterator(result)
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
<!-- $Id$ -->
|
||||
|
||||
<configure
|
||||
xmlns:zope="http://namespaces.zope.org/zope"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
i18n_domain="zope"
|
||||
>
|
||||
|
||||
<!--<zope:adapter
|
||||
name="search"
|
||||
for="loops.interfaces.IConcept
|
||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
provides="zope.interface.Interface"
|
||||
factory="loops.search.browser.Search"
|
||||
permission="zope.View"
|
||||
/>
|
||||
|
||||
<browser:page
|
||||
name="listConceptsForComboBox.js"
|
||||
for="loops.interfaces.ILoopsObject"
|
||||
class="loops.search.browser.Search"
|
||||
attribute="listConcepts"
|
||||
permission="zope.View"
|
||||
/>
|
||||
|
||||
<browser:page
|
||||
name="searchresults.html"
|
||||
for="loops.interfaces.ILoopsObject"
|
||||
class="loops.search.browser.SearchResults"
|
||||
permission="zope.View"
|
||||
/>-->
|
||||
|
||||
</configure>
|
358
search/search.pt
358
search/search.pt
|
@ -1,358 +0,0 @@
|
|||
<metal:search define-macro="search"
|
||||
i18n:domain="loops">
|
||||
<div id="search"
|
||||
tal:define="macros item/search_macros;
|
||||
idPrefix string:${view/itemNum}.search;
|
||||
formId string:$idPrefix.form;
|
||||
resultsId string:$idPrefix.results;
|
||||
submitted request/search.submitted|nothing">
|
||||
<h1 tal:attributes="class string:content-$level;
|
||||
ondblclick item/openEditWindow"
|
||||
tal:content="item/title">
|
||||
Search
|
||||
</h1>
|
||||
|
||||
<div metal:define-macro="search_form" class="searchForm">
|
||||
<fieldset class="box">
|
||||
<form action="." method="post" id="1.search.form"
|
||||
tal:attributes="id formId">
|
||||
<input type="hidden" name="search.submitted" value="yes" />
|
||||
<input type="hidden" name="search.resultsId" value="1.results"
|
||||
tal:attributes="value resultsId" />
|
||||
<table cellpadding="3">
|
||||
<tal:block repeat="search_param python:
|
||||
['type', 'text', 'concept', 'state']">
|
||||
<metal:row use-macro="macros/search_row" />
|
||||
</tal:block>
|
||||
<tr tal:condition="nothing">
|
||||
<td colspan="2">
|
||||
<input type="button" value="Add concept filter" class="button" />
|
||||
<input type="button" value="Add attribute filter" class="button" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colspan="3"><br />
|
||||
<input type="submit" name="button.search" value="Search" class="submit"
|
||||
i18n:attributes="value"
|
||||
tal:attributes="xx_onclick python:
|
||||
item.submitReplacing(resultsId, formId, view)" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</fieldset>
|
||||
</div>
|
||||
<tal:results condition="request/search.submitted|nothing">
|
||||
<metal:results use-macro="item/search_macros/results" />
|
||||
</tal:results>
|
||||
|
||||
<div metal:define-macro="search_results" id="1.search.results"
|
||||
tal:attributes="id resultsId | request/search.resultsId"
|
||||
i18n:domain="loops"
|
||||
xtal:define="item nocall:view;
|
||||
controller nocall:view/@@controller;
|
||||
resourceBase controller/resourceBase;">
|
||||
<fieldset class="box"
|
||||
tal:condition="request/search.submitted | nothing">
|
||||
<metal:results define-macro="results">
|
||||
<h2 i18n:translate="">Search results</h2>
|
||||
<table class="listing" summary="Search results"
|
||||
i18n:attributes="summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th i18n:translate="">Title</th>
|
||||
<th i18n:translate="">Type</th>
|
||||
<th i18n:translate=""
|
||||
tal:condition="view/useVersioning">V</th>
|
||||
<th i18n:translate="">Size</th>
|
||||
<th i18n:translate="">Modification Date</th>
|
||||
<th i18n:translate="">Author(s)</th>
|
||||
<th i18n:translate=""
|
||||
tal:condition="view/showObjectActions">Info</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tal:items tal:repeat="row item/results">
|
||||
<tal:item define="class python: repeat['row'].odd() and 'even' or 'odd';
|
||||
description row/description">
|
||||
<tr tal:attributes="class class">
|
||||
<td>
|
||||
<a tal:attributes="href
|
||||
string:${view/url}/.target${row/uniqueId}?version=this;
|
||||
title description">
|
||||
<img tal:define="icon row/icon"
|
||||
tal:condition="icon"
|
||||
tal:attributes="src icon/src" />
|
||||
<div tal:content="row/title" /></a>
|
||||
</td>
|
||||
<td i18n:translate="" class="center"
|
||||
tal:content="row/longTypeTitle|row/typeTitle">Type</td>
|
||||
<tal:version condition="view/useVersioning">
|
||||
<td class="center"
|
||||
tal:define="versionId row/versionId|string:">
|
||||
<a href="#"
|
||||
tal:content="versionId"
|
||||
tal:omit-tag="python: versionId and versionId=='1.1'"
|
||||
tal:attributes="href string:${view/url}/.target${row/uniqueId}?loops.viewName=listversions">1.1</a>
|
||||
</td>
|
||||
</tal:version>
|
||||
<td class="nowrap number">
|
||||
<span tal:replace="row/context/sizeForDisplay|string:">Size</span>
|
||||
</td>
|
||||
<td><span tal:replace="row/modified">modified</span></td>
|
||||
<td><span tal:replace="row/creators">John</span></td>
|
||||
<td class="nowrap center"
|
||||
tal:define="target nocall:row;
|
||||
style nothing"
|
||||
tal:condition="view/showObjectActions">
|
||||
<div metal:use-macro="views/node_macros/object_actions" />
|
||||
</td>
|
||||
</tr>
|
||||
</tal:item>
|
||||
</tal:items>
|
||||
</tbody>
|
||||
</table>
|
||||
</metal:results>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</metal:search>
|
||||
|
||||
|
||||
<div metal:define-macro="search_row" id="1.1.row"
|
||||
tal:define="rowNum item/rowNum;
|
||||
basicIdPrefix idPrefix;
|
||||
idPrefix string:$idPrefix.$rowNum;
|
||||
namePrefix string:search.$rowNum;
|
||||
param search_param | item/searchParam"
|
||||
tal:attributes="id string:$idPrefix.row">
|
||||
<div metal:use-macro="macros/?param" />
|
||||
</div>
|
||||
|
||||
|
||||
<metal:text define-macro="type">
|
||||
<tr>
|
||||
<td metal:use-macro="macros/minus"/>
|
||||
<td colspan="3">
|
||||
<h2 i18n:translate="">Type(s) to search for</h2>
|
||||
</td>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<label for="text"
|
||||
tal:attributes="for string:$idPrefix.text">
|
||||
<span i18n:translate="">Type</span>:</label>
|
||||
<select name="text"
|
||||
tal:define="name string:$namePrefix.text;
|
||||
value request/?name|nothing"
|
||||
tal:attributes="name name;
|
||||
id string:$idPrefix.text;">
|
||||
<tal:types repeat="type item/typesForSearch">
|
||||
<option value="loops:*"
|
||||
i18n:translate=""
|
||||
tal:attributes="value type/token;
|
||||
selected python: value == type.token"
|
||||
tal:content="type/title">Topic</option>
|
||||
</tal:types>
|
||||
</select>
|
||||
<input type="button" value="+"
|
||||
title="Add type"
|
||||
tal:condition="nothing" />
|
||||
</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</metal:text>
|
||||
|
||||
|
||||
<metal:text define-macro="text">
|
||||
<tr>
|
||||
<td metal:use-macro="macros/minus"/>
|
||||
<td colspan="3">
|
||||
<h2 i18n:translate="">Text-based search</h2>
|
||||
</td>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<input type="checkbox" value="yes"
|
||||
tal:define="name string:$namePrefix.title"
|
||||
tal:attributes="name name;
|
||||
id string:$idPrefix.title;
|
||||
checked request/?name|not:submitted|string:yes" />
|
||||
<label for="title"
|
||||
i18n:translate=""
|
||||
tal:attributes="for string:$idPrefix.title">Title</label>
|
||||
<input type="checkbox" value="yes"
|
||||
tal:define="name string:$namePrefix.full"
|
||||
tal:attributes="name name;
|
||||
id string:$idPrefix.full;
|
||||
checked request/?name|nothing" />
|
||||
<label for="full"
|
||||
i18n:translate=""
|
||||
tal:attributes="for string:$idPrefix.full">Full text</label>
|
||||
</td>
|
||||
<td>
|
||||
<label for="text"
|
||||
tal:attributes="for string:$idPrefix.text">
|
||||
<span i18n:translate="">Search text</span>:</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text"
|
||||
tal:define="name string:$namePrefix.text"
|
||||
tal:attributes="name name;
|
||||
id string:$idPrefix.text;
|
||||
value request/?name|nothing" />
|
||||
<input type="button" value="+"
|
||||
title="Add search word"
|
||||
tal:condition="nothing" />
|
||||
</td>
|
||||
</tr>
|
||||
</metal:text>
|
||||
|
||||
|
||||
<metal:text define-macro="concept">
|
||||
<tr>
|
||||
<td metal:use-macro="macros/minus"/>
|
||||
<td colspan="3">
|
||||
<h2 i18n:translate="">Search via related concepts</h2>
|
||||
</td>
|
||||
<tr tal:repeat="type item/presetSearchTypes">
|
||||
<tal:preset define="rowNum item/rowNum;
|
||||
idPrefix string:$basicIdPrefix.$rowNum;
|
||||
namePrefix string:search.$rowNum;">
|
||||
<td></td>
|
||||
<td>
|
||||
<span i18n:translate="">Type</span>:
|
||||
<b tal:content="type/title" i18n:translate="" />
|
||||
<input type="hidden" name="type" value=""
|
||||
tal:attributes="name string:$namePrefix.type;
|
||||
value type/token" />
|
||||
</td>
|
||||
<td>
|
||||
<label for="text"
|
||||
tal:attributes="for string:$idPrefix.text">
|
||||
<span i18n:translate="">Concept for Search</span>:</label>
|
||||
</td>
|
||||
<td>
|
||||
<select name="text_selected"
|
||||
tal:define="name string:$namePrefix.text_selected;
|
||||
value request/?name|nothing"
|
||||
tal:attributes="name name;
|
||||
id string:$idPrefix.text;">
|
||||
<tal:concepts repeat="concept python: item.conceptsForType(type['token'])">
|
||||
<option tal:attributes="value concept/token;
|
||||
selected python: value == concept['token']"
|
||||
i18n:translate=""
|
||||
tal:content="concept/title">Zope Corp</option>
|
||||
</tal:concepts>
|
||||
</select>
|
||||
</td>
|
||||
</tal:preset>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<label for="type"
|
||||
tal:attributes="for string:$idPrefix.type">
|
||||
<span i18n:translate="">Type</span>:</label>
|
||||
<select name="type"
|
||||
tal:define="name string:$namePrefix.type;
|
||||
global conceptTypeValue request/?name|string:"
|
||||
tal:attributes="name name;
|
||||
id string:$idPrefix.type;
|
||||
value conceptTypeValue;
|
||||
onChange string:setConceptTypeForComboBox('$idPrefix.type', '$idPrefix.text')">
|
||||
<tal:types repeat="type item/conceptTypesForSearch">
|
||||
<option value="loops:*"
|
||||
i18n:translate=""
|
||||
tal:attributes="value type/token;
|
||||
selected python: conceptTypeValue == type.token"
|
||||
tal:content="type/title">Topic</option>
|
||||
</tal:types>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<label for="text"
|
||||
tal:attributes="for string:$idPrefix.text">
|
||||
<span i18n:translate="">Concept for Search</span>:</label>
|
||||
<input type="text" name="text"
|
||||
tal:condition="nothing"
|
||||
tal:attributes="name string:$namePrefix.text;
|
||||
id string:$idPrefix.text;" />
|
||||
<input type="button" value="+"
|
||||
title="Add type"
|
||||
tal:condition="nothing" />
|
||||
</td>
|
||||
<td>
|
||||
<tal:combo tal:define="dummy item/initDojo;
|
||||
name string:$namePrefix.text;
|
||||
value request/?name|nothing;
|
||||
concept python:
|
||||
value and view.getObjectForUid(value);
|
||||
displayValue python:
|
||||
concept and concept.title or u''">
|
||||
<div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch"
|
||||
url="listConceptsForComboBox.js?searchType="
|
||||
tal:attributes="url
|
||||
string:listConceptsForComboBox.js?searchType=$conceptTypeValue">
|
||||
</div>
|
||||
<input dojoType="dijit.form.FilteringSelect" store="conceptSearch"
|
||||
autoComplete="False" labelAttr="name" style="height: 16px"
|
||||
name="concept.search.text" id="concept.search.text"
|
||||
tal:attributes="name name;
|
||||
id string:$idPrefix.text;
|
||||
displayedValue displayValue" />
|
||||
</tal:combo>
|
||||
</td>
|
||||
</tr>
|
||||
</metal:text>
|
||||
|
||||
|
||||
<metal:text define-macro="state"
|
||||
tal:define="stateDefs item/statesDefinitions;
|
||||
deftype string:resource"
|
||||
tal:condition="stateDefs">
|
||||
<tr>
|
||||
<td metal:use-macro="macros/minus"/>
|
||||
<td colspan="3">
|
||||
<h2 i18n:translate="">Restrict to objects with certain states</h2>
|
||||
</td>
|
||||
<tr>
|
||||
<td></td>
|
||||
<th i18n:translate="">Workflow</th>
|
||||
<th colspan="2"
|
||||
i18n:translate="">States</th>
|
||||
</tr>
|
||||
<tr tal:repeat="def stateDefs">
|
||||
<td></td>
|
||||
<td valign="top"
|
||||
tal:content="def/name"
|
||||
i18n:translate=""></td>
|
||||
<td colspan="2">
|
||||
<tal:states repeat="state def/states">
|
||||
<tal:state define="name string:state.$deftype.${def/name};
|
||||
value state/name">
|
||||
<input type="checkbox"
|
||||
tal:attributes="name string:$name:list;
|
||||
value value;
|
||||
checked python:
|
||||
value in item.selectedStates.get(name, ());
|
||||
id string:$name.$value"
|
||||
/> <label tal:content="state/title"
|
||||
i18n:translate=""
|
||||
tal:attributes="for string:$name.$value" />
|
||||
|
||||
</tal:state>
|
||||
</tal:states>
|
||||
</td>
|
||||
</tr>
|
||||
</metal:text>
|
||||
|
||||
|
||||
<td metal:define-macro="minus">
|
||||
<input type="button" value="−"
|
||||
title="Remove search parameter"
|
||||
tal:condition="python:
|
||||
param not in ['type', 'text', 'concept', 'state']" />
|
||||
</td>
|
|
@ -1,26 +0,0 @@
|
|||
# $Id$
|
||||
|
||||
import unittest, doctest
|
||||
from zope.testing.doctestunit import DocFileSuite
|
||||
from zope.interface.verify import verifyClass
|
||||
|
||||
#from loops.search.interfaces import ...
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
"Basic tests for the loops.search package."
|
||||
|
||||
def testBase(self):
|
||||
pass
|
||||
|
||||
|
||||
def test_suite():
|
||||
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(Test),
|
||||
DocFileSuite('README.txt',
|
||||
optionflags=flags,),
|
||||
))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
Loading…
Add table
Reference in a new issue